AOP将通用的、与业务无关的功能抽象封装为切面类。
切面可配置在目标方法的执行前、后运行,真正做到即插即用。实现了在不修改源码的情况下对程序行为进行扩展。
Spring AOP与AspectJ的关系:
Eclipse AspectJ 是基于Java平台的面向切面编程的语言。
Spring AOP底层依赖AspectJWeaver实现类和方法的匹配。
Spring AOP利用代理模式实现对象运行时功能扩展。
关键:
利用XML配置AOP的过程:
- 依赖AspectJ
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
-
实现切面类/方法
-
配置Aspect Bean
<!--AOP配置-->
<bean id="methodAspect" class="spring.aop.aspect.MethodAspect">
- 定义PointCut
<!--PointCut 切点,使用execution表达式描述切面的作用范围-->
<!--execution(public * spring.aop..*.*(..)) 说明切面作用在spring.aop包下的所有类所有方法上-->
<aop:pointcut id="pointcut" expression="execution(public * spring.aop..*.*(..))"></aop:pointcut>
- 配置Advice
<!--定义切面类-->
<aop:aspect ref="methodAspect">
<!--before通知(advice),代表在目标方法运行前先执行methodAspect.printExecutionTime()-->
<aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
</aop:aspect>
JoinPoint连接点参数的核心方法
PointCut 切点表达式
public可以去掉,默认调用public类型方法。
<!-- 对类约束-->
<aop:pointcut id="pointcut" expression="execution(* spring.aop..*Service.*(..))"></aop:pointcut>
<!-- 对方法名约束-->
<aop:pointcut id="pointcut1" expression="execution(* spring.aop..*.create*(..))"></aop:pointcut>
<!-- 对方法返回值类型约束-->
<aop:pointcut id="pointcut2" expression="execution(String spring.aop..*.*(..))"></aop:pointcut>
<!-- 对方法参数约束-->
<aop:pointcut id="pointcut3" expression="execution(* spring.aop..*.*(String,*))"></aop:pointcut>
Advice 五种通知类型
特殊通知:引介增强
引介增强是对类的增强,允许在运行时为目标类增加新属性或方法。允许在运行时改变类的行为,让类随运行环境动态变更。
//切面类
public class MethodAspect {
public void doAfterReturning(JoinPoint joinPoint,Object ret){
System.out.println("<---返回后通知:"+ret);
}
public void doAfterThrowing(JoinPoint joinPoint,Throwable th){
System.out.println("<---异常通知:"+th.getMessage());
}
public void doAfter(JoinPoint joinPoint){
System.out.println("<---触发后置通知");
}
}
<!--定义切面类-->
<aop:aspect ref="methodAspect">
<aop:after method="doAfter" pointcut-ref="pointcut"/>
<aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
<aop:after-throwing method="doAfterThrowing" throwing="th" pointcut-ref="pointcut"/>
</aop:aspect>
返回后通知/异常通知 与 后置通知 的执行顺序由配置顺序决定。
环绕通知的方法返回值为Object,需要返回目标方法的返回值,方法参数为ProceedingJoinPoint。
利用环绕通知计算方法执行时长:
public class MethodChecker {
//ProceedingJoinPoint 是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
public Object check(ProceedingJoinPoint pjp) throws Throwable {
try {
long startTime = new Date().getTime();
Object ret = pjp.proceed();//执行目标方法
long endTime = new Date().getTime();
long duration = endTime - startTime;
if(duration >= 1000){
String className = pjp.getTarget().getClass().getName();
String methodName =pjp.getSignature().getName();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String now = sdf.format(new Date());
System.out.println("==="+now+":"+className+"."+methodName+"("+duration+"ms)===");
}
return ret;
} catch (Throwable e) {
System.out.println("Exception message:"+e.getMessage());
throw e;
}
}
}
<bean id="methodChecker" class="spring.aop.aspect.MethodChecker"></bean>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* spring.aop..*.*(..))"/>
<aop:aspect ref="methodChecker">
<!--环绕通知-->
<aop:around method="check" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
利用注解配置Spring AOP
- 依赖AspectJ
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
- 在applicationContext.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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">
<!-- 组件扫描:在IOC容器初始化时,去扫描哪个包下的所有组件类型注解-->
<context:component-scan base-package="spring.aop"/>
<!--启用Spring AOP注解模式-->
<aop:aspectj-autoproxy/>
</beans>
- 在每个组件类上增加组件类型注解,说明当前类需要被IoC实例化
- 切面类需要使用两个个注解:@Component @Aspect
- 切面方法上使用对应类型的通知注解,并增加对应的切点表达式
@Component //标记当前类为组件
@Aspect //说明当前类是切面类
public class MethodChecker {
//环绕通知,参数为PointCut切点表达式
@Around("execution(* spring.aop..*Service.*(..))")
public Object check(ProceedingJoinPoint pjp) throws Throwable {
...
}
}
Spring AOP实现原理
基于代理模式
实现功能动态扩展,包括两种形式:
- 目标类实现了接口,通过JDK动态代理实现功能扩展
- 目标类没实现接口,通过CGLib组件实现功能扩展
代理模式:
通过代理对象
对原对象实现功能扩展。
代理类和委托类都实现了相同的接口,代理类持有委托类对象引用,在代理类的实现方法中对原始功能扩展。
弊端:每个委托类至少拥有一个代理类,随着功能的扩展,手动创建的代理类越来越多,导致系统臃肿。
这种需要手动创建代理类的代理模式使用方式称为 静态代理。
JDK动态代理
在运行时通过反射技术,按照接口的结构自动生成相应代理类,完成目标方法的扩展。
**
* InvocationHandler是JDK提供的反射类,用于JDK动态代理中对目标方法进行增强
* InvocationHandler实现类与切面类对环绕通知类似
*/
public class ProxyInvocationHandler implements InvocationHandler{
private Object target;//代理类持有目标类对象
private ProxyInvocationHandler(Object target){//传入目标对象
this.target=target;
}
/**
* 在invoke()方法对目标方法进行增强
* @param proxy 代理类对象
* @param method 目标方法对象
* @param args 目标方法实参
* @return 目标方法运行后返回值
* @throws Throwable 目标方法抛出的异常
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("======"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) +"=======");
Object ret = method.invoke(target,args);//调用目标方法
return ret;
}
}
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler(userService);
//动态创建代理类
UserService userServiceProxy =(UserService) Proxy.newProxyInstance //根据已有接口创建代理类
(userService.getClass().getClassLoader(),//类加载器
userService.getClass().getInterfaces(),//类要实现的接口
invocationHandler);//如何对目标方法进行扩展
userServiceProxy.createUser();
}
JDK动态创建代理类,只有在目标类实现了接口的情况下才可以。
CGLib实现代理类
CGLib是运行时字节码增强技术。
Spring AOP扩展无接口类使用CGLib。
在运行时生成目标继承类字节码
的方式进行扩展。
CGLib实现原理: