AOP
AOP(Aspect-OrientedProgramming,面向切面编程)
在不修改源代码的情况下,可以实现功能的增强。
基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 。
可以做到将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
特点
1、降低模块之间的耦合度
2、使系统容易扩展
3、更好的代码复用。
概念
- Aspect(切面):通常是一个类,里面可以定义切入点和通知
- JointPoint(连接点):被拦截到的每个点,spring中指被拦截到的每⼀个⽅法,spring aop⼀个连接点即代表⼀个⽅法的执⾏。
- Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
- Pointcut(切入点):对连接点进⾏拦截的定义(匹配规则定义 规定拦截哪些⽅法,对哪些⽅法进⾏处理),spring 有专⻔的表达式语⾔定义。
- AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
- Target(⽬标对象):被代理的⽬标对象
- . Weave(织⼊):将切⾯应⽤到⽬标对象并⽣成代理对象的这个过程即为织⼊
通知方法
- 前置通知:在我们执行目标方法之前运行(@Before)
- 后置通知:在我们目标方法运行结束之后 ,不管有没有异常(@After)
- 返回通知:在我们的目标方法正常返回值后运行(@AfterReturning)
- 异常通知:在我们的目标方法出现异常后运行(@AfterThrowing)
- 环绕通知:动态代理, 需要手动执行joinPoint.procced()(其实就是执行我们的目标方法执行之前相当于前置通知, 执行之后就相当于我们后置通知(@Around)
关于代理模式
在学习spring中aop之前,先简单了解一下代理模式
为某个对象提供一个代理,以控制对这个对象的访问。在不修改源代码的基础上做方法增强,代理模式是一种设计模式,又简单的分为两种。
静态代理
代理类和委托类在代码运行前关系就确定了,也就是说在代理类的代码一开始就已经存在了。
缺点,由于我们需要事先实现代理类,那么每个方法我都都需要去实现。如果我们要实现很多的代理类,那么工作量就太大了。动态代理的产生就是这样而来的。
动态代理:
动态代理类的字节码在程序运行时的时候生成。
AOP的核心就是采用了动态代理机制。
JDK动态代理
InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理,示例如下
@Slf4j
public class ProxyTest implements InvocationHandler {
Class<?> target;
Object real;
public ProxyTest(Class<?> target) {
//必须传入接口类型,因为Proxy.newProxyInstance传入的参数必须是接口类型的
this.target = target;
}
public Object bind(Object real) {
this.real = real;
return Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("proxy before......");
method.invoke(real, args);
log.info("proxy after.....");
return null;
}
public static void main(String[] args) {
ProxyTest proxyTest = new ProxyTest(ITestService.class);
TestService testService = new TestService();
ITestService proxy = (ITestService)proxyTest.bind(testService);
proxy.service();
}
}
其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用,动态代理是根据被代理的接口生成所有的方法。
即被代理类必须实现一个接口,但是现在也有很多技术如CGLIB可以实现不需要接口也可以实现动态代理的方式。
JDK动态代理小结
现在我们对JDK代理有个简单的源码级别的认识,理清楚一下思路:JDK会帮我们在运行时生成一个代理类,这个代理类实际上就是我们需要代理的接口的实现类。实现的方法里面会调用InvocationHandler类中的invoke方法,并且同时传入自身被调用的方法的的Method对象和参数列表方便我们编码实现方法的调用。比如我们调用reduce方法,那么我们就可以通过Method.Invoke(Object obj, Object… args)调用我们具体的实现类,再在周围做一些代理做的事儿。就实现了动态代理。我们对JDK的特性做一些简单的认识:
- JDK动态代理只能代理有接口的类,并且是能代理接口方法,不能代理一般的类中的方法
- 提供了一个使用InvocationHandler作为参数的构造方法。在代理类中做一层包装,业务逻辑在invoke方法中实现
- 重写了Object类的equals、hashCode、toString,它们都只是简单的调用了InvocationHandler的invoke方法,即可以对其进行特殊的操作,也就是说JDK的动态代理还可以代理上述三个方法
- 在invoke方法中我们甚至可以不用Method.invoke方法调用实现类就返回。这种方式常常用在RPC框架中,在invoke方法中发起通信调用远端的接口等
CGLIB动态代理
JDK中提供的生成动态代理类的机制有个鲜明的特点是:某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。那么如果一个类没有实现接口怎么办呢?这就有CGLIB的诞生了,前面说的JDK的代理类的实现方式是实现相关的接口成为接口的实现类,那么我们自然而然的可以想到用继承的方式实现相关的代理类。CGLIB就是这样做的。一个简单的CGLIB代理是这样实现的:
@Slf4j
public class CGLIBTest {
@Test
public void test(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(TestService.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
log.info("proxy before....");
method.invoke(new TestService(),objects);
log.info("proxy after.....");
return null;
}
});
TestService testService = (TestService) enhancer.create();
testService.service();
}
}
CGlib可以传入接口也可以传入普通的类,接口使用实现的方式,普通类使用会使用继承的方式生成代理类.
由于是继承方式,不能对final修饰的类,final、private、static的方法进行代理
AspectJ
简介
它不属于spring;
AspectJ是一个AOP的框架;
定义了AOP语法;
有一个专门的编译器用来生成遵守Java字节编码规范的Class文件
什么是AspectJ
AspectJ是使用面向切面的一个框架
它扩展了Java语言(它本身也是一种语言)
支持原生Java代码 有自己的编译器
将代码翻译成Java字节码文件 是为了方便编写AOP代码而出现的
使用AOP编程的三个重点 通知 切点 织入
Spring AOP
Spring AOP 与ApectJ 的目的一致,都是为了统一处理横切业务,但与AspectJ不同的是,Spring AOP 并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP 更注重的是与Spring IOC容器的结合,并结合该优势来解决横切业务的问题。因此在AOP的功能完善方面,相对来说AspectJ具有更大的优势,但同时,Spring注意到AspectJ在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与AspectJ(静态织入)最根本的区别。AspectJ 1.5后,引入@Aspect形式的注解风格的开发,Spring也非常快地跟进了这种方式,因此Spring 2.0后便使用了与AspectJ一样的注解。请注意,Spring 只是使用了与 AspectJ 5 一样的注解,但仍然没有使用 AspectJ 的编译器,底层依是动态代理技术的实现,因此并不依赖于 AspectJ 的编译器。(当然,也可以使用spring整合ApectJ的方式,但是那就是另一种方案了)
Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理(基于接口代理),在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理(基于类代理)。
示例代码
//定义切面
@Aspect
@Component
//可以使用@order注解指定切面的优先级,值越小优先级越高
@Order(1)
public class WebLogAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
/**
* 切⼊点:
* 匹配规则。规定什么⽅法被拦截、需要处理什么⽅法
* 定义切⼊点
* @Pointcut("匹配规则")
* Aop 切⼊点表达式简介 *代表所以,set*,代表以set开头的所有方法.
* https://blog.csdn.net/jinnianshilongnian/article/details/84156354?
* 1. 执⾏任意公共⽅法:
* execution(public *(..))
* 2. 执⾏任意的set⽅法
* execution(* set*(..))
* 3. 执⾏com.xxxx.service包下任意类的任意⽅法
* execution(* com.xxxx.service.*.*(..))
* 4. 执⾏com.xxxx.service 包 以及⼦包下任意类的任意⽅法
* execution(* com.xxxx.service..*.*(..))
*@within:用于匹配所以持有指定注解类型内的方法;
*@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
*@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
*@annotation:用于匹配当前执行方法持有指定注解的方法;
* 下面表达式代表com.macro.mall.controller包下所有类的所有方法,第⼀个*代表的是⽅法的修饰符为所有(可选值:private、protected、public、*)
*/
@Pointcut("execution(public * com.macro.mall.controller.*.*(..))||execution(public * com.macro.mall.*.controller.*.*(..))")
public void webLog() {
}
/**
* 之前执行
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
}
/**
* 正常返回后执行
* @param ret
* @throws Throwable
*/
@AfterReturning(value = "webLog()", returning = "ret")
public void doAfterReturning(Object ret) throws Throwable {
}
/**
* do something....
* joinPoint.proceed() //执行目标方法
* do something....
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("webLog()")
public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
LOGGER.info("before.......");
Object result = joinPoint.proceed();//执行目标方法
LOGGER.info("after.......");
}
}
参考
https://blog.csdn.net/JinXYan/article/details/89302126?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.control