zoukankan      html  css  js  c++  java
  • Spring:AOP面向切面编程

    AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

    AOP是软件开发思想阶段性的产物,我们比较熟悉面向过程OPP和面向对象OOP,AOP是OOP的延续,但不是OOP的替代,而是作为OOP的有益补充。

    参考《Spring 实战 (第4版)》和《精通Spring4.x 企业应用开发实战》两本书的AOP章节和其他资料将其知识点整理起来。

    部分代码实例摘自《精通Spring4.x 企业应用开发实战》,文末我会给出两本书的PDF下载地址!

    AOP概述

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程。那么什么又是面向切面呢?

    我知道面向对象的特性:封装、继承、多态。通过抽象出代码中共有特性来优化编程,但是这种方式又往往不能完全适用任何场景,无法避免的造成代码之间的耦合。

    如下面的代码,运用的OOP思想将共有操作抽象了出来,抽象出了两个处理类:性能监视 PerformanceMonitor 和事务管理 TransactionManager 

    package com.smart.concept;
     
    public class ForumService {
        private TransactionManager transManager;
        private PerformanceMonitor pmonitor;
        private TopicDao topicDao;
        private ForumDao forumDao;
    
        public void removeTopic(int topicId) {
            pmonitor.start();
            transManager.beginTransaction();
            topicDao.removeTopic(topicId); //
            transManager.commit();
            pmonitor.end();
        }
    
        public void createForum(Forum forum) {
            pmonitor.start();
            transManager.beginTransaction();
            forumDao.create(forum); //
            transManager.commit();
            pmonitor.end();
        }
    }

    ①、②处是两个方法 removeTopic 和 createForum 独有的业务逻辑,但它们淹没在了重复化非业务性代码之中。这种抽象为纵向抽取。

    将removeTopic和createForum进行横切重新审视:

    我们无法通过纵向抽取的方式来消除代码的重复性。然而切面能帮助我们模块化横切关注点,横切关注点可以被描述为影响应用多处的功能。例如,性能监视和事务管理分别就是一个横切关注点。

    AOP提供了取代继承和委托的另一种可选方案,那就是横向抽取,可以在很多场景下更清晰简洁。在使用面向切面编程时,我们仍然在一个地方定义通用功能,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称为切面(aspect)。这样做有两个好处:首先,现在每个关注点都集中于一个地方,而不是分散到多处代码中;其次,服务模块更简洁,因为它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中了。

    AOP术语

    AOP不容易理解的一方面原因就是概念太多,并且由英语直译过来的名称也不统一,这里我选择使用较多的译名并给出英文供大家参考。

    连接点(Joinpoint)

    程序执行的某个时间点,如类初始化前/后,某个方法执行前/后,抛出异常前/后。

    Spring仅支持方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时这些程序执行点织入增强。

    切点(Poincut)

    一个程序类可以有多个连接点,但是如果某一部分连接点需要用什么来定位呢?那就是切点,这么说可能有点抽象。借助数据库查询的概念来理解切点和连接点的关系再适合不过了:连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。

    在 Spring 中, 所有的方法都可以认为是 joinpoint, 但是我们并不希望在所有的方法上都添加 Advice, 而 Pointcut 的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice.

    增强/通知(Advice)

    将一段执行逻辑添加到切点,并结合切点信息定位到具体的连接点,通过拦截来执行逻辑。

    Spring切面可以应用5种类型的通知:

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

    目标对象(Target)

    增强逻辑的织入目标对象。目标对象也被称为 advised object 

    因为 Spring AOP 使用运行时代理的方式来实现 aspect, 因此 adviced object 总是一个代理对象(proxied object)
    注意, adviced object 指的不是原来的类, 而是织入 advice 后所产生的代理类.

    织入(Weaving)

    将增强添加到目标类的具体连接点上的过程。在目标对象的生命周期里有多个点可以进行织入:

    • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。
    • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。
    • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。

    引介/引入(Introduction)

    向现有的类添加新的方法或属性。

    Spring AOP 允许我们为 目标对象 引入新的接口(和对应的实现). 例如我们可以使用 introduction 来为一个 bean 实现 IsModified 接口, 并以此来简化 caching 的实现。

    代理(Proxy)

    一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以可以采用与调用原类相同的方式调用代理类。

    在 Spring AOP 中, 一个 AOP 代理是一个 JDK 动态代理对象或 CGLIB 代理对象.

    切面(Aspect)

    切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。

    Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入切面所指定的连接点中。

    Spring对AOP的支持

    Spring提供了4种类型的AOP支持:

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

    Spring AOP原理

    Spring AOP的底层原理就是动态代理

    Java实现动态代理的方式有两种:

    • 基于JDK的动态代理。
    • 基于CGLib的动态代理。

    JDK动态代理是需要实现某个接口了,而我们类未必全部会有接口,于是CGLib代理就有了。

    • CGLib代理其生成的动态代理对象是目标类的子类。
    • Spring AOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理

    那么JDK代理和CGLib代理我们该用哪个呢??在《精通Spring4.x 企业应用开发实战》给出了建议:

    • 如果是单例的我们最好使用CGLib代理,如果是多例的我们最好使用JDK代理

    原因:

    • JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。
    • 如果是单例的代理,推荐使用CGLib

    看到这里我们就应该知道什么是Spring AOP(面向切面编程)了:将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能

    • 这样一来,我们就在写业务时只关心业务代码,而不用关心与业务无关的代码

    代码实例

    带有横切逻辑的实例

    ForumService.java

    package com.spring05;
    
    interface ForumService {
        public void removeTopic(int topicId);
        public void removeForum(int forumId);
    }

    ForumServiceImpl.java

    package com.spring05;
    
    public class ForumServiceImpl implements ForumService{
        @Override
        public void removeTopic(int topicId) {
    
            // 开始对该方法进行性能监视
            PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeTopic");
            System.out.println("模拟删除Topic记录:"+topicId);
            try {
                Thread.currentThread().sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 结束对该方法的性能监视
            PerformanceMonitor.end();
        }
    
        @Override
        public void removeForum(int forumId) {
    
            // 开始对该方法进行性能监视
            PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeForum");
            System.out.println("模拟删除Forum记录:"+forumId);
            try {
                Thread.currentThread().sleep(40);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 结束对该方法的性能监视
            PerformanceMonitor.end();
        }
    }

    MethodPerformance.java

    package com.spring05;
    
    public class MethodPerformance {
        private long begin;
        private long end;
        private String serviceMethod;
    
        public MethodPerformance(String serviceMethod) {
            this.serviceMethod = serviceMethod;
            this.begin = System.currentTimeMillis(); // 记录目标类方法开始执行点的系统时间
        }
    
        public void printPerformance() {
            this.end = System.currentTimeMillis(); // 获取目标类方式执行完成后的系统时间,进而计算出目标类方法的执行时间
            long elapse = end - begin;
            System.out.println(serviceMethod + "花费" + elapse + "毫秒");
        }
    }

    PerformanceMonitor.java

    package com.spring05;
    
    public class PerformanceMonitor {
        // 通过一个ThreadLocal保存与调用线程相关的性能监视信息
        private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();
    
        // 启动对某一目标方法的性能监视
        public static void begin(String method) {
            System.out.println("begin monitor...");
            MethodPerformance mp = new MethodPerformance(method);
            performanceRecord.set(mp);
        }
    
        public static void end() {
            System.out.println("end monitor...");
            MethodPerformance mp = performanceRecord.get();
    
            // 打印出方法性能监视的结果信息
            mp.printPerformance();
        }
    }

    TestForumService.java

    package com.spring05;
    
    public class TestForumService {
        public static void main(String[] args) {
            ForumService forumService = new ForumServiceImpl();
            forumService.removeForum(10);
            forumService.removeTopic(1012);
        }
    }

    执行 TestForumService.main 输出结果:

    begin monitor...
    模拟删除Forum记录:10
    end monitor...
    com.spring05.ForumServiceImpl.removeForum花费42毫秒
    begin monitor...
    模拟删除Topic记录:1012
    end monitor...
    com.spring05.ForumServiceImpl.removeTopic花费28毫秒

    在 ForumServiceImpl 中的方法中仍然有非业务逻辑的性能监视代码(每个业务方法都有性能监视的开启和关闭代码),这破坏了方法的纯粹性。下面通过JDK动态代理和CGLib动态代理使非业务逻辑的性能监视代码动态的织入目标方法,以优化代码结构。

    PS:上面代码可以拷贝出一份,下面举例大多都是以上面代码为基础改进的。

    JDK动态代理

    先注释掉 ForumServiceImpl 中非业务逻辑的代码:

    package com.spring06;
    
    public class ForumServiceImpl implements ForumService{
        @Override
        public void removeTopic(int topicId) {
    
            // 开始对该方法进行性能监视
    //        PerformanceMonitor.begin("com.smart.proxy.ForumServiceImpl.removeTopic");
            System.out.println("模拟删除Topic记录:"+topicId);
            try {
                Thread.currentThread().sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 结束对该方法的性能监视
    //        PerformanceMonitor.end();
        }
    
        @Override
        public void removeForum(int forumId) {
    
            // 开始对该方法进行性能监视
    //        PerformanceMonitor.begin("com.smart.proxy.ForumServiceImpl.removeForum");
            System.out.println("模拟删除Forum记录:"+forumId);
            try {
                Thread.currentThread().sleep(40);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 结束对该方法的性能监视
    //        PerformanceMonitor.end();
        }
    }

    创建横切代码处理类:

    PerformanceHandler.java

    package com.spring06;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * 横切代码处理类,实现InvocationHandler接口
     */
    public class PerformanceHandler implements InvocationHandler {
       private Object target;
    
       public PerformanceHandler(Object target) {
           // 设置目标业务类
           this.target = target;
       }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 开始性能监视
            PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName());
            // 通过反射机制调用目标对象方法
            Object obj = method.invoke(target, args);
            // 结束性能监视
            PerformanceMonitor.end();
            return obj;
        }
    }

    修改 TestForumService 调用流程:

    package com.spring06;
    
    import java.lang.reflect.Proxy;
    
    public class TestForumService {
        public static void main(String[] args) {
            
            // 希望被代理的目标业务类
            ForumService target = new ForumServiceImpl();
    
            // 将目标业务类和横切代码编织到一起
            PerformanceHandler handler = new PerformanceHandler(target);
    
            // 根据编织了目标业务逻辑和性能监视横切逻辑的InvocationHandler实例创建代理实例
            ForumService proxy = (ForumService) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    handler);
            
            // 调用代理实例
            proxy.removeForum(10);
            proxy.removeTopic(1012);
        }
    }

    运行 TestForumService.main() 输出结果:

    begin monitor...
    模拟删除Forum记录:10
    end monitor...
    com.spring06.ForumServiceImpl.removeForum花费41毫秒
    begin monitor...
    模拟删除Topic记录:1012
    end monitor...
    com.spring06.ForumServiceImpl.removeTopic花费21毫秒

    运行效果是一致的,横切逻辑代码抽取到了 PerformanceHandler 中。当其他业务类的业务方法需要性能监视时候,只需要为他们创建代理类就行了。

    CGLib动态代理

    使用JDK创建代理有一个限制,即它只能为接口创建代理实例,CGLib作为一个替代者,填补了这项空缺。

    使用CGLib之前,需要先导入CGLib的jar包:

    GitHub:https://github.com/cglib/cglib

    Maven:

    <dependency>
          <groupId>cglib</groupId>
          <artifactId>cglib</artifactId>
          <version>3.2.8</version>
    </dependency>

    CglibProxy.java

    package com.spring07;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
     
    public class CglibProxy implements MethodInterceptor {
        private Enhancer enhancer = new Enhancer();
    
        public Object getProxy(Class clazz) {
    
            // 设置需要创建子类的类
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);
            // 通过字节码技术动态创建子类实例
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            // 拦截父类所有的方法
            // 开始性能监视
           PerformanceMonitor.begin(o.getClass().getName() + "." + method.getName());
            // 通过代理类调用父类中的方法
            Object result = methodProxy.invokeSuper(o, objects);
            // 结束性能监视
            PerformanceMonitor.end();
            return result;
        }
    }

    修改 TestForumService 调用流程:

    package com.spring07;
    
    public class TestForumService {
        public static void main(String[] args) {
            CglibProxy proxy = new CglibProxy();
            // 通过动态生成子类的方式创建代理类
            ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class);
            forumService.removeForum(10);
            forumService.removeTopic(1023);
         }
    }

    运行 TestForumService.main() 输出结果:

    begin monitor...
    模拟删除Forum记录:10
    end monitor...
    com.spring07.ForumServiceImpl$$EnhancerByCGLIB$$3123332f.removeForum花费60毫秒
    begin monitor...
    模拟删除Topic记录:1023
    end monitor...
    com.spring07.ForumServiceImpl$$EnhancerByCGLIB$$3123332f.removeTopic花费21毫秒

    这里可以看到类名变成了com.spring07.ForumServiceImpl$$EnhancerByCGLIB$$3123332f.removeTopic,这个就是CGLib动态创建的子类。

    由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中的 final 或 private 方法进行代理。

    基于注解和命名空的AOP编程

    Spring在新版本中对AOP功能进行了增强,体现在这么几个方面:

    • 在XML配置文件中为AOP提供了aop命名空间
    • 增加了AspectJ切点表达式语言的支持
    • 可以无缝地集成AspectJ

    Spring借鉴了AspectJ的切面,以提供注解驱动的AOP。这种AOP风格的好处在于能够不使用XML来完成功能。

    使用引介/引入功能实现为Bean引入新方法

    为了更好的理解,这里我新举个例子:

    手机接口: Phone 

    package com.spring09;
    
    public interface Phone {
    
        // 打电话
        public void call(String str);
    
        // 发短信
        public void sendMsg(String str);
    }

    手机接口实现类: BndPhone 

    package com.spring09;
    
    public class BndPhone implements Phone {
        @Override
        public void call(String str) {
            System.out.println("打电话 - " + str);
        }
    
        @Override
        public void sendMsg(String str) {
            System.out.println("发短信 - " + str);
        }
    }

    手机扩展功能接口: PhoneExtend 

    package com.spring09;
    
    public interface PhoneExtend {
    
        // 听音乐
        public void listenMusic(String str);
    
        // 看视频
        public void watchVideo(String str);
    }

    手机扩展功能接口实现类: BndPhoneExtend 

    package com.spring09;
    
    public class BndPhoneExtend implements PhoneExtend {
        @Override
        public void listenMusic(String str) {
            System.out.println("听音乐 - " + str);
        }
    
        @Override
        public void watchVideo(String str) {
            System.out.println("看视频 - " + str);
        }
    }

    注解类: EnablePhoneExtendAspect 这里面定义了切面。这里需要先引入aspectjrtaspectjweaver的jar包,Maven的配置代码会在后面贴出。

    package com.spring09;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.DeclareParents;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class EnablePhoneExtendAspect {
        @DeclareParents(value = "com.spring09.BndPhone",  // 指定手机具体的实现
                defaultImpl = BndPhoneExtend.class) // 手机扩展具体的实现
        public PhoneExtend phoneExtend; // 要实现的目标接口
    }

    Spring XML配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 开启注解扫描 -->
        <context:component-scan base-package="com.spring09"/>
    
        <!-- 开启aop注解方式,默认为false -->
        <aop:aspectj-autoproxy/>
    
        <bean id="phone" class="com.spring09.BndPhone"/>
        <bean id="phoneExtend" class="com.spring09.BndPhoneExtend"/>
        <bean class="com.spring09.EnablePhoneExtendAspect"/>
    </beans>

    测试类: Test 

    package com.spring09;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class Test {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml");
    
            Phone phone = (Phone) ctx.getBean("phone");
    
            // 调用手机原有的方法
            phone.call("BNDong");
            phone.sendMsg("BNDong");
    
            // 通过引介/引入切面已经将phone实现了PhoneExtend接口,所以可以强制转换
            PhoneExtend phoneExtend = (PhoneExtend) phone;
            phoneExtend.listenMusic("BNDong");
            phoneExtend.watchVideo("BNDong");
        }
    }

    执行 Test,main() 输出结果:

    打电话 - BNDong
    发短信 - BNDong
    听音乐 - BNDong
    看视频 - BNDong

    可以看到 BndPhone 并没有实现 PhoneExtend 接口,但是通过引介/引入切面 BndPhone 拥有了 PhoneExtend 的实现。

    我是通过Maven构建的Spring,这里我附上我Maven的pom:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.spring09</groupId>
        <artifactId>spring09-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <name>spring09-demo</name>
        <!-- FIXME change it to the project's website -->
        <url>http://www.example.com</url>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <maven.compiler.source>1.7</maven.compiler.source>
            <maven.compiler.target>1.7</maven.compiler.target>
            <org.springframework.version>4.3.7.RELEASE</org.springframework.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.11</version>
                <scope>test</scope>
            </dependency>
    
            <!-- spring start -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-expression</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-instrument</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-instrument-tomcat</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jms</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-messaging</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-orm</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-oxm</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-webmvc-portlet</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-websocket</artifactId>
                <version>${org.springframework.version}</version>
            </dependency>
            <!-- spring end -->
    
            <dependency>
                <groupId>aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>1.5.4</version>
            </dependency>
    
            <dependency>
                <groupId>aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.5.4</version>
            </dependency>
    
        </dependencies>
    
        <build>
            <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
                <plugins>
                    <plugin>
                        <artifactId>maven-clean-plugin</artifactId>
                        <version>3.0.0</version>
                    </plugin>
                    <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                    <plugin>
                        <artifactId>maven-resources-plugin</artifactId>
                        <version>3.0.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.7.0</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.20.1</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-jar-plugin</artifactId>
                        <version>3.0.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-install-plugin</artifactId>
                        <version>2.5.2</version>
                    </plugin>
                    <plugin>
                        <artifactId>maven-deploy-plugin</artifactId>
                        <version>2.8.2</version>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    </project>
    View Code
    当引入接口方法被调用时,代理对象会把此调用委托给实现了新接口的某个其他对象。实际上,一个Bean的实现被拆分到多个类中。

    在XML中声明切面

    基于注解的配置要优于基于Java的配置,基于Java的配置要优于基于XML的配置。

    Spring的AOP配置元素能够以非侵入性的方式声明切面。

    同样使用上面手机的实例代码,我们去掉注解类通过XML配置的方式实现切面。

    首先删除注解类 EnablePhoneExtendAspect ,这时再运行 Test.main() 就会抛出异常:

    打电话 - BNDong
    发短信 - BNDong
    Exception in thread "main" java.lang.ClassCastException: com.spring10.BndPhone cannot be cast to com.spring10.PhoneExtend
        at com.spring10.Test.main(Test.java:17)

    修改Spring XML配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 开启注解扫描 -->
        <context:component-scan base-package="com.spring10"/>
    
        <!-- 开启aop注解方式,默认为false -->
        <aop:aspectj-autoproxy/>
    
        <aop:config> <!-- 顶层aop配置元素 -->
            <aop:aspect> <!-- 定义一个切面 -->
                <aop:declare-parents
                        types-matching="com.spring10.BndPhone"
                        implement-interface="com.spring10.PhoneExtend"
                        delegate-ref="phoneExtend"/>
            </aop:aspect>
        </aop:config>
    
        <bean id="phone" class="com.spring10.BndPhone"/>
        <bean id="phoneExtend" class="com.spring10.BndPhoneExtend"/>
    </beans>
    • <aop:config> :顶层aop配置元素。
    • <aop:aspect> :定义一个切面。
    • <aop:declare-parents> :声明切面所通知的bean要在它的对象层次结构中拥有新的父类型。
      • types-matching :类型匹配的接口实现。
      • implement-interface :要实现的接口。
      • delegate-ref :引用一个Spring bean作为引入的委托。
      • default-impl :用全限定类名来显示指定实现。

    这时运行 Test.main() 发现切面配置成功:

    打电话 - BNDong
    发短信 - BNDong
    听音乐 - BNDong
    看视频 - BNDong

    切面类型总结图:

    总结

    • Spring采用JDK动态代理和CGLib动态代理技术在运行期织入增强。
    • JDK动态代理需要目标类实现接口,而CGLib不对目标类作任何限制。
    • JDK在创建代理对象时的性能高于CGLib,而生成的代理对象的运行性能却比CGLib的低。
    • Spring只能在方法级别上织入增强。
    • Spring的AOP配置元素能够以非侵入性的方式声明。
    • 当Spring AOP不能满足需求时,我们必须转向更为强大的AspectJ。

    参考资料

    《Spring实战(第4版)》Craig Walls 著 / 张卫滨 译    下载(密码:8ts2)

    《精通Spring 4.x 企业应用开发实战》陈雄华 林开雄 文建国 编著    下载(密码:my25)

    https://baike.baidu.com/item/AOP/1332219?fr=aladdin

    https://juejin.im/post/5b06bf2df265da0de2574ee1

    https://segmentfault.com/a/1190000007469968

  • 相关阅读:
    HDU 5671 矩阵
    HDU 5670
    UVA 11995 STL 使用
    VK Cup 2016
    字段定义
    apache用户
    apache
    使用第三方登录
    setex()
    如果客户端禁用了cookie,如何实现session
  • 原文地址:https://www.cnblogs.com/bndong/p/9809656.html
Copyright © 2011-2022 走看看