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>
当引入接口方法被调用时,代理对象会把此调用委托给实现了新接口的某个其他对象。实际上,一个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

Spring:AOP面向切面编程的更多相关文章

  1. 详细解读 Spring AOP 面向切面编程(二)

    本文是<详细解读 Spring AOP 面向切面编程(一)>的续集. 在上篇中,我们从写死代码,到使用代理:从编程式 Spring AOP 到声明式 Spring AOP.一切都朝着简单实 ...

  2. 浅谈Spring AOP 面向切面编程 最通俗易懂的画图理解AOP、AOP通知执行顺序~

    简介 我们都知道,Spring 框架作为后端主流框架之一,最有特点的三部分就是IOC控制反转.依赖注入.以及AOP切面.当然AOP作为一个Spring 的重要组成模块,当然IOC是不依赖于Spring ...

  3. spring AOP面向切面编程学习笔记

    一.面向切面编程简介: 在调用某些类的方法时,要在方法执行前或后进行预处理或后处理:预处理或后处理的操作被封装在另一个类中.如图中,UserService类在执行addUser()或updateUse ...

  4. 【Spring系列】Spring AOP面向切面编程

    前言 接上一篇文章,在上午中使用了切面做防重复控制,本文着重介绍切面AOP. 在开发中,有一些功能行为是通用的,比如.日志管理.安全和事务,它们有一个共同点就是分布于应用中的多处,这种功能被称为横切关 ...

  5. 从源码入手,一文带你读懂Spring AOP面向切面编程

    之前<零基础带你看Spring源码--IOC控制反转>详细讲了Spring容器的初始化和加载的原理,后面<你真的完全了解Java动态代理吗?看这篇就够了>介绍了下JDK的动态代 ...

  6. Spring AOP面向切面编程详解

    前言 AOP即面向切面编程,是一种编程思想,OOP的延续.在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等等.在阅读本文前希望您已经对Spring有一定的了解 注:在能对代码进行添 ...

  7. Spring AOP 面向切面编程相关注解

    Aspect Oriented Programming 面向切面编程   在Spring中使用这些面向切面相关的注解可以结合使用aspectJ,aspectJ是专门搞动态代理技术的,所以比较专业.   ...

  8. Spring AOP 面向切面编程入门

    什么是AOP AOP(Aspect Oriented Programming),即面向切面编程.众所周知,OOP(面向对象编程)通过的是继承.封装和多态等概念来建立一种对象层次结构,用于模拟公共行为的 ...

  9. 详细解读 Spring AOP 面向切面编程(一)

    又是一个周末, 今天我要和大家分享的是 AOP(Aspect-Oriented Programming)这个东西,名字与 OOP 仅差一个字母,其实它是对 OOP 编程方式的一种补充,并非是取而代之. ...

  10. Spring Aop面向切面编程&&自动注入

    1.面向切面编程 在程序原有纵向执行流程中,针对某一个或某一些方法添加通知,形成横切面的过程叫做面向切面编程 2.常用概念 原有功能:切点,pointcut 前置通知:在切点之前执行的功能,befor ...

随机推荐

  1. python 中 *args 和 **kwargs 的区别

    在 python 中,*args 和 **kwargs 都代表 1个 或 多个 参数的意思.*args 传入tuple 类型的无名参数,而 **kwargs 传入的参数是 dict 类型.下文举例说明 ...

  2. Spring Boot(六)集成 MyBatis 操作 MySQL 8

    一.简介 1.1 MyBatis介绍 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC代码和手动设置参数以及获取结果集. ...

  3. 痞子衡嵌入式:语音处理工具Jays-PySPEECH诞生记(3)- 音频显示实现(Matplotlib, NumPy1.15.0)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是语音处理工具Jays-PySPEECH诞生之音频显示实现. 音频显示是Jays-PySPEECH的主要功能,Jays-PySPEECH借 ...

  4. JavaScript小实例:拖拽应用(二)

    经常在网站别人的网站的注册页中看到一个拖拽验证的效果,就是它的验证码刚开始不出来,而是有一个拖拽的条,你必须将这个拖拽条拖到底,验证码才出来,说了感觉跟没说一样,你还是不理解,好吧,我给个图你看看: ...

  5. UED视觉交互设计与流程介绍

    UED视觉交互设计与流程介绍 ------------------------------------------------------------------ 今天先到这儿,希望对您技术领导力, ...

  6. 查看Windows电脑上.NET Framework版本的方法(找了好久的方法)

    照网上大多数人的方法,在路径 C:\Windows\Microsoft.NET\Framework 下可以查看到.NET Framework的版本,不过无论Win7还是Win10,显示都是这样的: 那 ...

  7. 学JAVA第七天,循环深入了解

    因为星期五放假,所以今天补回. 上次已经解释过循环了,现在我们来进一步了解. 例如for循环:for( int i=0 : i<10 : i++ ){需要循环的内容},这样就会循环10次了 如果 ...

  8. webpack打包vue

    一.原理 webpack 背后的原理其实就是把所有的非 js 资源都转换成 js (如把一个 css 文件转换成“创建一个 style 标签并把它插入 document ”的脚本.把图片转换成一个图片 ...

  9. Windows中通过命令行新建文件夹、新建文件,和一些常用命令

    新建文件 和Linux不太一样,Linux中的touch和vi命令创建新文件的方法都不能用了,在windows命令行下得用type nul>文件名.后缀名来创建: F:\study\vue\wo ...

  10. USGS-EROS项目espa-surface-reflectance中的Landsat8 大气校正LaSRC Version 1.3.0模块利用vs2010编译出windows64位版本(四)

    ,支持一些关键问题: 1    数据初始化问题.该问题是指在linux环境下编程标准c并编译,用户定义的变量默认初始值是0,但在windows 64 win7环境中,变量默认初始值是负值极小.... ...