• Spring框架(三)AOP


    一、引例

    需求:

    需求1-日志:在程序执行期间追踪正在发生的活动
    需求2-验证:希望计算器只能处理正数的运算

    原始方法 

    接口

    package com.jiehui.spring.aop.helloworld;
    
    public interface ArithmeticCalculator {
    
        void add(int i, int j);
        void sub(int i, int j);
        void mul(int i, int j);
        void div(int i, int j);
    }

    实现

    package com.jiehui.spring.aop.helloworld;
    
    public class ArithmeticCalculatorImpl implements ArithmeticCalculator{
    
        @Override
        public void add(int i, int j) {
            System.out.println("日志:The method add begains with["+i+","+j+"]");
            int result = i + j;
            System.out.println("result:"+result);
            System.out.println("日志:The method add ends with "+result);
            
        }
    
        @Override
        public void sub(int i, int j) {
            System.out.println("日志:The method sub begains with["+i+","+j+"]");
            int result = i - j;
            System.out.println("result:"+result);
            System.out.println("日志:The method sub ends with "+result);
        }
    
        @Override
        public void mul(int i, int j) {
            System.out.println("日志:The method mul begains with["+i+","+j+"]");
            int result = i * j;
            System.out.println("result:"+result);
            System.out.println("日志:The method mul ends with "+result);
        }
    
        @Override
        public void div(int i, int j) {
            System.out.println("日志:The method div begains with["+i+","+j+"]");
            if(j == 0) {System.out.println("errore");}else {
            int result = i/j;
            System.out.println("result:"+result);
            System.out.println("日志:The method div ends with "+result);}
        }    
    }

    运行

    package com.jiehui.spring.aop.helloworld;
    
    public class Main {
    
        public static void main(String[] args) {
            ArithmeticCalculator arithmeticCalculator = null;
            arithmeticCalculator = new ArithmeticCalculatorImpl();
            arithmeticCalculator.add(1, 2);
            arithmeticCalculator.div(4, 2);
        }
    }

    问题:日志和计算的代码混在一起

    改进:使用动态代理

    计算器接口

    package com.aidata.spring.aop;
    
    public interface ArithmeticCalculator {
    
        double add(double i, double j);
        double sub(double i, double j);
        double mul(double i, double j);
        double div(double i, double j);
    
    }

    实现接口

    package com.aidata.spring.aop;
    
    public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
        public double add(double i, double j) {
            return i + j;
        }
    
        public double sub(double i, double j) {
            return  i - j;
        }
    
        public double mul(double i, double j) {
            return  i * j;
        }
    
        public double div(double i, double j) {
            return  i / j;
        }
    }
    CalculatorLoggingHandler
    package com.aidata.spring.aop;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.ArrayList;
    import java.util.Arrays;
    
    public class CalculatorLoggingHandler implements InvocationHandler {
    
        private Log log = LogFactory.getLog(this.getClass());
    
        private Object target;
    
        public CalculatorLoggingHandler(Object target){
            this.target = target;
        }
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log.info("Method " + method.getName() + " begains with " + Arrays.toString(args));
            Object result = method.invoke(target, args);
            log.info("Method " + method.getName() + " ends with " + result);
            return result;
        }
    
        public static Object createProxy(Object target){
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CalculatorLoggingHandler(target));
        }
    
    }
    CalculatorValidationHandler
    package com.aidata.spring.aop;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    
    public class CalculatorValidationHandler implements InvocationHandler {
    
        private Object target;
    
        public CalculatorValidationHandler(Object target){
            this.target = target;
        }
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            for (Object arg: args){
                validate((Double)arg);
            }
            Object result = method.invoke(target, args);
            return result;
        }
    
        public static Object createProxy(Object target){
            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CalculatorValidationHandler(target));
        }
    
        private void validate(double a){
            if (a < 0)
                throw  new IllegalArgumentException("positive number only");
        }
    }

    测试

    package com.aidata.spring.aop;
    
    public class Main {
    
        public static void main(String[] args) {
            ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorImpl();
            ArithmeticCalculator arithmeticCalculator1 = (ArithmeticCalculator) CalculatorValidationHandler.createProxy(CalculatorValidationHandler.createProxy(arithmeticCalculator));
            System.out.println(arithmeticCalculator1.add(-12, 13));
        }
    }

    结果

    代理设计模式的原理: 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。

    任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上。

    但是如果每次都这么编写动态代理太麻烦了,把动态代理的功能封装起来简化使用就好了。

    二、AOP简介

    面向切面编程

    使用AOP可以简化上述工作

    AOP(Aspect-Oriented Programming, 面向切面编程):是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充.
    AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点.
    在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里.
    AOP 的好处:

      • 每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级
      • 业务模块更简洁, 只包含核心业务代码

    横切关注点的两种实现方法

    软件系统,可看作由一组关注点组成。其中,直接的业务关注点,是直切关注点。而为直切关注点提供服务的,就是横切关注点

    有两种方法可以提供横切关注点,一种是传统的OOP方法,提供一个与直切关注点的实现一样的类来提供服务。另一种是最新的AOP方法,提供一个Aspect方面(Spring AOP中叫advisor顾问)来提供服务。

    OOP方式是:业务类使用对象引用,使用“委派”的方式,调用横切类的方法(服务)。

    这是“主叫”式的服务。业务类用显示代码来呼叫服务。这在业务类中增加了与“直切关注点”概念无关的代码,破坏了封装性,增加了业务类和服务之间的耦合。

    AOP方式是:提供一个Aspect方面,这个方面的概念类似于“类”,它封装了“横切关注点”的实现代码。并且还提供了“切入点”。切入点是“连接点”的集合。切入点就是定义了Aspect方面为哪些类的哪些方法提供服务。

    AOP的实现方式有很多种。最早的方式是编译时织入。AspectJ就是使用这种方式。AspectJ的特殊编译器将业务类和Aspect方面的代码组装在一起,从而实现服务的无缝接入。

    这种方式,源代码中的业务方法和Aspect方面无关。

    另一种很典型、很精巧的实现方式是使用动态代理模拟实现AOP。这是现在最流行的方式。JBoss AOP框架和Spring AOP框架都使用这种方式。

    这里我介绍一下Spring AOP框架提供横切关注点服务的方式。编写一个方面,这个方面也是一个一般的pojo类,但是它需要提供一个接口。

    然后,使用Advisor顾问(就是方面,是方面+切入点)进行配置,配置接口和切入点模式。在程序运行到连接点方法时,构建一个动态代理类,返回一个匿名的java类,而不是直接使用业务类。这个匿名类组装了业务类和Advise建议(就是方面,SpringAOP的术语)。

    由此可见,AOP实现横切关注点的方式要比OOP方式好得多。

    而在AOP实现中,我更欣赏Spring AOP 这样运用动态代理模式实现的AOP。这种方式不需要任何辅助工具即可开发AOP。

    但是,对于开发AOP也要注意一点:  Spring AOP的advice代码只能够在连接点方法之前、之后调用,或者在异常被抛出之后调用。 

    这样,就要求我们的目标方法(就是连接点,Advice要捆在它上面)必须够小,要把一个大方法分割成多个小方法。这就需要“重构”技术来帮忙。当然,这不是什么缺点,反而是一个让你养成好习惯的机会。“面向方法重构!”

    AOP这种技术的提出是一个了不起的成就。这个技术的始作俑者AspectJ本身的实现技术十分笨拙。

    AOP思想实际上是一个软件设计思想的发展,“横切关注点”的发现,使施乐的科学家们创造了AOP这样一种“面向方面(横切关注点)编程”的思想。也使他们生造出了笨拙的怪胎AspectJ。而另一些也在苦苦思索OOP面临问题的一线程序高手,立刻从AOP思想中获得久久寻找中的解决之道。

    他们从设计模式中翻出了“动态代理模式”和“装修者模式”,用她们优雅的实现了JBossAOP,SpringAOP这样的pojo型的AOP解决方案!

    原文链接:https://blog.csdn.net/shendl/article/details/526362

    相关术语

    软件系统,可看作由一组关注点组成。其中,直接的业务关注点,是直切关注点。而为直切关注点提供服务的,就是横切关注点

    横切关注点从概念上来讲是与应用的业务逻辑(直切关注点)相分离的,把这些横切关注点与业务逻辑分离正是面向切面编程(AOP)所要解决的问题。前面计算器程序中在指定的地方输出日志就是一个横切关注点。

    在使用面向切面编程时,仍在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的业务类。

    横切关注点可以被模块化为特殊的类,这些类被称为切面。上面中编写日志这个关注点我们将其写为了一个动态代理类。

    通知(Advice)   
    切面的工作被称为通知。就是你想要的功能,也就是上面说的日志,就是通知。要把它们先定义好,以备在想用的地方使用。

    Spring有5种类型的通知:

    • 前置通知(Before):在目标方法被调用之前调用通知功能。
    • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么。
    • 返回通知(After-returning):在目标方法成功执行之后调用通知。
    • 异常通知(After-throwing):在目标方法抛出异常之后调用通知。
    • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

    连接点(JoinPoint)

    连接点是一个应用执行过程中能够插入一个切面的点,是程序执行过程中能够应用通知的所有点,切面代码可以利用这些点插入到应用的正规流程中。

    这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。

    切入点(Pointcut)

    连接点告诉了我们所有可以使用通知的点,切入点则是我们真的去使用通知的点。婚姻而言,理论上你可以和所有女生交往,但最终只娶了一个,前者连接点,后者切入点。

    如果通知定义了“什么”和“何时”。那么切点就定义了“何处”。切点会匹配通知所要织入的一个或者多个连接点,通常使用明确的类或者方法来指定这些切点。

    上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。

    切面(Aspect)
    切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义

    我们一般会定义切面类,里面的方法是通知,通知上的注解告诉我们何使用该方法即是切入点。

    引入(introduction)
    允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗。

    目标(target)
    引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
    代理(proxy)
    怎么实现整套aop机制的,都是通过代理,这个一会给细说。
    织入(weaving)

    织入是将切面应用到目标对象来创建的代理对象过程。
    切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可以织入

    1. 编译期——切面在目标类编译时期被织入,这种方式需要特殊编译器。AspectJ的织入编译器就是以这种方式织入切面。

    2. 类加载期——切面在类加载到 JVM ,这种方式需要特殊的类加载器,他可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的 LTW 就支持这种织入方式

    3. 运行期——切面在应用运行期间的某个时刻被织入。一般情况下,在织入切面时候,AOP 容器会为目标对象动态的创建代理对象。Spring AOP 就是以这种方式织入切面。

    把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时。
    关键就是:切点定义了哪些连接点会得到通知

    三、Spring AOP

    Spring AOP是基于动态代理的, Spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用伪装类,伪装类中就先执行了切面,再把调用转发给真正的目标bean。

    Spring 提供的 4 种各具特色的 AOP 支持:

    • 基于代理的经典 AOP;
    • @AspectJ 注解驱动的切面;
    • 纯 POJO 切面;
    • 注入式 AspectJ 切面;

    AspectJ是Java 社区里最完整最流行的 AOP 框架,在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP。
    Spring基于动态代理,所以只支持方法连接点。Spring缺少对字段连接点的支持,无法让我创建更细粒度的通知,且不支持构造器连接点,无法在bean创建时应用通知。可以利用Aspect来补充Spring AOP的功能。

    step1 通过切点选择连接点

    step2 创建并声明切面

    • 使用注解声明切面
    • 使用XML声明切面

    使用注解声明切面

    装配切面

    创建后只是Spring容器中的一个bean并不会被当成切面,需要进行配置:

    • JavaConfig
    • XML

    使用XML声明切面

    如果不使用注解创建切面可以使用该方法。

    step3 注入AspectJ切面

  • 相关阅读:
    如何修改自定义Webpart的标题?(downmoon)
    vs2003 和vs2005下的发送SMTP邮件
    Entity Framework 4.1 之八:绕过 EF 查询映射
    Entity Framework 4.1 之七:继承
    Entity Framework 4.1 之四:复杂类型
    Entity Framework 4.1 之三 : 贪婪加载和延迟加载
    MVC2 强类型的 HTML Helper
    EF Code First 和 ASP.NET MVC3 工具更新
    Entity Framework 4.1 之六:乐观并发
    Entity Framework 4.1 之一 : 基础
  • 原文地址:https://www.cnblogs.com/aidata/p/12301144.html
Copyright © 2020-2023  润新知