【Spring AOP】Spring动态代理、切入点、AOP编程

参考自B站视频《孙哥说Spring5》

文章目录

  • 为什么要使用代理设计模式?
  • Spring 动态代理开发
  • 动态代理细节分析
  • 动态代理开发详解
    • MethodBeforeAdvice接口
    • MethodInterceptor 方法拦截器
  • 切入点详解
    • 方法切入点表达式
    • 类切入点表达式
    • 包切入点表达式
    • 切入点函数(execution、args、within)
    • 切入点函数的逻辑运算
  • AOP概述
  • AOP底层实现原理
    • JDK 动态代理
    • CGlib 动态代理
    • 小结
  • Spring 工厂如何加工原始对象
  • 基于注解的AOP开发步骤
  • 切入点复用
  • 切换动态代理的创建方式(JDK、CGlib)
  • AOP开发中的一个坑!
  • AOP 知识总结

为什么要使用代理设计模式?

关于代理模式,详情可参考 https://blog.csdn.net/Kobe_k/article/details/105771530

在 JavaEE 分层开发开发中,哪个层次对于我们来讲最重要

  • Service 层

Service 层中包含了哪些代码?

  • 核心功能(代码量较多):业务运算,DAO 调用
  • 额外功能(附加功能,不属于业务,可有可无,代码量小):事务、日志、性能 …

将额外功能写在 Service 层好不好?

  • Service 层的调用者的角度(Controller):需要在 Service 层书写额外功能。
  • 软件设计者:Service 层不需要额外功能。

如果在 Service层 代码中将核心功能和额外功能混合在一起写,会造成代码的混乱,维护起来不方便,因此可以通过代理模式,在代理类中整合额外功能和核心功能,有利于代码的维护。

举个例子:

静态代理:

/* 实体类 */
public class User {}

/* Service接口 */
public interface UserService {
	void register(User user);
	boolean login(String name, String password);
}

/* 实现类 */
public class UserServiceImpl implements UserService {
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 业务运算 + DAO");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login 业务运算 + DAO");
        return true;
    }
}

/**
 * 静态代理类
 */
public class UserServiceProxy implements UserService { // 实现原始类相同的接口
	// 代理类中必须有原始类,调用其核心功能
    private UserService userService = new UserServiceImpl(); 
    @Override
    public void register(User user) {
    	// 通过代理类,添加额外功能,例如日志输出、事务开启关闭提交等
        System.out.println("---log---"); // 额外功能
        userService.register(user);
    }
    @Override
    public boolean login(String name, String password) {
        System.out.println("---log---"); // 额外功能
        return userService.login(name, password);
    }
}

从上可以将代理模式的框架提取一下:

// 目标类
public interface UserService {
	// 方法
	m1
	m2
}
// 原始实现类
public UserServiceImpl implements UserServiceImpl {
	m1 ---> 业务运算、调用DAO
	m2 
}
----------------------------------------------------
// 代理类:要实现目标类相同的接口
public UserServiceProxy implements UserService {
	// 包含原始实现类
	private UserServiceImpl userServiceImpl;
	// 实现方法 方法中要调用到原始实现类,然后在此基础上再添加其他额外功能
	m1
	m2
}

如果一个类就有一个代理类和原始实现类,一旦类的数量多了起来,静态代理会造成类爆炸的问题,不利于项目管理;另一方面,如果额外功能较多,那么修改额外功能也比较麻烦

因此而引出了动态代理

Spring 动态代理开发

Spring的两大特色之一:AOP,即面向切面编程,就是类似于动态代理的思想;

先举个例子:

引入依赖


<dependency>
  <groupId>org.springframeworkgroupId>
  <artifactId>spring-aopartifactId>
  <version>5.1.14.RELEASEversion>
dependency>

<dependency>
  <groupId>org.aspectjgroupId>
  <artifactId>aspectjweaverartifactId>
  <version>1.8.13version>
dependency>

创建接口、原始对象

/* 接口 */
public interface UserService {
    void register(User user);
    boolean login(String name, String password);
}
/* 实现类 */
public class UserServiceImpl implements UserService {
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 业务运算 + DAO");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login 业务运算 + DAO");
        return true;
    }
}

