【Spring】 AOP Base
一、AOP概述
AOP(Aspect Oriented Programing
),面向切面编程,AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码
AspecJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入
二、AOP的术语:
- 连接点(Joinpoint):所谓连接点是指那些被拦截到的点。在
Spring
中,这些点指的是方法,因为Spring
只支持方法类型的连接点. - 切入点(Pointcut):所谓切入点是指我们要对哪些
Joinpoint
进行拦截的定义. - 通知/增强(Advice):所谓通知是指拦截到
Joinpoint
之后所要做的事情就是通知.- 前置通知(Before advice):在目标方法被调用之前调用通知方法
- 后置通知(After/finally advice):在目标方法完成之后 调用通知方法,此时不会关心方法的输出是什么
- 返回通知(After returning advice):在目标方法成功执行之后调用通知
- 异常通知(After throwing advice):在目标方法抛出异常之后调用通知
- 环绕通知(Around advice):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
- 引介/引入(Introduction):引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field
- 目标对象(Target):代理的目标对象
- 织入(Weaving):是指把增强应用到目标对象来创建新的代理对象的过程.
Spring
采用动态代理织入,而AspectJ
采用编译期织入和类装在期织入 - 代理(Proxy):一个类被
AOP
织入增强后,就产生一个结果代理类 切面(Aspect):是切入点和通知(引介)的结合
三、AOP底层原理
Spring
的AOP
实现机制主要有两种:
JDK
动态代理:对实现了接口的类生成代理CGLib
代理机制:对类生成代理
详见代理模式
四、Spring 中的AOP
1. 概述
2. 分类
- 基于代理的经典Spring AOP(传统AOP)
- 纯POJO切面;
- @AspectJ注解驱动的切面;
- 注入式AspectJ切面(适用于Spring各版本)。
3. Spring的传统AOP
AOP:不是由Spring定义.AOP联盟的组织定义.
Spring中的通知:(增强代码)
- 前置通知:在目标方法执行前实施增强,要实现的接口
org.springframework.aop.MethodBeforeAdvice
- 后置通知:在目标方法执行后实施增强,要实现的接口,
org.springframework.aop.AfterReturningAdvice
- 环绕通知:在目标方法执行前后实施增强,要实现的接口,
org.aopalliance.intercept.MethodInterceptor
- 异常抛出通知:在方法抛出异常后实施增强,要实现的接口,
org.springframework.aop.ThrowsAdvice
- 引介通知:在目标类中添加一些新的方法和属性,要实现的接口,
org.springframework.aop.IntroductionInterceptor
针对所有方法的增强:(不带有切点的切面)
需要引入的Maven坐标:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
<!-- 这里要注释掉,因为设置了scope为test是,测试类只能卸载 Maven中的测试包下 -->
<!-- <scope>test</scope> -->
</dependency>
编写被代理对象
接口:
public interface ICustomerDao {
public void add();
public void update();
public void delete();
public void find();
}
实现类:
public class CustomerDaoImpl implements ICustomerDao {
@Override
public void add() {
System.out.println("添加客户");
}
@Override
public void update() {
System.out.println("更新客户");
}
@Override
public void delete() {
System.out.println("删除客户");
}
@Override
public void find() {
System.out.println("查询客户");
}
}
编写增强的代码
前置增强:
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
// TODO Auto-generated method stub
System.out.println("前置增强。。。。。");
}
}
生成代理:(在XML中配置生成代理)
<!-- 不带有切点的切面 -->
<!-- 1.定义目标对象 -->
<bean id="customerDao"
class="com.hao.aop.tradition.demo1.CustomerDaoImpl"></bean>
<!-- 2.定义增强 -->
<bean id="beforeAdvice"
class="com.hao.aop.tradition.demo1.MyBeforeAdvice"></bean>
<!-- 3.Spring支持配置生成代理: -->
<bean id="customerDaoProxy"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 设置目标对象 -->
<property name="target" ref="customerDao" />
<!-- 设置代理要实现的接口 -->
<property name="proxyInterfaces"
value="com.hao.aop.tradition.demo1.ICustomerDao"></property>
<!-- 设置需要织入目标的Advice -->
<property name="interceptorNames" value="beforeAdvice" />
</bean>
在XML配置说明:
- 首先要将前面定义的目标对象和增强 注册到Spring 容器中,
然后是代理类的配置(主要是这里的配置):
通过类org.springframework.aop.framework.ProxyFactoryBean
来设置代理对象,对于这个类,以后有时间要看一下,下面先看一下它在XML中需要配置的属性:- target : 代理的目标对象
proxyInterfaces : 代理要实现的接口,如果多个接口可以使用以下格式赋值:
<list> <value></value>
....
</list>proxyTargetClass:是否对类代理而不是接口,设置为true时,使用CGLib代理
interceptorNames : 需要织入目标的Advice
singleton : 返回代理是否为单实例,默认为单例
optimize : 当设置为true时,强制使用CGLib
测试
这里要注意的是,要注入代理对象!!!!
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AOPTest1 {
@Qualifier("customerDaoProxy")
@Autowired
private ICustomerDao customerDao;
@Test
public void test() {
customerDao.add();
customerDao.find();
customerDao.update();
customerDao.delete();
}
}
带有切点的切面:(针对目标对象的某些方法进行增强)
创建目标对象:
这里没有实现接口,这里试下用CGLIB代理的方式
public class OrderDao {
public void add() {
System.out.println("添加订单");
}
public void delete() {
System.out.println("删除订单");
}
public void find() {
System.out.println("查询订单");
}
}
编写增强的类:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAroundAdvice implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("执行方法之前增强.....");
Object result = invocation.proceed();
System.out.println("指向方法之后增强.....");
return result;
}
}
生成代理(配置生成代理):
<!-- 定义带有切点的切面 -->
<!-- 定义目标对象 -->
<bean id="orderDao" class="com.hao.aop.tradition.demo2.OrderDao"></bean>
<!-- 定义增强 -->
<bean id="aroundAdvice"
class="com.hao.aop.tradition.demo2.MyAroundAdvice"></bean>
<!-- 定义切面 -->
<bean id="myPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 定义表达式,规定哪些方法执行拦截 -->
<!-- . 任意字符 * 任意个 -->
<property name="patterns" value=".*add.*,.*find.*"></property>
<!-- 应用增强 -->
<property name="advice" ref="aroundAdvice"/>
</bean>
<!-- 定义生成代理对象 -->
<bean id="orderDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置目标 -->
<property name="target" ref="orderDao"></property>
<!-- 针对类的代理 -->
<property name="proxyTargetClass" value="true"></property>
<!-- 在目标上应用增强 -->
<property name="interceptorNames" value="myPointcutAdvisor"></property>
</bean>
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AOPTest2 {
@Qualifier("orderDaoProxy") //此处注入的是代理对象
@Autowired
private OrderDao orderDao;
@Test
public void test() {
orderDao.add();
orderDao.delete();
orderDao.find();
}
}
4. 自动代理
前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大
自动创建代理(基于后处理Bean,在Bean创建的过程中完成的增强,生成Bean就是代理。前面利用ProxyFactoryBean
代理的方式需要先生成代理对象才可以实现代理功能,而这种方式在Bean的创建过程中就可以完成代码的增强)
- BeanNameAutoProxyCreator:根据Bean名称创建代理
- DefaultAdvisorAutoProxyCreator:根据Advisor本身包含信息创建代理
- AnnotationAwareAspectJAutoProxyCreator:基于Bean中的AspectJ 注解进行自动代理
BeanNameAutoProxyCreator
目标对象和增强,应用前面中的例子,配置示例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 自动代理 -->
<!-- 目标对象 -->
<bean id="customerDao" class="com.hao.aop.tradition.demo1.CustomerDaoImpl"></bean>
<bean id="orderDao" class="com.hao.aop.tradition.demo2.OrderDao"></bean>
<!-- 定义增强-->
<bean id="beforeAdvice" class="com.hao.aop.tradition.demo1.MyBeforeAdvice"></bean>
<bean id="aroundAdvice" class="com.hao.aop.tradition.demo2.MyAroundAdvice"></bean>
<!-- 自动代理:按名称的代理 基于后处理Bean,后处理Bean不需要配置ID-->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Dao"/>
<property name="interceptorNames" value="aroundAdvice"/>
</bean>
</beans>
测试代码:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.hao.aop.tradition.demo1.ICustomerDao;
import com.hao.aop.tradition.demo2.OrderDao;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class AOPTest3 {
@Qualifier("customerDao")
@Autowired
private ICustomerDao customerDao;
@Qualifier("orderDao")
@Autowired
private OrderDao orderDao;
@Test
public void test() {
orderDao.add();
orderDao.find();
customerDao.add();
customerDao.delete();
}
}
DefaultAdvisorAutoProxyCreator
根据切面本身包含信息创建代理
配置示例:
<!-- 目标对象 -->
<bean id="customerDao" class="com.hao.aop.tradition.demo1.CustomerDaoImpl"></bean>
<bean id="orderDao" class="com.hao.aop.tradition.demo2.OrderDao"></bean>
<!-- 定义增强-->
<bean id="beforeAdvice" class="com.hao.aop.tradition.demo1.MyBeforeAdvice"></bean>
<bean id="aroundAdvice" class="com.hao.aop.tradition.demo2.MyAroundAdvice"></bean>
<!-- 定义一个带有切点的切面 -->
<bean id="myPointcutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value=".*add.*"/>
<property name="advice" ref="aroundAdvice"/>
</bean>
<!-- 自动生成代理 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
测试代码:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.hao.aop.tradition.demo1.ICustomerDao;
import com.hao.aop.tradition.demo2.OrderDao;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext3.xml")
public class AOPTest4 {
@Autowired
@Qualifier("customerDao")
private ICustomerDao customerDao;
@Autowired
@Qualifier("orderDao")
private OrderDao orderDao;
@Test
public void test() {
customerDao.add();
customerDao.update();
orderDao.add();
orderDao.delete();
}
}
区别:基于ProxyFattoryBean的代理与自动代理
- ProxyFactoryBean:先有被代理对象,将被代理对象传入到代理类中生成代理.
- 自动代理基于后处理Bean.在Bean的生成过程中,就产生了代理对象,把代理对象返回.生成Bean已经是代理对象.
5. AspectJ
概述
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
AspectJ是一个基于Java语言的AOP框架
Spring2.0以后新增了对AspectJ切点表达式支持
@AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面
新版本Spring框架,建议使用AspectJ方式来开发AOP
AspectJ表达式
在Spring AOP中,要使用AspectJ的切点表达式语言来定义切点。关于Spring AOP的AspectJ切点,最重要的一点就是Spring仅支持AspectJ切点指示器(pointcut designator
)的一个子集。Spring是基于代理的,而某些切点表达式是与基于代理的AOP无关的。Spring AOP所支持的AspectJ切点指示器如下:
在Spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgument-Exception异常。当我们查看如上所展示的这些Spring支持的指示器时,注意只有execution指示器是实际执行匹配的,而其他的指示器都是用来限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器。在此基础上,我们使用其他指示器来限制所匹配的切点。
execution 表达式语法:
execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
我们使用execution()
指示器选择Performance
的perform()
方法。方法表达式以“*”
号开始,表明了我们不关心方法返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,我们使用两个点号(..)
表明切点要选择任意的perform()
方法,无论该方法的入参是什么。
请注意我们使用了“&&”
操作符把execution()
和within()
指示器连接在一起形成与(and
)关系(切点必须匹配所有的指示器)。类似地,我们可以使用“||”
操作符来标识或(or
)关系,而使用“!”
操作符来标识非(not)
操作。
因为“&”
在XML中有特殊含义,所以在Spring的XML配置里面描述切点时,我们可以使用and
来代替“&&”
。同样,or
和not
可以分别用来代替“||”
和“!”
。
AspectJ增强:
- @Before 前置通知,相当于BeforeAdvice
- @AfterReturning 后置通知,相当于AfterReturningAdvice
- @Around 环绕通知,相当于MethodInterceptor
- @AfterThrowing抛出通知,相当于ThrowAdvice
- @After 最终final通知,不管是否异常,该通知都会执行
- @DeclareParents 引介通知,相当于IntroductionInterceptor (了解)
开发步骤
开发之前需要引入依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
基于XML
第一步:编写被增强的类:
public class ProductDao {
public int add(){
System.out.println("添加商品...");
int d = 10/0;
return 100;
}
public void update(){
System.out.println("修改商品...");
}
public void delete(){
System.out.println("删除商品...");
}
public void find(){
System.out.println("查询商品...");
}
}
第二步:定义切面
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 切面类
* @author hao
*
*/
public class MyAspectXML {
public void before(){
System.out.println("前置通知...");
}
public void afterReturing(Object returnVal){
System.out.println("后置通知...返回值:"+returnVal);
}
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕前增强....");
Object result = proceedingJoinPoint.proceed();
System.out.println("环绕后增强....");
return result;
}
public void afterThrowing(Throwable e){
System.out.println("异常通知..."+e.getMessage());
}
public void after(){
System.out.println("最终通知....");
}
}
第三步:配置applicationContext.xml
<!-- 定义目标对象 -->
<bean id="productDao" class="com.hao.aop.aspectj.xml.ProductDao"></bean>
<!-- 定义增强 -->
<bean id="myAspectXML" class="com.hao.aop.aspectj.xml.MyAspectXML"></bean>
<aop:config>
<!-- 定义切点: -->
<aop:pointcut id="mypointcut" expression="execution(* com.hao.aop.aspectj.xml.ProductDao.add(..))" />
<aop:aspect ref="myAspectXML">
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="mypointcut"/>
<!-- 后置通知 -->
<aop:after-returning method="afterReturing" pointcut-ref="mypointcut" returning="returnVal"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="mypointcut"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="mypointcut" throwing="e"/>
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="mypointcut"/>
</aop:aspect>
</aop:config>
基于注解
第一步:导入aop模块;Spring AOP:(spring-aspects)
第二部:定义一个业务逻辑类:在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)
public class UserDao {
public void add(){
System.out.println("添加用户");
}
public int update(){
System.out.println("修改用户");
return 1;
}
public void delete(){
System.out.println("删除用户");
}
public void find(){
System.out.println("查询用户");
//int d = 1/ 0;
}
}
第三步:定义一个切面类:切面类里面的方法需要动态感知业务类的方法运行到哪里然后执行;
- 给切面类的目标方法标注何时何地运行(通知注解);
- 必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
/**
* 切面类:就是切点与增强结合
*
* @author hao
*
*/
@Aspect
public class MyAspect {
// 定义一个切点
@Pointcut("execution(* com.hao.aop.aspectj.anno.UserDao.find(..))")
private void myPointcut() {} // 此方法没有实际的意义,就是为了提供给注解一个修饰的地方
@Before("execution(* com.hao.aop.aspectj.anno.UserDao.add(..))")
public void before(JoinPoint joinPoint) {
System.out.println("前置增强...." + joinPoint);
}
@AfterReturning(value = "execution(* com.hao.aop.aspectj.anno.UserDao.update(..))", returning = "returnVal")
public void afterReturin(Object returnVal) {
System.out.println("返回增强....方法的返回值:" + returnVal);
}
@Around(value = "MyAspect.myPointcut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕前增强....");
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕后增强....");
return obj;
}
@AfterThrowing(value = "MyAspect.myPointcut()", throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("不好了 出异常了!!!" + e.getMessage());
}
@After("MyAspect.myPointcut()")
public void after() {
System.out.println("最终/后置通知...");
}
}
AspectJ的通知类型:
- 前置通知(@Before):logStart:在目标方法(div)运行之前运行
- 后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
- 返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
- 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
- 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
- 引介通知(@DeclareParents) ,相当于IntroductionInterceptor (不要求掌握)
切点的定义:
// 定义一个切点
@Pointcut("execution(* com.hao.aop.aspectj.anno.UserDao.find(..))")
private void myPointcut() {} // 此方法没有实际的意义,就是为了提供给注解一个修饰的地方
Advisor和Aspect的区别?
- Advisor:Spring传统意义上的切面:支持一个切点和一个通知的组合.
- Aspect:可以支持多个切点和多个通知的组合.
第四步:开启注解,并将切面类和业务逻辑类(目标方法所在类)都加入到容器中
XML方式:
<!-- 自动生成代理 底层就是AnnotationAwareAspectJAutoProxyCreator -->
<aop:aspectj-autoproxy />
<bean id="userDao" class="com.hao.aop.aspectj.anno.UserDao"></bean>
<bean id="myAspect" class="com.hao.aop.aspectj.anno.MyAspect"></bean>
注解方式
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
//业务逻辑类加入容器中
@Bean
public UserDao userDao(){
return new UserDao();
}
//切面类加入到容器中
@Bean
public MyAspect myAspect(){
return new MyAspect();
}
}
测试
- 注意加载的配置文件
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=MainConfigOfAOP.class)
//此处的配置文件可以加载java类 或者 XML文件
//@ContextConfiguration("classpath:contextApplication5.xml")
public class AOPTest2 {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
@Test
public void demo1(){
userDao.add();
userDao.delete();
userDao.update();
userDao.find();
}
}
给配置类中加@EnableAspectJAutoProxy【开启基于注解的aop模式】,在Spring中很多的 @EnableXXX(要抽时间整理一下其原理);
总结
- 将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
- 在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
- 开启基于注解的aop模式;@EnableAspectJAutoProxy
6. AOP的实现机制原理(AnnotationAwareAspectJAutoProxyCreator)
AnnotationAwareAspectJAutoProxyCreator 底层的原理
已经整理完,查阅【Spring】AOP注解方式实现机制