Spring AOP详解

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实现动态代理有两种方式:

  1. JDK动态代理
  2. 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的打印");
    }
}

引用

代理模式VS装饰模式

设计模式—代理模式

Spring AOP的实现原理及应用场景(通过动态代理)

Springboot使用Aspectj实现AOP面向切面编程

Spring Boot(十一)使用AOP,@Aspect统一处理Web请求日志

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值