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 {@AutowiredUserService userService;@Testpublic 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 {@Overridepublic void login(String username, String pwd) {System.out.printf("用户%s登录成功!\n", username);}@Overridepublic 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))")