AOP是软件开发思想阶段性的产物,咱们比较熟悉面向过程OPP和面向对象OOP,AOP是OOP的延续,但不是OOP的替代,而是做为OOP的有益补充。html
参考《Spring 实战 (第4版)》和《精通Spring4.x 企业应用开发实战》两本书的AOP章节和其余资料将其知识点整理起来。java
部分代码实例摘自《精通Spring4.x 企业应用开发实战》,文末我会给出两本书的PDF下载地址!git
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程。那么什么又是面向切面呢?github
我知道面向对象的特性:封装、继承、多态。经过抽象出代码中共有特性来优化编程,可是这种方式又每每不能彻底适用任何场景,没法避免的形成代码之间的耦合。web
以下面的代码,运用的OOP思想将共有操做抽象了出来,抽象出了两个处理类:性能监视 PerformanceMonitor 和事务管理 TransactionManager spring
1 package com.smart.concept; 2 3 public class ForumService { 4 private TransactionManager transManager; 5 private PerformanceMonitor pmonitor; 6 private TopicDao topicDao; 7 private ForumDao forumDao; 8 9 public void removeTopic(int topicId) { 10 pmonitor.start(); 11 transManager.beginTransaction(); 12 topicDao.removeTopic(topicId); // ① 13 transManager.commit(); 14 pmonitor.end(); 15 } 16 17 public void createForum(Forum forum) { 18 pmonitor.start(); 19 transManager.beginTransaction(); 20 forumDao.create(forum); // ② 21 transManager.commit(); 22 pmonitor.end(); 23 } 24 }
①、②处是两个方法 removeTopic 和 createForum 独有的业务逻辑,但它们淹没在了重复化非业务性代码之中。这种抽象为纵向抽取。数据库
将removeTopic和createForum进行横切从新审视:express
咱们没法经过纵向抽取的方式来消除代码的重复性。然而切面能帮助咱们模块化横切关注点,横切关注点能够被描述为影响应用多处的功能。例如,性能监视和事务管理分别就是一个横切关注点。apache
AOP提供了取代继承和委托的另外一种可选方案,那就是横向抽取,能够在不少场景下更清晰简洁。在使用面向切面编程时,咱们仍然在一个地方定义通用功能,可是能够经过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的类。横切关注点能够被模块化为特殊的类,这些类被称为切面(aspect)。这样作有两个好处:首先,如今每一个关注点都集中于一个地方,而不是分散到多处代码中;其次,服务模块更简洁,由于它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被转移到切面中了。编程
AOP不容易理解的一方面缘由就是概念太多,而且由英语直译过来的名称也不统一,这里我选择使用较多的译名并给出英文供你们参考。
程序执行的某个时间点,如类初始化前/后,某个方法执行前/后,抛出异常前/后。
一个程序类能够有多个链接点,可是若是某一部分链接点须要用什么来定位呢?那就是切点,这么说可能有点抽象。借助数据库查询的概念来理解切点和链接点的关系再适合不过了:链接点至关于数据库中的记录,而切点至关于查询条件。切点和链接点不是一对一的关系,一个切点能够匹配多个链接点。
将一段执行逻辑添加到切点,并结合切点信息定位到具体的链接点,经过拦截来执行逻辑。
Spring切面能够应用5种类型的通知:
加强逻辑的织入目标对象。目标对象也被称为 advised object
将加强添加到目标类的具体链接点上的过程。在目标对象的生命周期里有多个点能够进行织入:
向现有的类添加新的方法或属性。
一个类被AOP织入加强后,就产生了一个结果类,它是融合了原类和加强逻辑的代理类。根据不一样的代理方式,代理类既多是和原类具备相同接口的类,也可能就是原类的子类,因此能够采用与调用原类相同的方式调用代理类。
切面由切点和加强/通知组成,它既包括了横切逻辑的定义、也包括了链接点的定义。
Spring提供了4种类型的AOP支持:
Spring AOP的底层原理就是动态代理。
Java实现动态代理的方式有两种:
JDK动态代理是须要实现某个接口了,而咱们类未必所有会有接口,因而CGLib代理就有了。
那么JDK代理和CGLib代理咱们该用哪一个呢??在《精通Spring4.x 企业应用开发实战》给出了建议:
缘由:
看到这里咱们就应该知道什么是Spring AOP(面向切面编程)了:将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来同样的功能。
1 package com.spring05; 2 3 interface ForumService { 4 public void removeTopic(int topicId); 5 public void removeForum(int forumId); 6 }
1 package com.spring05; 2 3 public class ForumServiceImpl implements ForumService{ 4 @Override 5 public void removeTopic(int topicId) { 6 7 // 开始对该方法进行性能监视 8 PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeTopic"); 9 System.out.println("模拟删除Topic记录:"+topicId); 10 try { 11 Thread.currentThread().sleep(20); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 // 结束对该方法的性能监视 16 PerformanceMonitor.end(); 17 } 18 19 @Override 20 public void removeForum(int forumId) { 21 22 // 开始对该方法进行性能监视 23 PerformanceMonitor.begin("com.spring05.ForumServiceImpl.removeForum"); 24 System.out.println("模拟删除Forum记录:"+forumId); 25 try { 26 Thread.currentThread().sleep(40); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 // 结束对该方法的性能监视 31 PerformanceMonitor.end(); 32 } 33 }
1 package com.spring05; 2 3 public class MethodPerformance { 4 private long begin; 5 private long end; 6 private String serviceMethod; 7 8 public MethodPerformance(String serviceMethod) { 9 this.serviceMethod = serviceMethod; 10 this.begin = System.currentTimeMillis(); // 记录目标类方法开始执行点的系统时间 11 } 12 13 public void printPerformance() { 14 this.end = System.currentTimeMillis(); // 获取目标类方式执行完成后的系统时间,进而计算出目标类方法的执行时间 15 long elapse = end - begin; 16 System.out.println(serviceMethod + "花费" + elapse + "毫秒"); 17 } 18 }
1 package com.spring05; 2 3 public class PerformanceMonitor { 4 // 经过一个ThreadLocal保存与调用线程相关的性能监视信息 5 private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>(); 6 7 // 启动对某一目标方法的性能监视 8 public static void begin(String method) { 9 System.out.println("begin monitor..."); 10 MethodPerformance mp = new MethodPerformance(method); 11 performanceRecord.set(mp); 12 } 13 14 public static void end() { 15 System.out.println("end monitor..."); 16 MethodPerformance mp = performanceRecord.get(); 17 18 // 打印出方法性能监视的结果信息 19 mp.printPerformance(); 20 } 21 }
1 package com.spring05; 2 3 public class TestForumService { 4 public static void main(String[] args) { 5 ForumService forumService = new ForumServiceImpl(); 6 forumService.removeForum(10); 7 forumService.removeTopic(1012); 8 } 9 }
执行 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:上面代码能够拷贝出一份,下面举例大多都是以上面代码为基础改进的。
先注释掉 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(); } }
建立横切代码处理类:
1 package com.spring06; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 6 /** 7 * 横切代码处理类,实现InvocationHandler接口 8 */ 9 public class PerformanceHandler implements InvocationHandler { 10 private Object target; 11 12 public PerformanceHandler(Object target) { 13 // 设置目标业务类 14 this.target = target; 15 } 16 17 @Override 18 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 19 // 开始性能监视 20 PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName()); 21 // 经过反射机制调用目标对象方法 22 Object obj = method.invoke(target, args); 23 // 结束性能监视 24 PerformanceMonitor.end(); 25 return obj; 26 } 27 }
修改 TestForumService 调用流程:
1 package com.spring06; 2 3 import java.lang.reflect.Proxy; 4 5 public class TestForumService { 6 public static void main(String[] args) { 7 8 // 但愿被代理的目标业务类 9 ForumService target = new ForumServiceImpl(); 10 11 // 将目标业务类和横切代码编织到一块儿 12 PerformanceHandler handler = new PerformanceHandler(target); 13 14 // 根据编织了目标业务逻辑和性能监视横切逻辑的InvocationHandler实例建立代理实例 15 ForumService proxy = (ForumService) Proxy.newProxyInstance( 16 target.getClass().getClassLoader(), 17 target.getClass().getInterfaces(), 18 handler); 19 20 // 调用代理实例 21 proxy.removeForum(10); 22 proxy.removeTopic(1012); 23 } 24 }
运行 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 中。当其余业务类的业务方法须要性能监视时候,只须要为他们建立代理类就好了。
使用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>
1 package com.spring07; 2 3 import net.sf.cglib.proxy.Enhancer; 4 import net.sf.cglib.proxy.MethodInterceptor; 5 import net.sf.cglib.proxy.MethodProxy; 6 7 import java.lang.reflect.Method; 8 9 public class CglibProxy implements MethodInterceptor { 10 private Enhancer enhancer = new Enhancer(); 11 12 public Object getProxy(Class clazz) { 13 14 // 设置须要建立子类的类 15 enhancer.setSuperclass(clazz); 16 enhancer.setCallback(this); 17 // 经过字节码技术动态建立子类实例 18 return enhancer.create(); 19 } 20 21 @Override 22 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 23 // 拦截父类全部的方法 24 // 开始性能监视 25 PerformanceMonitor.begin(o.getClass().getName() + "." + method.getName()); 26 // 经过代理类调用父类中的方法 27 Object result = methodProxy.invokeSuper(o, objects); 28 // 结束性能监视 29 PerformanceMonitor.end(); 30 return result; 31 } 32 }
修改 TestForumService 调用流程:
1 package com.spring07; 2 3 public class TestForumService { 4 public static void main(String[] args) { 5 CglibProxy proxy = new CglibProxy(); 6 // 经过动态生成子类的方式建立代理类 7 ForumServiceImpl forumService = (ForumServiceImpl) proxy.getProxy(ForumServiceImpl.class); 8 forumService.removeForum(10); 9 forumService.removeTopic(1023); 10 } 11 }
运行 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 方法进行代理。
Spring在新版本中对AOP功能进行了加强,体如今这么几个方面:
Spring借鉴了AspectJ的切面,以提供注解驱动的AOP。这种AOP风格的好处在于可以不使用XML来完成功能。
为了更好的理解,这里我新举个例子:
手机接口: Phone
1 package com.spring09; 2 3 public interface Phone { 4 5 // 打电话 6 public void call(String str); 7 8 // 发短信 9 public void sendMsg(String str); 10 }
手机接口实现类: BndPhone
1 package com.spring09; 2 3 public class BndPhone implements Phone { 4 @Override 5 public void call(String str) { 6 System.out.println("打电话 - " + str); 7 } 8 9 @Override 10 public void sendMsg(String str) { 11 System.out.println("发短信 - " + str); 12 } 13 }
手机扩展功能接口: PhoneExtend
1 package com.spring09; 2 3 public interface PhoneExtend { 4 5 // 听音乐 6 public void listenMusic(String str); 7 8 // 看视频 9 public void watchVideo(String str); 10 }
手机扩展功能接口实现类: BndPhoneExtend
1 package com.spring09; 2 3 public class BndPhoneExtend implements PhoneExtend { 4 @Override 5 public void listenMusic(String str) { 6 System.out.println("听音乐 - " + str); 7 } 8 9 @Override 10 public void watchVideo(String str) { 11 System.out.println("看视频 - " + str); 12 } 13 }
注解类: EnablePhoneExtendAspect 这里面定义了切面。这里须要先引入aspectjrt和aspectjweaver的jar包,Maven的配置代码会在后面贴出。
1 package com.spring09; 2 3 import org.aspectj.lang.annotation.Aspect; 4 import org.aspectj.lang.annotation.DeclareParents; 5 import org.springframework.stereotype.Component; 6 7 @Component 8 @Aspect 9 public class EnablePhoneExtendAspect { 10 @DeclareParents(value = "com.spring09.BndPhone", // 指定手机具体的实现 11 defaultImpl = BndPhoneExtend.class) // 手机扩展具体的实现 12 public PhoneExtend phoneExtend; // 要实现的目标接口 13 }
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
1 package com.spring09; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 6 public class Test { 7 public static void main(String[] args) { 8 ApplicationContext ctx = new ClassPathXmlApplicationContext("META-INF/applicationContext.xml"); 9 10 Phone phone = (Phone) ctx.getBean("phone"); 11 12 // 调用手机原有的方法 13 phone.call("BNDong"); 14 phone.sendMsg("BNDong"); 15 16 // 经过引介/引入切面已经将phone实现了PhoneExtend接口,因此能够强制转换 17 PhoneExtend phoneExtend = (PhoneExtend) phone; 18 phoneExtend.listenMusic("BNDong"); 19 phoneExtend.watchVideo("BNDong"); 20 } 21 }
执行 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>
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>
这时运行 Test.main() 发现切面配置成功:
打电话 - BNDong
发短信 - BNDong
听音乐 - BNDong
看视频 - BNDong
切面类型总结图:
《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