SpringAOP详解(上)
切入点表达式
within(包名.类名) 粗粒度表达式(一般不使用)
最高精确到类
举例
<aop:pointcut expression="within(com.wiscom.service.UserServletImply)" id="pc01"/>
within中可以使用通配符
*
:匹配当前包
<aop:pointcut expression="within(com.wiscom.service.*)" id="pc01"/>
‘
*
: 匹配包(只针对最后一个包中的类)
<aop:pointcut expression="within(com.wiscom.*.*)" id="pc01"/>
‘匹配所有子包中的所有类
..*
: 匹配指定包及其子孙包下的所有类
<aop:pointcut expression="within(com.wiscom.service..*)" id="pc01"/>
‘
execution() 细粒度表达式(常使用)
execution(返回值类型 包名.类名.方法名(参数类型,参数类型…))
最高精确到方法
举例
<!-- 切出指定包下指定类下指定名称指定参数指定返回值的方法 --> <aop:pointcut expression="execution(void com.wiscom.service.UserServiceImpl.addUser(java.lang.String))" id="pc1"/> <!-- 切出指定包下所有的类中的query方法,要求无参,但返回值类型不限 --> <aop:pointcut expression="execution(* com.wiscom.service.*.query())" id="pc1"/> <!-- 切出指定包及其子孙包下所有的类中的query方法,要求无参,但返回值类型不限 --> <aop:pointcut expression="execution(* com.wiscom.service..*.query())" id="pc1"/> <!-- 切出指定包及其子孙包下所有的类中的query方法,要求参数为int java.langString类型,但返回值类型不限 --> <aop:pointcut expression="execution(* com.wiscom.service..*.query(int,java.lang.String))" id="pc1"/> <!-- 切出指定包及其子孙包下所有的类中的query方法,参数数量及类型不限,返回值类型不限 --> <aop:pointcut expression="execution(* com.wiscom.service..*.query(..))" id="pc1"/> <!-- 切出指定包及其子孙包下所有的类中的任意方法,参数数量及类型不限,返回值类型不限。这种写法等价于within表达式的功能 --> <aop:pointcut expression="execution(* com.wiscom.service..*.*(..))" id="pc1"/>
五大通知类型
前置通知(@Before)
在目标方法前执行
<aop:before method="before" pointcut-ref="pc01"/>
可以传入目标对象参数:JoinPoint
JoinPoint必须放在第一个参数位置
@Before("execution(* com.wiscom.service..*.*(..))") public void before(JoinPoint jp){ System.out.println("前置通知开始执行..."); }
环绕通知(@Around)
在目标方法执行之前之后都可以执行额外代码
<aop:around method="around" pointcut-ref="pc01"/>
默认目标方法不执行
执行目标方法需要引入参数:ProcessdingJoinPoint
ProceedingJoinPoint必须放在第一个参数位置
需要手动设置返回值
如果不设置返回值,真正的调用者无法拿到返回值
@Around("execution(* com.wiscom.service..*.*(..))") public Object abc(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("环绕通知开始执行..."); Object obj = pjp.proceed(); System.out.println("环绕通知执行完毕..."); return obj; }
后置通知(@AfterReturning)
在目标方法执行之后执行
<aop:after-returning method="afterReturning" pointcut-ref="pc01" returning="msg"/>
可以选择性的接收一个JoinPoint
需放在第一个参数位置
如果有需要,可以通过配置获取返回值
返回值配置在参数列表中,再配置文件中也需要配置
@AfterReturning(value = "execution(* com.wiscom.service..*.*(..))",returning="msg") public void afterReturning(JoinPoint jp,String msg){ }
异常通知(@AfterThrowing)
在目标方法抛出异常时执行
<aop:after-throwing method="afterThrowing" pointcut-ref="pc01" throwing="e"/>
可以传入JoinPoint,获取目标对象和目标方法相关信息
也可以配置参数,让异常通知可以接收到目标方法抛出的异常对象
在配置文件中也需要进行配置
@AfterThrowing(value="execution(* com.wiscom.service..*.*(..))",throwing ="e") public void exceptionFunc(JoinPoint jp,Throwable e){ }
最终通知(@After)
是在目标方法执行之后执行,一定会执行的通知
<aop:after method="afterFinal" pointcut-ref="pc01"/>
也可以额外接收一个JoinPoint参数
@After public void afterFinal(JoinPoint jp){ System.out.println("second最终通知..."); }
五种通知的执行顺序
见笔记,或者自己操作进行验证
如果存在多个切面
采用了责任链设计模式
五大通知常见使用场景
前置通知 (@Before) | 记录日志(方法将被调用) |
---|---|
环绕通知 (@Around) | 控制事务 权限控制 |
后置通知 (@AfterReturning) | 记录日志(方法已经成功调用) |
异常通知 (@AfterThrowing) | 异常处理 控制事务 |
最终通知 (@After) | 记录日志(方法已经调用,但不一定成功) |
四个练习
异常信息收集
统计业务方法执行时间
通过AOP进行权限控制
实现事务控制
异常信息收集
采用异常通知(@AfterThrowing)
com.wiscom.aspect.FirstAspect
@AfterThrowing(value="execution(* com.wiscom.service..*(..))",throwing="e") public void ThrowMethod(JoinPoint join,Throwable e){ System.out.println("异常信息:"+e.getMessage()); Object target = join.getTarget(); System.out.println("产生异常的类:"+target.getClass()); // class com.wiscom.service.UserServiceImpl System.out.println("产生异常类中的方法:"+join.getSignature().getName()); // loginUser }
统计业务方法执行的时间
使用环绕通知(@Around)
com.wiscom.aspect.FirstAspect
@Around("execution(* com.wiscom.service..*(..))") public Object around(ProceedingJoinPoint join) throws Throwable{ long start = System.currentTimeMillis(); // 执行方法 Object obj = join.proceed(); long end = System.currentTimeMillis(); System.out.println("方法执行了"+(end-start)+"ms"); return obj; }
通过AOP进行权限控制
使用环绕通知(@Around)
com.wiscom.anno.PrivAnno:代表权限的注解
package com.wiscom.anno; @Target(ElementType.METHOD) //注解使用在方法上 @Retention(RetentionPolicy.RUNTIME)//保证注解保留在运行阶段 public @interface PrivAnno { // 代表权限的数组 PrivEnum[] value() default PrivEnum.CUST; }
PrivEnum:枚举出权限的类别
package com.wiscom.enums; public enum PrivEnum { CUST,USER,ADMIN }
com.wiscom.service.UserServiceImpl
package com.wiscom.service; @Service public class UserServiceImpl implements UserService{ @Autowired private UserDao dao; @Override //游客 用户 管理员都可以调用 public void registUser(User user) { dao.addUser(user); } @Override // 用户和管理员可以调用 @PrivAnno({PrivEnum.USER,PrivEnum.ADMIN}) public void updateUser(User user) { // 可以执行权限 dao.updateUser(user); } @Override //只有管理员可以删除 @PrivAnno(PrivEnum.ADMIN) public void deleteUser(int id) { dao.deleteUser(id); } @Override //用户和管理员可以查询 @PrivAnno({PrivEnum.USER,PrivEnum.ADMIN}) public User queryUser(int id) { return dao.queryUser(id); } }
com.wiscom.aspect.PrivAspect
package com.wiscom.aspect; @Component @Aspect public class PrivAspect { @Around("execution(* com.wiscom.service.*.*(..))") public Object privFunc(ProceedingJoinPoint pjp) throws Throwable{ // 在环绕通知中根据注解的权限决定要不要执行目标方法 Object obj; // 1.获取被拦截下来的方法 MethodSignature ms = (MethodSignature) pjp.getSignature(); Method method = ms.getMethod();// 获取的默认是接口的方法 // 需要获取的是实际运行时调用的方法 // 获取目标对象 Object targetObj = pjp.getTarget(); // 获取实际调用的方法 Method instanceMethod = targetObj.getClass().getMethod(method.getName(), method.getParameterTypes()); // 2.判断方法上面是否有PrivAnno注解 if (instanceMethod.isAnnotationPresent(PrivAnno.class)){ // 代码执行到这一行,代表方法上面有权限注解 // 3.获取被拦截方法上面的注解的权限 PrivAnno anno = instanceMethod.getAnnotation(PrivAnno.class); PrivEnum[] privs = anno.value(); // [admin,user] // 4.获取当前用户的权限 PrivEnum role = TestDemo.role;//cust // 5.判断注解上面的权限是否包含当前用户的权限 if (Arrays.asList(privs).contains(role)){ // 6.包含权限,执行目标方法 obj = pjp.proceed(); return obj; }else{ // 7.如果权限不足,那么抛出异常 throw new RuntimeException("权限不足"); } }else{// 没有权限对应的注解 // 直接执行 obj = pjp.proceed(); return obj; } } }