创建额外功能对象,实现 MethodBeforeAdvice 接口

public class Before implements MethodBeforeAdvice {
    /**
     * 作用: 把需要运行在原始方法执行之前运行的额外功能, 书写在 before 方法中
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("---method before advice log---");
    }
}

配置文件配置

  • 切入点表达式详解可参考: https://zhuanlan.zhihu.com/p/81623354,本文后面也有关于切入段表达式的介绍

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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/aop
                           https://www.springframework.org/schema/aop/spring-aop.xsd">
	
	<bean id="userService" class="com.yusael.aop.UserServiceImpl"/>
    
    <bean id="before" class="com.yusael.aop.Before"/>

    
    
   
    <aop:config>
    	
        <aop:pointcut id="pc" expression="execution(* * (..))"/>
        
        
        <aop:advisor advice-ref="before" pointcut-ref="pc"/>
    aop:config>

beans>

调用测试

  • Spring 的工厂通过原始对象的 id 值获得的是代理对象
  • 获得代理对象后,可以通过声明接口类型,进行对象的存储
/**
 * 用于测试动态代理
 */
@Test
public void test1() {
	// 获得 Spring 工厂创建的动态代理对象
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    // 获得Bean
    UserService userService = (UserService) ctx.getBean("userService");
    userService.login("admin", "1234");
    userService.register(new User());
}

动态代理细节分析

Spring 创建的动态代理类在哪里

  • Spring 框架在运行时,通过动态字节码技术,在 JVM 创建的,运行在 JVM 内部,等程序结束后,会和 JVM 一起消失。

什么是 动态字节码技术?

  • 通过第三方动态字节码框架,在 JVM 中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码跟着消失。

结论:

  • 动态代理不需要定义类文件,都是 JVM 运行过程中动态创建的
  • 所以不会造成静态代理的缺点:类⽂件数量过多,影响项目管理的问题。

【Spring AOP】Spring动态代理、切入点、AOP编程_第1张图片

动态代理编程能简化代理的开发

