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 

 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 这里面定义了切面。这里需要先引入aspectjrtaspectjweaver的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 

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

posted @ 2018-10-23 15:03  BNDong  阅读(...)  评论(...编辑  收藏