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 这里面定义了切面。这里需要先引入aspectjrt和aspectjweaver的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>
当引入接口方法被调用时,代理对象会把此调用委托给实现了新接口的某个其他对象。实际上,一个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