  • 在额外功能不改变的前提下,创建其他目标类(原始类)的代理对象时,只需要指定原始(目标)对象即可。

总结:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成类文件数量过多,影响项目管理的问题,而且额外功能的维护性也大大增强,更换额外功能的类即可。

动态代理开发详解

MethodBeforeAdvice接口

作用: 额外功能运行在原始方法执行之前,进行额外功能操作。

public class Before implements MethodBeforeAdvice {
    /**
     * 作用: 把需要运行在原始方法执行之前运行的额外功能, 书写在 before 方法中
     *
     * Method: 额外功能所增加给的那个原始方法
     *                          login
     *                          register
     *                          --------
     *                          showOrder
     *
     * Object[]:  额外功能所增加给的那个原始方法的参数
     *                          在 login 方法中的参数为:String name,String password
     *                          在 register 方法中的参数为:User user
     *                          --------
     *
     * Object: 额外功能所增加给的那个原始对象
     *                          UserServiceImpl
     *                          ---------------
     *                          OrderServiceImpl
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("---new method before advice log---");
    }
}

before() 方法的 3 个参数在实战中,该如何使用?

  • before 方法的参数,在实战中,会根据需要进行使用,不⼀定都会用到,也有可能都不用
  • 孙哥:”我用了 15 年 Spring 一次都没有用到过这个。"

MethodInterceptor 方法拦截器

MethodInterceptor接口:额外功能可以根据需要运行在原始方法执行 前、后、前后

  • 参数:MethodInvocation:额外功能所增加给的那个原始方法 (login, register)
  • 返回值:Object:原始方法的返回值 (没有就返回 null)
  • invocation.proceed():原始方法运行

额外功能运行在原始方法 之前

public class Around implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("---额外功能运行在原始方法执行之前---");
        Object ret = methodInvocation.proceed(); // 原始方法运行, 获取原始方法的返回值
        return ret;
    }
}

额外功能运行在原始方法 之后

public class Around implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object ret = methodInvocation.proceed(); // 原始方法运行, 获取原始方法的返回值
        System.out.println("---额外功能运行在原始方法执行之后---");
        return ret;
    }
}

额外功能运行在原始方法 之前、之后

public class Around implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    	System.out.println("---额外功能运行在原始方法执行之前---");
        Object ret = methodInvocation.proceed(); // 原始方法运行, 获取原始方法的返回值
        System.out.println("---额外功能运行在原始方法执行之后---");
        return ret;
    }
}

额外功能运行在原始方法抛出异常的时候:

public class Around implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object ret = null;
        try {
            ret = methodInvocation.proceed(); // 原始方法运行, 获取原始方法的返回值
        } catch (Throwable throwable) {
            System.out.println("---额外功能运行在原始方法抛异常的时候---");
        }
        return ret;
    }
}

MethodInterceptor 影响原始方法的返回值:

public class Around implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("---log---");
        Object ret = methodInvocation.proceed();
        // 根据需求直接返回即可,也可以返回另外一个值,这里的返回值就是调用后的返回值
        return false;
    }
}

切入点详解

切入点:决定了额外功能的切入位置(方法)


<aop:pointcut id="pc" expression="execution(* * (..))"/>
  • execution():切入点函数
  • * *(..):切入点表达式

方法切入点表达式

定义一个方法的格式
public void add(int i, int j)
对应的表达式格式为
      *      *      (..)

* 即通配符,任意都行

每个位置对应的含义:
*  --->  修饰符 返回值
*  --->  方法名
() --->  参数表
.. --->  对于参数没有要求 (参数有没有,参数有⼏个都行,参数是什么类型的都行)

举个例子

定义 login 方法作为切入点:


<aop:pointcut id="pc" expression="execution(* login (..))"/>


<aop:pointcut id="pc" expression="execution(* register (..))"/>

定义方法名为 login 且 有两个字符串类型的参数 作为切入点;


<aop:pointcut id="pc" expression="execution(* login (String,String))"/><


<aop:pointcut id="pc" expression="execution(* register (com.yusael.proxy.User))"/>


<aop:pointcut id="pc" expression="execution(* login(String, ..))"/>

精准方法切入点限定

  • 格式:修饰符 返回值 包.类.方法(参数)
<aop:pointcut id="pc" expression="execution(* com.yusael.proxy.UserServiceImpl.login(..))"/>

<aop:pointcut id="pc" expression="execution(* com.yusael.proxy.UserServiceImpl.login(String, String))"/>

类切入点表达式

指定 特定类作为切入点(额外功能加入的位置),这个类中的所有方法,都会加上对应的额外功能。

# 类中所有的方法加入了额外功能
<aop:pointcut id="pc" expression="execution(* com.yusael.proxy.UserServiceImpl.*(..))"/>


# 忽略包
1. 类只存在一级包   '*.'->只能处理一层包
<aop:pointcut id="pc" expression="execution(* *.UserServiceImpl.*(..))"/>
2. 类存在多级包 需要用 '*..' 来处理多层包结构
<aop:pointcut id="pc" expression="execution(* *..UserServiceImpl.*(..))"/>

包切入点表达式

指定包作为额外功能加入的位置,自然包中的所有类及其方法都会加入额外的功能。

# 切入点包中的所有类,必须在proxy中,不能在proxy包的⼦包中
<aop:pointcut id="pc" expression="execution(* com.yusael.proxy.*.*(..))"/>

# 切入点当前包及其⼦包都生效
<aop:pointcut id="pc" expression="execution(* com.yusael.proxy..*.*(..))"/>

# 和类切入点类似,也是使用 '..' 来表示多层结构

切入点函数(execution、args、within)

切入点函数:用于执行切入点表达式

execution

execution最为重要的切入点函数,功能最全;可以执行 方法切入点表达式类切入点表达式包切入点表达式

弊端:execution 执⾏切入点表达式 ,书写麻烦

格式:execution(* com.yusael.proxy..*.*(..))

注意:其他的 切入点函数 简化的是 execution 的书写复杂度,功能上完全⼀致。

args

args 作用:主要用于 函数(方法) 参数的匹配;

切入点:方法参数必须得是 2 个字符串类型的参数

# 使用 execution
<aop:pointcut id="pc" expression="execution(* *(String, String))"/>

# 使用 args
<aop:pointcut id="pc" expression="args(String, String)"/>

within

within 作用:主要用于进行 类、包切入点表达式 的匹配

切入点: UserServiceImpl 这个类

# 使用 execution
<aop:pointcut id="pc" expression="expression(* *..UserServiceImpl.*(..))"/>

# 使用 within
<aop:pointcut id="pc" expression="within(*..UserServiceImpl)"/>

---------------------------------------------------------------------------

切入点: com.yusael.proxy 这个包

# 使用 execution
<aop:pointcut id="pc" expression="execution(* com.yusael.proxy..*.**(..)"/>

# 使用 within
<aop:pointcut id="pc" expression="within(com.yusael.proxy..*)"/>

@annotation

作用:为 具有特殊注解的方法 加入额外功能。

例如我们自定义了一个注解:Log

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}

然后我们要为使用了 Log 注解的方法加入额外功能。

<aop:pointcut id="pc" expression="@annotation(com.yusael.Log)"/>

切入点函数的逻辑运算

切入点函数的逻辑运算 指的是:整合多个切入点函数⼀起配合工作,进⽽完成更为复杂的需求

and 逻辑与

案例: 方法名叫 login 同时 参数是 2个字符串
# execution
<aop:pointcut id="pc" expression="execution(* login(String, String))"/>
# execution and args
<aop:pointcut id="pc" expression="execution(* login(..)) and args(String, String))"/>


注意:与操作不同⽤于同种类型的切⼊点函数
以下这个是错误的, 因为不存在同时叫 login 和 register 的方法
<aop:pointcut id="pc" expression="execution(* login(..)) and execution(* register(..))"/>

or 逻辑或

案例: 方法名叫 register 或 login 的⽅法作为切⼊点 如果用and就不存在了
<aop:pointcut id="pc" expression="execution(* login(..)) or execution(* register(..))"/>

AOP概述

POP (Producer Oriented Programing

  • 面向**过程(方法、函数)**编程 —— C
  • 以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建。

OOP (Object Oritened Programing)

  • 面向对象编程 —— Java
  • 以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建。

AOP (Aspect Oriented Programing)

  • 面向切面编程 = Spring动态代理开发 (不同的称呼,但指代的都是一个东西)
  • 以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。
  • 切面 = 切入点 + 额外功能

AOP 的概念:

  • 本质就是 Spring 的动态代理开发,通过代理类为原始类增加额外功能。
  • 好处:利于原始类的维护
  • 注意:AOP 编程不可能取代 OOP,AOP 是 OOP 编程的补充

那为什么称为切面呢?

  • 切面 = 切入点 + 额外功能
  • 在几何学中:面 = 点 + 相同的性质

AOP底层实现原理

首先要明确两个核心问题:

  • AOP 如何创建动态代理类?
    • 动态字节码技术
  • Spring 工厂如何加工创建代理对象
    • 即:为什么通过原始对象的 id 值,获得的是代理对象呢?

Spring 动态代理类的创建有两种方式:JDK动态代理创建、CGlib动态代理创建

JDK 动态代理

先上一段代码:

public class TestJDKProxy {
    /**
     1. 借⽤类加载器  TestJDKProxy 或 UserServiceImpl 都可以
     2. JDK8.x 前必须加 final
     final UserService userService = new UserServiceImpl();
     */
    public static void main(String[] args) {
        // 1. 创建原始对象
        UserService userService = new UserServiceImpl();

        // 2. JDK 动态代理
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            	// 加入额外功能,例如日志记录
                System.out.println("---- proxy log ----");
                // 原始方法运行
                Object ret = method.invoke(userService, args);
                return ret;
            }
        };
        // 创建代理类
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
        						TestJDKProxy.class.getClassLoader(),
                                userService.getClass().getInterfaces(),
                                handler);
        // 代理类执行方法
        userServiceProxy.login("zhenyu", "123456");

        userServiceProxy.register(new User());
    }
}

