03.AOP

作者: apieceof2_d368 | 来源:发表于2020-04-10 18:44 被阅读0次

    面向切面

    假设这样的情况, 你有三个类, 需要这三个类都有 "日志" 的功能. 如果用一般面向对象的思维来做的话 , 也许需要这三个类都实现一个 "日志" 的接口, 或者继承一个父类, 这三个类中的方法的时候调用父类或接口中的日志方法.

    这样听起来就很麻烦, 更好的方法是, 利用代理, 让一个代理类继承目标类的接口(为了让这个代理类拥有目标类的基本功能), 然后在调用方法的时候同时调用日志方法.

    代理设计模式是Spring AOP 的核心. 如果使用了AOP 代理Bean, 当调用一个 Bean 的方法时, Spring 会拦截这个调用, 给这个方法的某一步(执行前, 返回前, 执行后, 抛出错误后, 或者全部)加上需要的功能. 再进行调用

    AOP 的一些名词

    了解了面向切面, 就应该了解一下有关的术语.

    • 通知: 通知就是被代理的方法在执行的时候添加的额外的内容
    • 切点: 切点定义了通知在什么地方起作用
    • 切面: 通知和切点结合就是切面

    Spring对AOP的支持

    • 基于代理的经典Spring AOP
    • 纯 POJO 切面
    • @AspectJ注解驱动的切面
    • 注入式AspectJ切面(适用于Spring各个版本)

    第一种太经典了, 书里没讲

    纯 POJO 切面用 XML 配置

    @AspectJ注解挺好, 可以用注解完成, 不用XML

    前三种都是基于代理的 AOP, 功能上仅限于方法拦截, 如果对 AOP 的需求有构造器或属性拦截. 或者要代理的目标类没有接口. 那么需要考虑 AspectJ

    通过切点来选择连接点

    Spring 借助 AspectJ 切点表达式来定义 Spring 切面.

    比如说, 要给一个目标类的方法加上一个通知, 在 AOP 中不需要对目标类进行修改. 要编写一个类, 在这个类中定义切点, 再写一个方法作为通知, Spring 会把这个通知应用到切点所定位到的目标类方法中去

    切点怎么写

    假如有一个目标类, 他的接口是这样的

    public interface Perfoormance{
        public void perform();
    }
    

    需要给 perform() 方法加上通知. AspectJ 表达式如下

    execution(* concert.Performance.perform(..))
    

    第一个 * 表示 返回类型随意, concert.Performance.perform 表示方法名, 两个英文句号表示参数随意

    编写切面

    @Aspect
    public class Audience{
        @Before("execution(** concert.Performance.perform(..))")
        public void silenceCellPhones(){
            
            System.out.println("Silencing cell phones");
        }
        
        @After("execution(** concert.Performance.perform(..))")
        public void after(){
            
            System.out.println("after");
        }
        
        @AfterReturning("execution(** concert.Performance.perform(..))")
        public void afterReturning(){
            
            System.out.println("after returning");
        }
        
        @AfterThrowing("execution(** concert.Performance.perform(..))")
        public void afterThrowing(){
            
            System.out.println("after Throwing");
        }
    }
    

    从这里看出来, 通知类型有before, after, afterreturning afterthrowing

    如果要重复使用切点可以这样:

    @Aspect
    public class Audience{
        @Pointcut("execution(** concert.Performance.perform(..))")
        public void performance();
        
        @Before("performance")
        public void before(){
            System.out.println("before");
        }
    }
    

    完成以后, 还需要再配置文件中开启 AspectJ 自动代理

    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan
    public class Configuration(){}
    

    用 XML 开启 AspectJ 自动代理

    <aop:aspectj-autoproxy />
    

    创建环绕通知

    前面提到的几种通知类型, 都只是再方法的某一个位置进行通知. 而环绕通知是可以对方法全方位多角度地代理

    @Aspect
    public class Audience{
        // 这里声明一个 performance 的切点
        @Around("performance()")
        public void around(ProceedingJoinPoing jp){
            try{
                System.out.println("before");
                jp.proceed();
            }catch(Throwable e){
                // ..
                System.out.println("afterThrowing")
            }finally{
                System.out.println("After")
            }
        }
    }
    

    jp.proceed() 就是在运行原始方法, 在这行代码前面的部分相当于 before 通知, catch 中相当于 afterThrowing(); finally 中的相当于 after, 这里无法表现出 afterreturing, 如果如果报错, afterreturing是不会运行的

    有参数的情况

    如果目标类要被代理的方法有参数, 应该这么写

    表达式

    execution(* soundsystem.CompactDisc.playTract(int)) && args(trackNumber)
    

    通知

    @Before("pointcut(trackNumber)")
    public void countTrack(int trackNumber){
        //...
    }
    

    首先, 表达式要匹配到这个有参数的方法, 然后, 方法的 @Before 标签要有一个参数, 这个参数是参数名, 方法的参数也需要接收一个参数名相同的参数.

    相关文章

      网友评论

          本文标题:03.AOP

          本文链接:https://www.haomeiwen.com/subject/vfjdmhtx.html