Spring:AOP面向切面编程

本文内容纲要:

- AOP概述

- AOP术语

- 连接点(Joinpoint)

- 切点(Poincut)

- 增强/通知(Advice)

- 目标对象(Target)

- 织入(Weaving)

- 引介/引入(Introduction)

- 代理(Proxy)

- 切面(Aspect)

- Spring对AOP的支持

- Spring AOP原理

- 代码实例

- 带有横切逻辑的实例

- ForumService.java

- ForumServiceImpl.java

- MethodPerformance.java

- PerformanceMonitor.java

- TestForumService.java

- JDK动态代理

- PerformanceHandler.java

- CGLib动态代理

- CglibProxy.java

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

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

- 在XML中声明切面

- 总结

- 参考资料

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进行横切重新审视:

Image

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

Image

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

Image

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的动态代理。

Image

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

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

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

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

原因:

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

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

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

Image

代码实例

带有横切逻辑的实例

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 中。当其他业务类的业务方法需要性能监视时候,只需要为他们创建代理类就行了。

Image

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

Image

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

Image

使用引介/引入功能实现为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:

ImageImage

<?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的实现被拆分到多个类中。

Image

在XML中声明切面

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

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

Image

同样使用上面手机的实例代码,我们去掉注解类通过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

切面类型总结图:

Image

总结

  • 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

本文内容总结:AOP概述,AOP术语,连接点(Joinpoint),切点(Poincut),增强/通知(Advice),目标对象(Target),织入(Weaving),引介/引入(Introduction),代理(Proxy),切面(Aspect),Spring对AOP的支持,Spring AOP原理,代码实例,带有横切逻辑的实例,ForumService.java,ForumServiceImpl.java,MethodPerformance.java,PerformanceMonitor.java,TestForumService.java,JDK动态代理,PerformanceHandler.java,CGLib动态代理,CglibProxy.java,基于注解和命名空的AOP编程,使用引介/引入功能实现为Bean引入新方法,在XML中声明切面,总结,参考资料,

原文链接:https://www.cnblogs.com/bndong/p/9809656.html

以上是 Spring:AOP面向切面编程 的全部内容, 来源链接: utcz.com/z/296807.html

回到顶部