来画个图分析一波:
【Spring AOP】Spring动态代理、切入点、AOP编程_第2张图片

那么也许有人会问了,那第一个参数中的类加载器有什么作用呢?

还记得之前提到的动态字节码技术吗?其实这个类加载器就是利用了这个技术,动态地创建类的字节码加载到JVM中。

另外,类的加载是需要类加载器的,但是动态代理类是动态创建的,其动态字节码是没有真正的字节码文件的,因此JVM也就无法为其指定一个类加载器,但是类加载器又是必需的,所以可以通过借用类加载器来加载动态代理类。

【Spring AOP】Spring动态代理、切入点、AOP编程_第3张图片

CGlib 动态代理

CGlib 创建动态代理的原理

  • 通过父子继承关系创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证两者方法⼀致,同时在代理类中可以提供新的实现(额外功能+原始方法)。、

【Spring AOP】Spring动态代理、切入点、AOP编程_第4张图片

上代码:

public class TestCglib {
    public static void main(String[] args) {
        // 1. 创建原始对象
        UserService userService = new UserService();

        /*
         2. 通过 cglib 方式创建动态代理对象
         对比 jdk 动态代理 ---> Proxy.newProxyInstance(classLoader, interface, invocationHandler);

         Enhancer.setClassLoader()   <==> classLoader
         Enhancer.setSuperClass()   <==>  interface
         Enhancer.setCallBack()  ---> 实现 MethodInterceptor(cglib) 接口   <==>  invocationHandler
         Enhancer.createProxy() ---> 创建代理对象
         */
        Enhancer enhancer = new Enhancer();

        enhancer.setClassLoader(TestCglib.class.getClassLoader());
        enhancer.setSuperclass(userService.getClass());

        MethodInterceptor interceptor = new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("--- cglib log ----");
                Object ret = method.invoke(userService, args); // 执行原始方法
                return ret;
            }
        };

        enhancer.setCallback(interceptor);
        UserService userServiceProxy = (UserService) enhancer.create();
        userServiceProxy.login("zhenyu", "123456");
        userServiceProxy.register(new User());
    }
}

