Spring-AOP
What's AOP?
相关信息
面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块(切面),使减少系统中的重复代码,降低模块间的耦合度。
Spring AOP 动态代理运行时在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,回调原对象的方法。
应用场景
- 日志记录
- 性能统计
- 安全控制
- 权限管理
- 事务处理
- 异常处理
- 资源池管理
动态代理
Spring AOP 中的有两种方式(JDK、CGLIB)实现动态代理。
JDK动态代理
JDK 实现的动态代理只提供接口的代理,不支持类的代理。
InvocationHandler
接口和 Proxy
类,InvocationHandler
的invoke()
方法通过反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;
接着,Proxy
利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
CGLIB动态代理
如果代理类没有实现 InvocationHandler 接口,那么 Spring AOP 会选择使用 CGLIB
来动态代理目标类。
CGLIB(Code Generation Library)动态代理,使代理对象继承目标对象(如果类被标记为final,那么它无法使用)。CGLIB 是一个代码生成库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现 AOP.
切点表达式
execution(* com.java.service..*.*(..))
表达式意义:选择 service 包及其子包下任意类任意方法。 第一个 *
代表任意返回值类型。 第二个 *
代表所有类。 第三个 *
代表所有方法。 第一个 ..
代表包及其子包。 第二个 ..
代表任意参数。
模糊查询:
execution(* com.java.service..*.add*(..))
表达式意义:选择 service 包及其子包下任意类方法名以 add 开头方法。
execution(* com.java.service..*.*Emp*(..))
表达式意义:选择 service 包及其子包下任意类方法名包括 Emp 的方法。
逻辑运算符:
execution(* com.java.service..*.*Emp*(..))
表达式意义:选择 service 包及其子包下任意类方法名不包括 Emp 的方法。
通知类型
前置通知:通知切面方法在目标方法执行前执行,使用注解:
@Before()
标记方法。后置通知:通知切面方法在目标方法执行后执行,无论目标方法是否出现异常。使用注解:
@After()
标记方法。返回通知:通知切面方法在目标方法执行后执行,只在方法正确返回结果时执行,可以获取目标方法的返回值。使用注解:
@AfterReturning
标记方法。异常通知:通知切面方法在目标方法发生异常时执行,可以获取目标方法出现的异常类型。使用注解:
@AfterThrowing
标记方法。环绕通知:前四个通知的集合。可以拿到目标方法的返回值,并且修改返回值。可以控制目标方法是否继续执行(是否调用
proceed()
)。切面方法必须有一个 Object 类型的返回值。使用注解:@Around
标记方法。
注解方式实现
通过使用注解的方式实现动态代理。
- 在 Spring 主配置文件中开启 AOP 注解。
- 编写切面对象。
- 目标对象,目标方法。
<?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: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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--导入aop命名空间,开启aop注解开发功能-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @Component:切面方法必须放到IOC容器
* @Aspect:表示这是一个切面对象
*/
@Component
@Aspect
public class TestDateAspect {
private Date methodStart;
/**
* 切点表达式,选择目标对象
*/
@Before("execution(* com.java.service.serviceImpl.CarServiceImpl.queryByFy(..))")
public void before(JoinPoint jp){
// jp.getSignature() 获取方法签名
methodStart = new Date();
}
//后置通知,在方法后执行的通知
@After("execution(* com.java.service.serviceImpl.CarServiceImpl.queryByFy(..))")
public void after(JoinPoint jp){
Date methodStop = new Date();
System.out.println(jp.getSignature()+"方法执行花费时间:"+(methodStop.getTime()-methodStart.getTime())+"毫秒");
}
}
/**
* 被代理的方法
*/
public List<Car> queryByFy(FenYeUtil fy) {
//业务逻辑
return cars;
}
配置文件方式实现
使用配置文件的方式来实现动态代理,通过配置文件来配置切面对象。
- 编写切面对象(同注解方式实现)。
- 编写 AOP 配置文件。
<?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: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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<!--配置切点表达式-->
<aop:pointcut id="mypointcut1" expression="execution(* com.java.service.serviceImpl.CarServiceImpl.queryByFy(..))"/>
<aop:pointcut id="mypointcut2" expression="execution(* com.java.service..*.*(..))"/>
<!--配置切面对象-->
<aop:aspect ref="testDateAspect">
<!--配置后置通知,method:方法名,pointcut-ref:选择切点表达式-->
<aop:after method="after" pointcut-ref="mypointcut2"/>
<aop:before method="before" pointcut-ref="mypointcut1"/>
<aop:after-returning method="returning" pointcut-ref="mypointcut1" returning="obj"/>
</aop:aspect>
</aop:config>
</beans>