文章目录
什么是 AOP
-
AOP(Aspect-OrientedProgramming,面向切面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。
-
OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为(日志、安全、事务)的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,模块间的藕合度高,而不利于各个模块的重用。
-
AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
-
AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心业务逻辑组件和横切关注点。横切关注点模块化为特殊的类,这些类被称为“切面”,好处:
1. 横切关注点都集中于一块,不会出现大量重复代码。
2. 核心模块只关注核心功能的代码,模块间藕合度降低。
AOP 的实现原理
如图:AOP 实际上是由目标类的代理类实现的。AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异,AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。
AOP相关概念
-
连接点(Joinpoint):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点 。
-
通知/增强(Advice):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知,在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
-
切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义 (对哪些方法进行拦截的定义),在spring中通常用注解或者包名或者以什么开头的方法来表示,详见切入点表达式。
切入点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切入点语法。 -
引介(Introduction) :引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态的为该事务添加接口的实现逻辑,让业务类成为这个接口的实现类。
-
织入(Weaving):将切面应用到目标对象来创建新的代理对象的过程。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,采用动态代理在运行时完成织入。
-
代理(Proxy):一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增前逻辑的代理类。根据不同的代理方式,代理类及可能是和原类具有相同的接口的类,也可能是原类的子类,所以我们可以采用调用原类得相同方式调用代理类。
-
切面(Aspect):切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的链接点中。
切入点表达式增强
切入点表达式
Spring中通过切入点表达式定义具体切入点,主要有以下集中切入点表达式:
-
bean: 用于匹配指定bean对象的方法
-
within: 用于匹配指定包下所有类内的方法
-
execution: 用于按指定语法规则匹配到具体方法
-
@annotation: 用于匹配指定注解修饰的方法
表达式应用:
@Pointcut("切入点表达式")
@通知类型("@Pointcut标记的方法名")
@通知类型("切入点表达式")
bean表达式
bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
bean("类名")
bean("userServiceImpl")指定一个userServiceImpl类中所有方法。
bean("*ServiceImpl")指定所有的后缀为ServiceImpl的类。
within表达式
within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
within("aop.service.UserServiceImpl")指定当前包中这个类内部的所有方法。
within("aop.service.*") 指定当前目录下的所有类的所有方法。
within("aop.service…*") 指定当前目录以及子目录中类的所有方法。
execution表达式
execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
execution(返回值类型 包名.类名.方法名(参数列表))。
execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
execution(* aop.service….(…)) 万能配置
@annotation表达式
@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
@annotation(“注解类路径”)
@annotation(anno.RequiredLog) 匹配有RequiredLog注解描述的方法。
@annotation(anno.RequiredCache) 匹配有RequiredCache注解描述的方法。
切面优先级设置实现
切面的优先级需要借助@Order注解进行描述,数字越小优先级越高,默认优先级比较低。例如:
定义日志切面并指定优先级。
@Order(1)
@Aspect
@Component
public class SysLogAspect {
…
}
通知/增强类型详解
spring中定义了五种类型的通知,基于AspectJ框架标准,它们分别是:
-
环绕通知 (@Around) : 包围一个连接点的通知,最强大的一种通知类型,环绕通知可以在方法前后完成自定义的行为,它可以自己选择是否继续执行连接点或直接返回方法的返回值或抛异常结束执行
-
前置通知 (@Before) : 在指定连接点(join point)前执行的通知,但它不能阻止连接点前的执行(除非抛异常)
-
后置通知 (@After): 在指定连接点(join point)退出的时候执行(不管是正常返回还是异常退出)
-
返回通知 (@AfterReturning) : 在指定连接点(join point)正常返回后执行,如果抛出异常则不执行(和After通知同时存在则在After通知执行完之后再执行)
-
异常通知 (@AfterThrowing) : 在目标方法抛出异常退出时执行
注:若是存在环绕通知(@Aroud)一定要调用连接点的proceed()方法,否则会在环绕通知后直接返回,跳过目标方法
Spring增强类
Spring支持5种增强类型:
1)前置增强:org.springframework.aop.BeforeAdvice代表前置增强,spring只支持方法级的增强,目前可用MethodBeforeAdvice。
public interface Waiter {
public void greetTo(String name);
public void serveTo(String name);
}
public class NaiveWaiter implements Waiter {
@Override
public void greetTo(String name) {
System.out.println("greetTo "+ name +"...");
}
@Override
public void serveTo(String name) {
System.out.println("serveTo "+ name +"...");
}
}
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class GreeteBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
String client = (String) arg1[0];
System.out.println("How are you!"+client+".");
}
}
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
public class TestBeforeAdvice {
public static void main(String[] args) {
Waiter target = new NaiveWaiter();
BeforeAdvice advice = new GreeteBeforeAdvice();
//Spring提供的代理工厂
ProxyFactory pf = new ProxyFactory();//使用Cglib2AOPProx 即CGlib代理技术创建代理
// pf.setInterfaces(target.getClass().getInterfaces());//使用JdkDynamicAopProxy
//设置代理目标
pf.setTarget(target);
//添加增强处理
pf.addAdvice(advice);
//生成代理实例
Waiter waiter = (Waiter) pf.getProxy();
waiter.greetTo("Tom");
waiter.serveTo("Lucy");
// ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// Waiter waiter1 = (Waiter) ctx.getBean("waiter");
// waiter1.greetTo("John");
}
}
<bean id="target" class="com.baobaotao.advice.NaiveWaiter" />
<bean id="greeteAdvice" class="com.baobaotao.advice.GreeteBeforeAdvice" />
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.baobaotao.advice.Waiter" p:interceptorNames="greeteAdvice"
p:target-ref="target" />
2)后置增强:org.springframework.aop.AfterReturningAdvice代表后置增强,在目标方法执行后实施增强。
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class GreeteAfterAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
System.out.println("Please enjoy yourself!");
}
}
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="com.baobaotao.advice.Waiter" p:interceptorNames="greeteBefore,greeteAfter"
p:target-ref="target" />
3)环绕增强:org.aopalliance.intercept.MethodInterceptor代表环绕增强,在目标方法执行前后实施增强。
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class GreeteInterceptor implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation inv) throws Throwable {
Object[] args = inv.getArguments();
String client = (String) args[0];
System.out.println("How are you!"+client+".");
Object obj = inv.proceed();
System.out.println("Please enjoy yourself!");
return obj;
}
}
4)异常抛出增强:org.springframework.aop.ThrowsAdvice,在目标方法执行抛出异常后实施增强。方法名必须为afterThrowing,如参前三个可选,最后一个是Throwable或其子类。
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
public class TransactionManager implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target,
Exception ex) throws Throwable {
System.out.println("-----------");
System.out.println("method:" + method.getName());
System.out.println("抛出异常:" + ex.getMessage());
System.out.println("成功回滚事务。");
}
}
5)引介增强:org.springframework.aop.IntroductionInterceptor,表示目标类添加一些新的方法和属性,连接点是类级别,而不是方法级别。
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import com.baobaotao.proxy.PerformanceMonitor;
public class ControllablePerformaceMonitor extends DelegatingIntroductionInterceptor implements Monitorable{
private ThreadLocal<Boolean> MonitorStatusMap = new ThreadLocal<Boolean>();
@Override
public void setMonitorActive(boolean active) {
MonitorStatusMap.set(active);
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
Object obj = null;
if (MonitorStatusMap.get() != null && MonitorStatusMap.get()) {
PerformanceMonitor.begin(mi.getClass().getName() + "."
+ mi.getMethod().getName());
obj = super.invoke(mi);
PerformanceMonitor.end();
} else {
obj = super.invoke(mi);
}
return obj;
}
}
<bean id="forumService" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interfaces="com.baobaotao.introduce.Monitorable" //引入引介增强要实现的接口
p:target-ref="forumServiceTarget"
p:interceptorNames="pmonitor"
p:proxyTargetClass="true" />//只能通过为目标类型类创建子类的方式生成增强的代理
AOP切面的配置方式
1)基于Schema的配置:在xml中描述切点、增强类型,切面类为pojo
略
2)基于AspectJ的配置:切点、增强类型使用注解进行描述
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
package com.space.aspect.bo;
import lombok.Data;
/**
* 系统日志bo
*/
@Data
public class SysLogBO {
private String className;
private String methodName;
private String params;
private Long exeuTime;
private String remark;
private String createDate;
}
package com.space.aspect.service;
import com.space.aspect.bo.SysLogBO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class SysLogService {
public boolean save(SysLogBO sysLogBO){
// 这里就不做具体实现了
log.info(sysLogBO.getParams());
return true;
}
}
package com.space.aspect.anno;
import java.lang.annotation.*;
/**
* 定义系统日志注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
String value() default "";
}
/**
* 系统日志切面
*/
@Aspect // 使用@Aspect注解声明一个切面
@Component
public class SysLogAspect {
@Autowired
private SysLogService sysLogService;
/**
* 这里我们使用注解的形式
* 当然,我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method
* 切点表达式: execution(...)
*
* execution(public * *(..)) 任意的公共方法
* execution(* set*(..)) 以set开头的所有的方法
* execution(* com.LoggerApply.*(..))com.LoggerApply这个类里的所有的方法
* execution(* com.annotation.*.*(..))com.annotation包下的所有的类的所有的方法
* execution(* com.annotation..*.*(..))com.annotation包及子包下所有的类的所有的方法
* execution(* com.annotation..*.*(String,?,Long)) com.annotation包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法
* execution(@annotation(com.lingyejun.annotation.Lingyejun))
*/
@Pointcut("@annotation(com.space.aspect.anno.SysLog)")
public void logPointCut() {}
/**
* 环绕通知 @Around , 当然也可以使用 @Before (前置通知) @After (后置通知)
* @param point
* @return
* @throws Throwable
*/
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
Object result = point.proceed();
long time = System.currentTimeMillis() - beginTime;
try {
saveLog(point, time);
} catch (Exception e) {
}
return result;
}
/**
* 保存日志
*/
private void saveLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLogBO sysLogBO = new SysLogBO();
sysLogBO.setExeuTime(time);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
sysLogBO.setCreateDate(dateFormat.format(new Date()));
SysLog sysLog = method.getAnnotation(SysLog.class);
if(sysLog != null){
//注解上的描述
sysLogBO.setRemark(sysLog.value());
}
//请求的 类名、方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLogBO.setClassName(className);
sysLogBO.setMethodName(methodName);
//请求的参数
Object[] args = joinPoint.getArgs();
try{
List<String> list = new ArrayList<String>();
for (Object o : args) {
list.add(new Gson().toJson(o));
}
sysLogBO.setParams(list.toString());
}catch (Exception e){ }
sysLogService.save(sysLogBO);
}
}
//前置通知
@Before("pointcut()")
public void beforeMethod(JoinPoint joinPoint) {
if (joinPoint.getArgs().length == 1 && joinPoint.getArgs()[0] instanceof User) {
User user = (User) joinPoint.getArgs()[0];
user.setUsername("aop赋值");
log.info("调用了前置通知" + user.toString());
}
}
//@After: 后置通知
@After("pointcut()")
public void afterMethod(JoinPoint joinPoint) {
log.info("调用了后置通知");
}
//@AfterRunning: 返回通知 result为返回内容
@AfterReturning(value = "pointcut()", returning = "result")
public void afterReturningMethod(JoinPoint joinPoint, Object result) {
log.info("调用了返回通知");
}
测试:
package com.space.aspect.controller;
import com.space.aspect.anno.SysLog;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@SysLog("测试")
@GetMapping("/test")
public String test(@RequestParam("name") String name){
return name;
}
}
Spring中AOP的两种代理方式(JDK动态代理和CGLIB代理)
-
JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler只是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑与业务逻辑织在一起。而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。(只能为接口创建代理实例)
-
CGLib采用底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类的调用方法,并顺势织入横切逻辑。
SpringBoot 2.x 为何默认使用 Cglib
假设,我们有一个UserServiceImpl和UserService类,此时需要在UserContoller中使用UserService。在 Spring 中通常都习惯这样写代码:
@Autowired
UserService userService;
在这种情况下,无论是使用 JDK 动态代理,还是 CGLIB 都不会出现问题。但是,如果代码是这样的:
@Autowired
UserServiceImpl userService;
这个时候,如果使用 JDK 动态代理,启动时就会报错,因为 JDK 动态代理是基于接口的,代理生成的对象只能赋值给接口变量。
而 CGLIB 就不存在这个问题。因为 CGLIB 是通过生成子类来实现的,代理对象无论是赋值给接口还是实现类,这两者都是代理对象的父类。
SpringBoot 正是出于这种考虑,于是在 2.x 版本中,将 AOP 默认实现改为了 CGLIB。
引入AOP的方法
- 如果引入的是spring-aop包,则需要使用@EnableAspectJAutoProxy开启aop功能
- 如果引入的是spring-boot-starter-aop,则AOP就直接可以用了,无需加注解之类的开启它
对于SpringBoot:
- spring-boot-starter-aop (此包里包含了spring-aop与aspectjweaver)
对于Spring:
- spring-aop:AOP核心功能,例如代理工厂等等
- aspectjweaver:支持切入点表达式、aop相关注解等。(本依赖包含aspectjrt)
aspectjrt:支持aop相关注解等。
类的关系
本文的版本:springboot:2.3.0.RELEASE;Spring:5.2.6.RELEASE。
AOP的核心类的分类
advisorCreator,继承 spring ioc的扩展接口 BeanPostProcessor,主要用来扫描获取 advisor。
advisor:通知者,封装了spring aop中的切点和通知。 就是我们常用的@Aspect 注解标记的类。
advice: 通知,也就是aop中增强的方法。
AnnotationAwareAspectJAutoProxyCreator
- 实现了Order接口,所以先于普通的BeanPostProcessor注册,并对普通BeanPostProcessor也能起作用。
- 实现了InstantiationAwareBeanPostProcessor接口,所以会在Bean被创建之前被调用。
Spring Aop主要是通过AbstractAutoProxyCreator实现的BeanPostProcessor、InstantiationAwareBeanPostProcessor以及SmartInstantiationAwareBeanPostProcessor接口里面的后置处理器方法,来介入到Spring IOC容器的Bean的实例化以及初始化的过程中对Bean进行AOP的处理的。所以AbstractAutoProxyCreator类实现的容器级别的后置处理器方法是介入分析的点。
- AbstractAutoProxyCreator:Spring 为Spring AOP 模块暴露的可扩展抽象类,也是 AOP 中最核心的抽象类。
- BeanNameAutoProxyCreator:根据指定名称创建代理对象(阿里连接池框架druid也基于此类做了扩展)。通过设置 advisor,可以对指定的 beanName 进行代理。支持模糊匹配。
- AbstractAdvisorAutoProxyCreator:功能比较强大,默认扫描所有Advisor的实现类。相对于根据Bean名称匹配,该类更加灵活。动态的匹配每一个类,判断是否可以被代理,并寻找合适的增强类,以及生成代理类。
- DefaultAdvisorAutoProxyCreator:AbstractAdvisorAutoProxyCreator的默认实现类。可以单独使用,在框架中使用AOP,尽量不要手动创建此对象。
- AspectJAwareAdvisorAutoProxyCreator:Aspectj的实现方式,也是Spring Aop中最常用的实现方式,如果用注解方式,则用其子类AnnotationAwareAspectJAutoProxyCreator。
- AnnotationAwareAspectJAutoProxyCreator:目前最常用的AOP使用方式。spring aop 开启注解方式之后,该类会扫描所有@Aspect()注释的类,生成对应的advisor。目前SpringBoot框架中默认支持的方式,自动配置。本文我们就分析此类。