java springboot AOP切面编程
AOP
我们现在从Controller的角度来看这个代理对象,本来我们的Controller在使用Service时,Service的实例对象是容器提供给我们的,现在如果我们需要代理对象完成新增的较长业务,代理的对象创建也应该交给容器来实现,这个过程就是aop。aop:面相切面编程(百度,务必要总结)。面向对象编程oop。
在通知spring容器创建代理对象时,我们需要告知容器以下几点:
1、哪些类的哪些方法需要使用代理对象。
2、指出交叉业务是在目标方法的什么位置实现:前面,后面,前后、出现异常时
3、交叉业务的逻辑
添加依赖
直接引用springboot的依赖,同时引入aop的相关依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
配置启动类
@SpringBootApplication
@EnableAspectJAutoProxy //启动切面(即启动自动代理)
public class AopApp {
public static void main(String[] args) {
SpringApplication.run(AopApp.class, args);
}
配置切面Aspect
切面 = 被增强的目标对象 + 切入点(需要增加交叉业务的目标方法)+ 交叉业务逻辑
//切面类
@Component //切面类也需要创建实例
@Aspect //表示当前类是一个切面类
public class MyAspect {
//定义切入点Pointcut(指明哪些类的哪些方法需要增强(交叉业务))
//value属性接收一个execution表达式,我们使用execution表达式来确认目标方法
//下面示例中的表达式表示要为com.wngz.aop.service.UserService接口中所有的方法,无论什么参数都要进行增强
@Pointcut(value = "execution(* com.wngz.aop.service.*.*(..))")
public void initPointcut() {
//方法中啥也不用写,关键是这个方法上的切入点相关注解
}
//在所有目标方法运行前,显示提示文字. JoinPoint连接点,就是执行核心业务的方法
@Before(value = "initPointcut()")
public void myBefore(JoinPoint joinPoint) {
System.out.printf("------- 马上要开始执行方法(%s)了 -------\n", joinPoint.getSignature().getName());
}
}
修改UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
加上这个注解后,我们还是通过spring容器正常获取UserService,但是得到的对象就是增强后的对象了
测试类
@SpringBootTest(classes = {AopApp.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class AppTest {
@Autowired
UserService userService;
@Test
public void myTest() {
userService.login("alice", "678");
System.out.println(userService.getMessage(11));
}
}
Aop控制方法连续点击
@Aspect
@Component
public class IdemAspect {
private final RedisTemplate redisTemplate;
public IdemAspect(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Before(value = "@annotation(idemAnnotation)")
public void before(JoinPoint joinPoint, IdemAnnotation idemAnnotation) {
String key = joinPoint.getSignature().getName();
for (Object arg : joinPoint.getArgs()) {
key += "_" + arg.hashCode();
}
if (redisTemplate.hasKey(key)) {
throw new BusinessException(BusinessErrorEnum.REPEAT_SUBMIT);
}
redisTemplate.opsForValue().set(key, "", idemAnnotation.timeout(), idemAnnotation.timeunit());
}
}
增强的时机
//切面类
@Component //切面类也需要创建实例
@Aspect //表示当前类是一个切面类
public class MyAspect {
@Before(value = "@annotation(idemAnnotation)")//有注解idemAnnotation的方法为切入点
public void before(JoinPoint joinPoint, IdemAnnotation idemAnnotation) {
//相关方法
}
//定义切入点Pointcut(指明哪些类的哪些方法需要增强(交叉业务))
//value属性接收一个execution表达式,我们使用execution表达式来确认目标方法
//下面示例中的表达式表示要为com.wngz.aop.service.UserService接口中所有的方法,无论什么参数都要进行增强
@Pointcut(value = "execution(* com.aop.service.UserService.*(..))")
public void initPointcut() {
//方法中啥也不用写,关键是这个方法上的切入点相关注解
}
//在所有目标方法运行前,显示提示文字. JoinPoint连接点,就是执行核心业务的方法
@Before(value = "initPointcut()")
public void myBefore(JoinPoint joinPoint) {
System.out.printf("------- 马上要开始执行方法(%s)了 -------\n", joinPoint.getSignature().getName());
}
//在目标方法成功执行后做增强
@AfterReturning(value = "initPointcut()")
public void mySuccess(JoinPoint joinPoint) {
System.out.printf("------- 方法(%s)正常执行结束了 -------\n", joinPoint.getSignature().getName());
}
//使用@After注解,无论是否发生异常都会触发
@After(value = "initPointcut()")
public void myFinally(JoinPoint joinPoint) {
System.out.printf("+++++++ 方法(%s)执行结束了 +++++++\n\n", joinPoint.getSignature().getName());
}
//拦截异常(必须明确指定异常变量的名称)
@AfterThrowing(value = "initPointcut()", throwing = "ex10")
public void handleException(JoinPoint joinPoint, Exception ex10) {
System.out.printf("+++++++ 执行方法(%s)出现异常,原因是:%s +++++++\n",
joinPoint.getSignature().getName(),
ex10.getMessage());
}
//环绕增强, 注意环绕增加方法中的参数类型不是简单的切入点,而是可以执行的切入点类型
@SneakyThrows
@Around(value = "initPointcut()")
public Object around(ProceedingJoinPoint pjp) {
long startTime = System.currentTimeMillis();
Object result = pjp.proceed();
long endTime = System.currentTimeMillis();
System.out.printf("====== 方法(%s)用时(%s)毫秒 ======\n",
pjp.getSignature().getName(),
endTime - startTime);
return result;
}
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void login(String username, String pwd) {
System.out.printf("用户%s登录成功!\n", username);
}
@Override
public String getMessage(Integer id) {
//int i = 1 / 0; //如果测试环绕增强,就不要制造异常,否则看不到返回结果
return "用户的id为:" + id;
}
}
制造异常,验证("@AfterThrowing")的使用效果
execution表达式
execution表达式目的是选中我们要加入AOP编程的方法,即连接点。匹配特定包中的特定类中特定返回值类型的特定参数的特定方法。
语法:execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
通过方法签名定义切点
execution(public * *(..))
匹配所有目标类的public方法,但不匹配protected void showGoods()方法。第一个*代表返回类型,第二个*代表方法名,而..代表任意形参的方法;
execution(* *To(..))
匹配目标类所有以To为后缀的方法。第一个*代表返回类型,而*To代表任意以To为后缀的方法;
通过类定义切点
execution(* com.baobaotao.UserService.*(..))
匹配Waiter接口的所有方法,第一个*代表返回任意类型,com.baobaotao.Waiter.*代表Waiter接口中的所有方法
execution(* com.baobaotao.Waiter+.*(..))
匹配Waiter接口及其所有实现类的方法,它不但匹配实现类中接口定义的方法,同时还匹配没有在Waiter 接口中定义的其他方法。
通过包定义切点
在类名模式串中,"."表示包下的所有类,而".."表示包、子孙包下的所有类。
execution(* com.baobaotao.*(..))
匹配com.baobaotao包下所有类的所有方法;
execution(* com.baobaotao..*(..))
匹配com.baobaotao包、子孙包下所有类的所有方法,如com.baobaotao.dao,com.baobaotao.servier以及 com.baobaotao.dao.user包下的所有类的所有方法都匹配。".."出现在类名中时,后面必须跟"*",表示包、子孙包下的所有类;
execution(* com..*.*Dao.find*(..))
匹配包名前缀为com的任何包下类名后缀为Dao的类,方法名必须以find为前缀。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切点。
<!--配置pointcut切入点,匹配名称以ServiceImpl结尾类中所有方法-->
@Pointcut(value = "execution(* com.woniu.spring_ioc.service.impl.*ServiceImpl.*(..))")
<!-- 配置pointcut切入点,匹配com.woniu.spring_ioc.service.impl包下所有类中所有方法。Impl后面的两个点之间其实是匹配所有后代类名。最后那个*匹配的是所有方法名 -->
@Pointcut(value = "execution(* com.woniu.spring_ioc.service.impl..*(..))")
<!-- OrderService接口中方法名以st结尾的方法 -->
@Pointcut(value = "execution(* com.woniu.spring_ioc.service.OrderService.*st(..))")
<!-- OrderServiceImpl类中方法名以in结尾的方法 -->
@Pointcut(value = "execution(* com.woniu.spring_ioc.service.impl.OrderServiceImpl.*in(..))")
<!-- service包中所有类的方法名以logo开始的方法 -->
@Pointcut(value = "execution(* com.woniu.spring_ioc.service.*.logo*(..))")
<!-- 类名以Impl结束的类中所有包含两个字符串参数的方法 -->
@Pointcut(value = "execution(* com.woniu.spring_ioc..*Impl.*(String, String))")
原文地址:https://blog.csdn.net/wyh2943455568/article/details/143466319
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!