小结

  1. JDK 动态代理
    Proxy.newProxyInstance:通过接口创建代理的实现类
  2. Cglib 动态代理
    Enhancer:通过继承⽗类创建的代理类

Spring 工厂如何加工原始对象

  • 思路分析:主要通过 BeanPostProcessor 将原始对象加工为代理对象

  • 有两个回调方法,一个在调用初始化方法之前被调用;一个在调用初始化方法之后被调用

【Spring AOP】Spring动态代理、切入点、AOP编程_第5张图片

开发流程

  1. 实现 BeanPostProcessor 进行加工
  2. 配置文件中对 BeanPostProcessor 进行配置
// 实现 BeanPostProcessor 进行加工
public class ProxyBeanPostProcessor implements BeanPostProcessor {
    @Override
    // bean初始化方法调用前被调用
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    // bean初始化方法调用后被调用
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    	// JDK动态代理创建代理类
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 额外功能
                System.out.println("--- new log ---");
                // 原始功能 传入参数
                Object ret = method.invoke(bean, args);
                // 返回
                return ret;
            }
        };
        // 生成代理类
        return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);
    }
}

<bean id="userService" class="com.yusael.factory.UserServiceImpl"/>

<bean id="proxyBeanPostProcessor" class="com.yusael.factory.ProxyBeanPostProcessor"/>
  • 所以当我们通过原始对象的id值获取Bean的时候,由于BeanPostProcessor的加工,所以我们获得到的是代理类。
  • 也回应了文章开头提出的第二个核心问题。

基于注解的AOP开发步骤

不管是不是使用注解开发,其步骤都是一样的:

  1. 原始功能
  2. 额外功能
  3. 切入点
  4. 组装切面

举个例子

原始功能

// 接口
public interface UserService {
    void register(User user);
    boolean login(String name, String password);
}
// 原始实现类
public class UserServiceImpl implements UserService {
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 业务运算 + DAO");
        // throw new RuntimeException("测试异常");
    }

    @Log
    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login 业务运算 + DAO");
        return true;
    }
}

组装切面类(将剩余步骤简化,合为一体,将之看成一个对象)

