AOP
作用:在不修改源代码的情况下,可以实现功能的增强
1. 静态代理
要说AOP,它的核心是动态代理,说道动态代理,必须先说静态代理。静态代理是通过新增代理类对源代码进行增强
IRunnner
接口如下
public interface IRunner{
public void run();
}
Runner
类如下
public class Runner implements IRunner {
@Override
public void run() {
System.out.println("运动员在跑步...");
}
}
RunnerProxy
代理类如下
public class RunnerProxy implements IRunner {
private IRunner runner; //静态类的核心就是组合 实际的业务类
public RunnerProxy(IRunner runner) {
this.runner = runner;
}
@Override
public void run() {
//do something
runner.run();
//do something
}
}
测试场景
public class Main {
public static void main(String[] args) {
IRunner runner = new Runner();
IRunner proxy = new RunnerProxy(runner);
System.out.println("有人请求代理人让运动员跑步...");
proxy.run();
}
}
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
2. AOP的实现原理——动态代理
在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建。Spring中AOP实现动态代理有两种方式:
- JDK动态代理
- Cglib动态代理
Spring中
(1)当Bean实现接口时,Spring就会用JDK的动态代理
(2)当Bean没有实现接口时,Spring使用CGlib是实现
下面来说明原因
2.1 JDK动态代理
由JDK提供的动态代理接口来自动生成代理接口
- 继承
IInvocationHandler
接口
public class DynamicProxyHandler<T> implements InvocationHandler {
private T proxyHandler; //聚合
public DynamicProxyHandler(T proxyHandler){
this.proxyHandler = proxyHandler;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("打印日志--------before");
Object result = method.invoke(proxyHandler,args); //增强
System.out.println("打印日志--------after");
return result;
}
}
//-------------测试类
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
UserDao proxy = (UserDao) Proxy.newProxyInstance(UserDao.class.getClassLoader(),
new Class[]{UserDao.class},
new DynamicProxyHandler<>(userDao));
/*或者,效果一样
UserDao proxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
userDao.getClass().getInterfaces(),
new DynamicProxyHandler<>(userDao));
*/
proxy.saveUser();
}
注意Proxy.newProxyInstance()
方法接受三个参数
ClassLoader loader
:指定当前目标对象使用的类加载器,获取加载器的方法是固定的Class[] interfaces
:指定目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler:
指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。
2.2 CGLIB
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
- 继承
MethodInterceptor
接口
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("打印日志--------before");
Object result = method.invoke(target,objects);
System.out.println("打印日志--------after");
return result;
}
}
//-----测试类
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
CglibProxy cglibProxy = new CglibProxy();
UserDaoImpl userDaoCglibProxy = (UserDaoImpl)cglibProxy.getInstance(userDao);
userDaoCglibProxy.saveUser();
}
CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
3. SpringBoot AOP的使用
要在 Springboot
中声明 AspectJ 切面, 需在 IOC 容器中将切面声明为 Bean 实例 即加入@Component
注解;当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.
在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.引入jar包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
网上也有说要在application.properties
中添加spring.aop.auto=true
这个配置,才能开启Aspectj
注解的扫面,但是我去查询了springboot
全局配置文件,里面默认配置为true(spring.aop.auto=true # Add @EnableAspectJAutoProxy)
,所以我没有去做添加,功能没有问题,切面能正常实现。
最后补充一点小知识:
AspectJ
支持 5 种类型的通知注解
1)@Before
: 前置通知:在方法执行之前执行的通知
2)@After
: 后置通知, 在方法执行之后执行 , 即方法返回结果或者抛出异常的时候, 下面的后置通知记录了方法的终止.
3)@AfterRunning
: 返回通知, 在方法返回结果之后执行
PS:无论方法是正常返回还是抛出异常, 后置通知都会执行. 如果只想在方法返回的时候记录日志, 应使用返回通知代替后置通知.
4)@AfterThrowing
: 异常通知, 在方法抛出异常之后
5) @Around
: 环绕通知, 围绕着方法执行(即方法前后都有执行)
环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点
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.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect
@Component
public class LogAspect {
/*
标识这个方法是个前置通知, 切点表达式表示执行任意类的任意方法.
第一个 * 代表匹配任意修饰符及任意返回值,
第二个 * 代表任意类的对象,
第三个 * 代表任意方法,
参数列表中的 .. 匹配任意数量的参数
*/
private final static Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Pointcut("execution(public * com.example.demo.controller..*.*(..))")
public void webLog(){}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().toString();
Object result= Arrays.asList(joinPoint.getArgs());
logger.info("==============doBefore的打印 method:"+methodName+" args:"+result);
}
@After("webLog()")
public void doAfter(JoinPoint joinPoint) {
logger.info("==============doAfter的打印");
}
}