一、SpringAOP是什么?
Spring的核心是控制反转(IoC)和面向切面(AOP)。AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。
二、AOP术语
连接点(Joinpoint) 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强
连接点表示具体要拦截的方法,上面切点是定义一个范围,而连接点是具体到某个方法
切点(PointCut) 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。比如,连接点--数据库的记录,切点--查询条件
切点用于来限定Spring-AOP启动的范围,通常我们采用表达式的方式来设置,所以关键词是范围
增强(Advice) 增强是织入到目标类连接点上的一段程序代码。在Spring中,像BeforeAdvice等还带有方位信息
通知是直译过来的结果,我个人感觉叫做“业务增强”更合适 对照代码就是拦截器定义的相关方法,通知分为如下几种:
前置通知(before):在执行业务代码前做些操作,比如获取连接对象
后置通知(after):在执行业务代码后做些操作,无论是否发生异常,它都会执行,比如关闭连接对象
异常通知(afterThrowing):在执行业务代码后出现异常,需要做的操作,比如回滚事务
返回通知(afterReturning),在执行业务代码后无异常,会执行的操作
环绕通知(around)
目标对象(Target) 需要被加强的业务对象
织入(Weaving) 织入就是将增强添加到对目标类具体连接点上的过程。
织入是一个形象的说法,具体来说,就是生成代理对象并将切面内容融入到业务流程的过程。
代理类(Proxy) 一个类被AOP织入增强后,就产生了一个代理类。
切面(Aspect) 切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。
三. 常见用法
使用AOP最常见的用法,就是定义个@Aspect注解类,在该类里面写上大量的切入点和增强方法,当然也可以以一种组合的方式,直接把pointcut的定义写在增强方法注解上。
@Aspect
@Component
public class MyAspect {
/**
* 以注解方式找到切入点
* 此处是找到被 MyAnnotation 注解标记的 methodA()方法,把这些方法作为切入点
*/
@Pointcut("@annotation(com.zhanfu.springtest.MyAnnotation)")
public void someService1(){}
/**
* 在指定的范围找切入点
* 此处是 找到定义在springtest包里的任意方法
*/
@Pointcut("execution(* com.zhanfu.springtest.*.*(..)) ")
public void someService2(){}
/**
* 增强方法,本增强方法作用于上面定义的切入点someService1(),
* 增强方式为Around
*/
@Around("someService1()")
public Object doSomething (ProceedingJoinPoint joinPoint){
System.out.println("执行前");
try {
Object result = joinPoint.proceed();
System.out.println("执行后");
return result;
} catch (Throwable e) {
System.out.println("发生异常");
e.printStackTrace();
}
return null;
}
/**
* 增强方法,组合使用,这种方式不需要另外写个pointcut,而是直接把 pointcut 写在@before注解里面
* 增强方式为Before
*/
@Before("execution(* com.zhanfu.springtest.*.main(..))")
public void before(ProceedingJoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println("前置通知:在" + name + "执行前");
}
}
四、Spring AOP的实现原理
Spring的AOP实现原理其实很简单,就是通过动态代理实现的。
Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理。
JDK动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口
CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。
JDK动态代理
Spring默认使用JDK的动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式实现动态代理。
JDK实现动态代理需要两个组件,首先第一个就是InvocationHandler接口。
我们在使用JDK的动态代理时,需要编写一个类,去实现这个接口,然后重写invoke方法,这个方法其实就是我们提供的代理方法。
如下源码所示:
/**
* 动态代理
*
* @author mikechen
*/
public class JdkProxySubject implements InvocationHandler {
private Subject subject;
public JdkProxySubject(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before 前置通知");
Object result = null;
try {
result = method.invoke(subject, args);
}catch (Exception ex) {
System.out.println("ex: " + ex.getMessage());
throw ex;
}finally {
System.out.println("after 后置通知");
}
return result;
}
}
然后JDK动态代理需要使用的第二个组件就是Proxy这个类,我们可以通过这个类的newProxyInstance方法,返回一个代理对象。
生成的代理类实现了原来那个类的所有接口,并对接口的方法进行了代理,我们通过代理对象调用这些方法时,底层将通过反射,调用我们实现的invoke方法。
public class Main { public static void main(String[] args) {
//获取InvocationHandler对象 在构造方法中注入目标对象
InvocationHandler handler = new JdkProxySubject(new RealSubject());
//获取代理类对象
Subject proxySubject = (Subject)Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Subject.class}, handler);
//调用目标方法
proxySubject.request(); proxySubject.response();
}
运行结果:
before 前置通知
执行目标对象的request方法......
after 后置通知
before 前置通知
执行目标对象的response方法......
after 后置通知
五、JDK动态代理优缺
优点
JDK动态代理是JDK原生的,不需要任何依赖即可使用;
通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快;
缺点
如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;
JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。
JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;
六、CGLib代理
CGLIB组成结构
Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截,如下图所示Cglib与Spring等应用的关系:
最底层的是字节码 Bytecode ,字节码是Java为了保证“一次编译、到处运行”而产生的一种虚拟指令格式,例如iload_0、iconst_1、if_icmpne、dup等
位于字节码之上的是 ASM ,这是一种直接操作字节码的框架,应用ASM需要对Java字节码、Class结构比较熟悉
位于 ASM 之上的是 CGLIB 、 Groovy 、 BeanShell ,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明 在JVM中执行程序并不一定非要写Java代码----只要你能生成Java字节码,JVM并不关心字节码的来源 ,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码
位于 CGLIB 、 Groovy 、 BeanShell 之上的就是 Hibernate 、 Spring AOP 这些框架了,这一层大家都比较熟悉
最上层的是Applications,即具体应用,一般都是一个Web项目或者本地跑一个程序
所以,Cglib的实现是在字节码的基础上的,并且使用了开源的ASM读取字节码,对类实现增强功能的。
1.SpringAOP不是一种新的AOP实现,使用JDK动态代理和CGLIB动态代理实现
2.SpringAOP配置方式核心是Advisor,可以自定义Advisor,也可以通过AspectJ间接定义Advisor
3.SpringAOP的实现遵循了AOP联盟规范,AOP联盟顶级API接口贯穿了整个AOP过程