/*
	原来的开发步骤
    1. 实现额外功能
        public class MyAround implements MethodInterceptor {
            public Object invoke(MethodInvocation invocation) {
                Object ret = invocation.invoke();
                return ret;
            }
        }
        

    2. 配置文件中组装切面
        
        	切入点
            
            组装
            
        
 */

/*  现在可以简化上面的步骤,通过注解  */
@Aspect  // 切面类
public class MyAspect {

    @Around("execution(* login(..))") // 切入点
    
    // 额外功能
    // ProceedingJoinPoint  <==>  MethodInterceptor 两者的作用是一样的
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---- aspect log ----");
        Object ret = joinPoint.proceed();
        return ret;
    }
}

配置文件中配置

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
	
    <bean id="userService" class="com.yusael.aspect.UserServiceImpl"/>
    
    
    <bean id="around" class="com.yusael.aspect.MyAspect"/>
    
    
    <aop:aspectj-autoproxy/>

beans>

切入点复用

当切入点表达式是一样的时候,如果有多个额外功能,那么在注解中需要写多次表达式,这会让代码维护起来不方便,可以通过切入点复用的方式来统一切入点;

  • 切入点复用:在切面类中定义⼀个函数,上面用 @Pointcut 注解。

  • 通过这种方式定义切入点表达式,后续更加有利于切入点复用

@Aspect
public class MyAspect {

    @Pointcut("execution(* login(..))") // 切入点复用
    public void myPoincut() {}

    @Around(value = "myPoincut()") // 直接引用上面的切入点
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---- aspect log ----");
        Object ret = joinPoint.proceed();
        return ret;
    }
    @Around(value = "myPoincut()") // 直接引用上面的切入点
    public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("---- aspect transaction ----");
        Object ret = joinPoint.proceed();
        return ret;
    }
}

切换动态代理的创建方式(JDK、CGlib)

AOP 底层实现 2 种代理创建方式:

  • JDK:通过 实现接口,做新的实现类 创建代理对象
  • Cglib:通过 继承父类,做新的子类 创建代理对象

默认情况 AOP 编程 底层应用 JDK动态代理 创建方式。

那如何切换两种动态代理的创建方式呢?

  • 基于注解的 AOP 开发 中切换为 CGlib:
<aop:aspectj-autoproxy proxy-target-class="true"/>
  • 传统的 AOP 开发 中切换为 CGlib:
<aop:config proxy-target-class="true">
	
aop:config>

AOP开发中的一个坑!

在同⼀个业务类中,进⾏业务方法间的相互调用,只有最外层的方法,才是加入了额外功能

  • 内部的方法,通过普通的方式调用都调用的是原始方法
  • 其实这个并不是Spring的BUG,这个是我们在代码调用过程中直接调用的原始方法,而不是通过Spring调用方法,那肯定就是直接调用原始方法,而不是代理方法。
  • 如果想让内层的方法也调用代理对象的方法,就要实现 AppicationContextAware接口 获得⼯厂,进而获得代理对象。
// 实现 ApplicationContextAware 接口 Spring会自动调用 setApplicationContext 方法将工厂注入
public class UserServiceImpl implements UserService, ApplicationContextAware {
	// 工厂是重量级资源,不应该多次创建工厂,而是拿到已经创建好了的工厂
    private ApplicationContext ctx;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ctx = applicationContext;
    }
    
    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 业务运算 + DAO");

        // this.login("zhenyu", "123456"); // 这么写调用的是本类的 login 方法, 即原始对象的 login 方法
        
        // 为什么不在这里创建一个工厂获取代理对象呢?
        // Spring的工厂是重量级资源, 一个应用中应该只创建一个工厂.
        // 因此我们必须通过 ApplicationContextAware 拿到已经创建好的工厂
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("yusael", "123456");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login 业务运算 + DAO");
        return true;
    }

}

测试类

public static void main(String[] args){
	ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
	UserService userService = (UserService) cts.getBean("userService");
	userService.register(new User());
}

AOP 知识总结

【Spring AOP】Spring动态代理、切入点、AOP编程_第6张图片

你可能感兴趣的:(深入理解Spring)