Spring AOP
文章目录
1. AOP概述
AOP(Aspect Oriented Programing) 翻译过来就是面向切面编程。
可以先来了解一下面向xx编程都是什么意思:
- POP (Producer Oriented Programing) 面向过程(方法、函数):以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建
- OOP (Object Oritened Programing) 面向对象编程 :以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建
那么AOP (Aspect Oriented Programing) 面向切面编程 = Spring动态代理开发以切⾯为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。
切面 = 切入点 + 额外功能
AOP的本质就是Spring得动态代理开发,通过代理类为原始类增加额外功能。
好处:利于原始类的维护
2. 切入点详解
切入点是组成切面的单位,简单来讲,切入点就是我们要去对其进行代理,增加额外功能的点。这个点可以是方法。
程序员需要在配置文件中进行切入点的配置,面向切面编程的过程大致为以下步骤:
- 创建原始类对象
- 实现相关接口
- 配置文件中定义切入点
切⼊点决定额外功能加⼊位置(方法)
<aop:pointcut id="pc" expression="execution(* *(..))"/>
exection(* *(..)) ---> 匹配了所有⽅法 a b c
1. execution() 切⼊点函数
2. * *(..) 切⼊点表达式
2.1 切入点表达式
2.1.1 方法切入点表达式
* *(..) --> 所有⽅法
* ---> 修饰符 返回值
* ---> ⽅法名
()---> 参数表
..---> 对于参数没有要求 (参数有没有,参数有⼏个都⾏,参数是什么类型的都⾏)
-
定义login方法作为切入点
* login(..)
-
定义login方法且login方法有两个字符串类型的参数 作为切入点
* login(String,String) # 注意:非java.lang包下的内容需要写全限定名,如: * register(com.jc.bean.User) # .. 可以与类型参数连用 * login(String,..) 可以匹配: login(String) login(String,String) login(String,com.jc.bean.User)
-
精准方法切入点限定
修饰符/返回值 包.类.⽅法(参数) * com.jc.service.UserServiceImpl.login(..) * com.jc.service.UserServiceImpl.login(String,String)
2.1.2 类切入点表达式
指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能。
-
语法1
# 类中的所有⽅法加⼊了额外功能 * com.jc.service.UserServiceImpl.*(..)
-
语法2
# 忽略包 1. 类只存在⼀级包 com.UserServiceImpl * *.UserServiceImpl.*(..) 2. 类存在多级包 com.jc.service.UserServiceImpl * *..UserServiceImpl.*(..)
2.1.3 包切入点表达式
指定包作为额外功能加入的位置,自然包中的所有类及其方法都会加入额外的功能
-
语法1
# 切入点包中的所有类,必须在service包中,不能在service子包中 * com.jc.service.*.*(..)
-
语法2
# 切入点包中的所有类,必须在service包中,但是可以在service包的子包中 * com.jc.servce..*.*(..)
2.2 切入点函数
切入点函数:用于执行切入点表达式
2.2.1 execution
最为重要的切⼊点函数,功能最全。
执⾏ ⽅法切⼊点表达式 类切⼊点表达式 包切⼊点表达式
弊端: execution执⾏切⼊点表达式 ,书写麻烦
execution(* com.baizhiedu.proxy..*.*(..))
注意:其他的切⼊点函数 简化是execution书写复杂度,功能上完全⼀致
2.2.2 args
作⽤:主要⽤于函数(⽅法) 参数的匹配
例如:
切⼊点:⽅法参数必须得是2个字符串类型的参数
execution(* *(String,String)) 等同于 args(String,String)
2.2.3 within
作⽤:主要⽤于进⾏类、包切⼊点表达式的匹配
切⼊点: UserServiceImpl这个类
execution(* *..UserServiceImpl.*(..))
within(*..UserServiceImpl)
execution(* com.baizhiedu.proxy..*.*(..))
within(com.baizhiedu.proxy..*)
2.2.4 @annotation
作用:为具有特殊注解的方法加入额外功能
<aop:pointcut id="pc" expression="@annotation(com.annotation.MyAnnotation)"/>
自定义注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
2.2.5 切入点函数的逻辑运算
指的是整合多个切入点函数⼀起配合工作,进而完成更为复杂的需求
-
and与操作
案例: login 同时 参数 2个字符串 1. execution(* login(String,String)) 2. execution(* login(..)) and args(String,String) 注意:与操作不同⽤于同种类型的切⼊点函数 案例: register⽅法 和 login⽅法作为切⼊点 execution(* login(..)) or execution(* register(..))
-
or或操作
案例: register⽅法 和 login⽅法作为切⼊点 execution(* login(..)) or execution(* register(..))
3. AOP编程的开发
通过Spring动态代理要实现额外功能,也就是我们要使用代理模式添加的功能。
Spring中主要实现方式有两种:
- 实现MethodBeforeAdvice接口
- 实现 MethodInterceptor接口
3.1 开发步骤
- 提供原始类对象
- 编写额外功能(实现相关接口)
- 配置切入点
- 组装切面 (额外功能 + 切入点)
3.2 MethodBeforeAdvice接口
额外的功能书写在接行的实现中,运行在原始方法执行之前运行额外功能。 方法名翻译过来叫做“方法前置通知”。
代码实现:
-
实现接口
import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class Before implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("---------proxy处理--------"); } }
<bean id="before" class="com.jc.proxy.Before"/>
-
定义切入点并整合
<aop:config> <!--定义切入点--> <aop:pointcut id="pc" expression="execution(* *(..))"/> <aop:advisor advice-ref="before" pointcut-ref="pc"></aop:advisor> </aop:config>
-
调用
@Test public void test1() { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // 此时根据id获取对象自动获取的是代理类对象 UserService userService = (UserService) ctx.getBean("userService"); userService.login("tom", "123456"); userService.register(new User("jerry", "123123")); }
3.3 MethodInterceptor(方法法拦截器) 接口
methodinterceptor接口:额外功能可以根据需要运行在原始方法执行前、后、前后。
这种方式更加泛用,实际使用的也是最多的。
代码实现:
-
实现接口
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; 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; } }
<bean id="around" class="com.jc.proxy.Around"/>
-
定义切入点并整合
<aop:config> <!--定义切入点--> <aop:pointcut id="pc" expression="execution(* *(..))"/> <aop:advisor advice-ref="around" pointcut-ref="pc"></aop:advisor> </aop:config>
-
调用
@Test public void test1() { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); // 此时根据id获取对象自动获取的是代理类对象 UserService userService = (UserService) ctx.getBean("userService"); userService.login("tom", "123456"); userService.register(new User("jerry", "123123")); }
4. Spring动态代理细节分析
-
Spring创建的动态代理类在哪里?
1. Spring框架在运⾏时,通过动态字节码技术,在JVM创建的,运⾏在JVM内部,等程序结束后,会和JVM⼀起消失 2. 什么叫动态字节码技术:通过第三个动态字节码框架,在JVM中创建对应类的字节码,进⽽创建对象,当虚拟机结束,动态字节码跟着消失。 3. 结论:动态代理不需要定义类⽂件,都是JVM运⾏过程中动态创建的,所以不会造成静态代理,类⽂件数量过多,影响项⽬管理的问题。
-
动态代理编程简化代理的开发
在额外功能不改变的前提下,创建其他⽬标类(原始类)的代理对象时,只需要指定原始(⽬标)对象即可。
-
动态代理额外功能的维护性大大增强
5. AOP底层实现原理
问题:
1. AOP如何创建动态代理类(动态字节码技术)
2. Spring⼯⼚如何加⼯创建代理对象
通过原始对象的id值,获得的是代理对象
5.1 Spring工厂如何加工原始对象
AOP底层实现实际上就是使用了动态代理。
那么我们在Spring中配置了原始类的信息,但是通过Spring工厂获取的确实代理类对象是怎么实现的呢?
Spring工厂创建一个对象的流程是这样的:
- 从配置文件中读取数据,代用类的构造器创建对象
- 注入属性
- 调用Bean初始化的前置方法(BeanPostProcessor接口中定义)
- 执行初始化方法(可以通过实现InitializingBean接口自定义)
- 调用Bean初始化的前置方法(BeanPostProcessor接口中定义)
- 返回创建的对象
5.1 自定义原始对象加工
-
编码
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyBeanPostProcessor implements BeanPostProcessor { /* 前置不做处理直接返回bean */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } /* 后置处理中创建动态代理对象,返回代理类对象 */ @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { InvocationHandler h = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("--- log begin ---"); Object ret = method.invoke(bean, args); System.out.println("--- log end ---"); return ret; } }; Object proxyInstance = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), h); return proxyInstance; } }
-
Spring配置文件:
<bean id="userService" class="com.jc.factory.impl.UserServiceImpl"/> <bean id="proxyBeanPostProcessor" class="com.jc.factory.ProxyBeanPostProcessor"/>