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;
        }
        
    }
}

 

 

posted @ 2020-08-19 15:09  minnersun  阅读(76)  评论(0编辑  收藏  举报