AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。
AOP 是软件开发思想阶段性的产物,我们比较熟悉面向过程 OPP 和面向对象 OOP,AOP 是 OOP 的延续,但不是 OOP 的替代,而是作为 OOP 的有益补充。
参考《Spring 实战 (第4版)》和《精通Spring4.x 企业应用开发实战》两本书的AOP章节和其他资料将其知识点整理起来。
部分代码实例摘自《精通Spring4.x 企业应用开发实战》,文末我会给出两本书的PDF下载地址!
AOP概述 AOP为 Aspect Oriented Programming 的缩写,意为:面向切面编程。那么什么又是面向切面呢?
我知道面向对象的特性:封装、继承、多态。通过抽象出代码中共有特性来优化编程,但是这种方式又往往不能完全适用任何场景,无法避免的造成代码之间的耦合。
如下面的代码,运用的OOP思想将共有操作抽象了出来,抽象出了两个处理类:性能监视 PerformanceMonitor
和事务管理 TransactionManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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
1 2 3 4 5 6 package com.spring05;interface ForumService { public void removeTopic (int topicId) ; public void removeForum (int forumId) ; }
ForumServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.spring05;public class PerformanceMonitor { 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
1 2 3 4 5 6 7 8 9 package com.spring05;public class TestForumService { public static void main (String[] args) { ForumService forumService = new ForumServiceImpl(); forumService.removeForum(10 ); forumService.removeTopic(1012 ); } }
执行 TestForumService.main
输出结果:
1 2 3 4 5 6 7 8 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
中非业务逻辑的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.spring06;public class ForumServiceImpl implements ForumService { @Override public void removeTopic (int topicId) { System.out.println("模拟删除Topic记录:" +topicId); try { Thread.currentThread().sleep(20 ); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void removeForum (int forumId) { System.out.println("模拟删除Forum记录:" +forumId); try { Thread.currentThread().sleep(40 ); } catch (InterruptedException e) { e.printStackTrace(); } } }
创建横切代码处理类:
PerformanceHandler.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.spring06;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;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
调用流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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); ForumService proxy = (ForumService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler); proxy.removeForum(10 ); proxy.removeTopic(1012 ); } }
运行 TestForumService.main()
输出结果:
1 2 3 4 5 6 7 8 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:
1 2 3 4 5 <dependency > <groupId > cglib</groupId > <artifactId > cglib</artifactId > <version > 3.2.8</version > </dependency >
CglibProxy.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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
调用流程:
1 2 3 4 5 6 7 8 9 10 11 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()
输出结果:
1 2 3 4 5 6 7 8 begin monitor...模拟删除Forum记录:10 end monitor...com.spring07.ForumServiceImpl$ $ EnhancerByCGLIB$ $ 3123332 f.removeForum花费60 毫秒 begin monitor...模拟删除Topic记录:1023 end monitor...com.spring07.ForumServiceImpl$ $ EnhancerByCGLIB$ $ 3123332 f.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
1 2 3 4 5 6 7 8 9 10 package com.spring09; public interface Phone { public void call (String str) ; public void sendMsg (String str) ; }
手机接口实现类: BndPhone
1 2 3 4 5 6 7 8 9 10 11 12 13 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
1 2 3 4 5 6 7 8 9 10 package com.spring09;public interface PhoneExtend { public void listenMusic (String str) ; public void watchVideo (String str) ; }
手机扩展功能接口实现类: BndPhoneExtend
1 2 3 4 5 6 7 8 9 10 11 12 13 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
这里面定义了切面。这里需要先引入 aspectjrt 和 aspectjweaver 的 jar 包,Maven 的配置代码会在后面贴出。
1 2 3 4 5 6 7 8 9 10 11 12 13 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配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?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:aspectj-autoproxy /> <bean id ="phone" class ="com.spring09.BndPhone" /> <bean id ="phoneExtend" class ="com.spring09.BndPhoneExtend" /> <bean class ="com.spring09.EnablePhoneExtendAspect" /> </beans >
测试类: Test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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" ); PhoneExtend phoneExtend = (PhoneExtend) phone; phoneExtend.listenMusic("BNDong" ); phoneExtend.watchVideo("BNDong" ); } }
执行 Test.main()
输出结果:
1 2 3 4 打电话 - BNDong 发短信 - BNDong 听音乐 - BNDong 看视频 - BNDong
可以看到 BndPhone
并没有实现 PhoneExtend
接口,但是通过引介/引入切面 BndPhone
拥有了 PhoneExtend
的实现。
我是通过 Maven 构建的 Spring,这里我附上我 Maven 的 pom:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 <?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 > <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 > <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 > <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 > <plugins > <plugin > <artifactId > maven-clean-plugin</artifactId > <version > 3.0.0</version > </plugin > <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 >
当引入接口方法被调用时,代理对象会把此调用委托给实现了新接口的某个其他对象。实际上,一个Bean的实现被拆分到多个类中。
在XML中声明切面 基于注解的配置要优于基于Java的配置,基于Java的配置要优于基于XML的配置。
Spring 的 AOP 配置元素能够以非侵入性的方式声明切面。
同样使用上面手机的实例代码,我们去掉注解类通过 XML 配置的方式实现切面。
首先删除注解类 EnablePhoneExtendAspect
,这时再运行 Test.main()
就会抛出异常:
1 2 3 4 打电话 - 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 配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?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:aspectj-autoproxy /> <aop:config > <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()
发现切面配置成功:
1 2 3 4 打电话 - 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