zoukankan      html  css  js  c++  java
  • 从代理模式 到 SpringAOP

    前言

    Spring AOP 就是通过代理模式来实现切面编程的。代理模式用来为其他对象提供一种代理,以控制对这个对象的访问。

    代理对象在客户端和目标对象之间起到中介的作用。通过控制对这个对象的访问,可以做一些自己想做的事。比如在AOP中,方法调用前打印请求方的信息,结束时记录用时,便于后续分析。还可以在代理中进行权限校验,将职责进行清晰,一个类负责一件事,也易于分别进行测试。低耦合和对代码进行改造,拓展性好,而不用对目标对象进行改造。

    本文会从静态代理模式到动态代理再到SpringAOP来介绍。

    1. 静态代理

    静态代理代码实现:

    // 抽象主题
    public interface IBus {
        Object create();
    }
    
    // 具体主题
    public class Bus implements IBus {
        @Override
        public Bus create() {
            System.out.println("give you a bus");
            return this;
        }
    }
    
    // 代理主题角色
    public class BusProxy implements IBus {
        private String userName;
        private Bus bus;
    
        public BusProxy(Bus bus, String userName) {
            this.userName = userName;
            this.bus = bus;
        }
    
        @Override
        public Object create() {
            if (userName.equals("god")) {
                return bus.create();
            }
            throw new IllegalStateException("you are not a god");
        }
    
    }
    
    // 静态工厂 隐藏具体主题
    public class Factory {
    
        public static BusProxy getBus(String userName) {
            return new BusProxy(new Bus(), userName);
        }
        
    }
    

    UML

    静态代理UML

    代理模式中是没有Factory 部分的,是本人加上的。只是为了对于用户隐藏具体对象。静态代理的缺点就是不能复用,每次代理都需要写一个代理类。

    2. 动态代理

    动态代理和静态代理有什么不一样?动态代理可以复用,静态代理不行。静态代理不同的类都需要写一套。动态代理可以理解为动态生成的。

    2.1 JDK 动态代理

    通过实现InvocationHandler接口

    // 实现 InvocationHandler
    public class JDKProxy implements InvocationHandler {
    
        // 具体主题
        private Object target;
    
        // 构造器传入 具体主题
        public JDKProxy(Object object) {
            this.target = object;
        }
    
        // 调用者需使用bind() 本质上通过 Proxy.newProxyInstance 获取代理主题
        public Object bind() {
            Class clazz = target.getClass();
            return Proxy.newProxyInstance(
                    clazz.getClassLoader(),
                    clazz.getInterfaces(),
                    this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            beforeMethod();
            // 反射执行
            Object object = method.invoke(target, args);
            afterMethod();
            return object;
        }
    
        private void beforeMethod() {
            System.out.println("before method");
        }
    
        private void afterMethod() {
            System.out.println("after method");
        }
    }
    
    // 测试类使用动态代理
    public class Test {
        public static void main(String[] args) {
            IBus bus = (IBus) new JDKProxy(new Bus()).bind();
            bus.create();
        }
    }
    

    动态生成的代理类

    源码中Proxy.newProxyInstance中式通过 静态方法ProxyGenrator.genrateClass 动态生成代理类的字节码。

    方法中为代理的类"动态地"继承了Proxy 类。(所有生成的动态代理类都是Proxy类的子类)Java不支持多继承,因此JDK动态代理只能代理实现了接口的类,不能代理已经继承了别的类的类。

    其中 JDKProxy 定义的是代理行为 而非代理类,而代理类是在运行时通过反射动态的创建得出来的。

    2.2 Cglib 动态代理

    通过代理类继承目标类的方式来实现实现代理类,无法代理final修饰的方法

    CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。

    代码

    <!-- maven cglib 依赖 -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>
    
    public class CGLibProxy implements MethodInterceptor {
        
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            beforeMethod();
            // 方法调用
            Object result = methodProxy.invokeSuper(o, objects);
            afterMethod();
            return result;
        }
        
        private void beforeMethod() {
        }
    
        private void afterMethod() {
        }
    
    }
    
    public class CGLibTest {
        public static void main(String[] args) {
            // 创建Enhancer对象,类似于JDK动态代理的Proxy类
            Enhancer enhancer = new Enhancer();
            // 设置目标类的字节码文件
            enhancer.setSuperclass(Bus.class);
            // 设置回调函数
            enhancer.setCallback(new CGLibProxy());
            // 这里的creat方法就是正式创建代理类
            IBus bus = (IBus) enhancer.create();
            // 调用代理类的 create 方法
            bus.create();
        }
    }
    

    Spring 的 AOP

    Spring AOP 面向切面编程。就像代码案例中的beforeMethod 和 afterMethod 一样。在 method中进行统一拦截和验证,日志记录,这样关注点进行了分离,职责分离。通用化的代码放在切面上,不再和原来的业务代码耦合。

    Spring AOP代码使用

    @Aspect
    @Component
    public class ControllerAspect {
        private static final Logger log = LoggerFactory.getLogger(ControllerAspect.class);
    
        // where 切入点
        @Pointcut("execution(public * org.cs.backend.pattern.structure.proxy.spring..*.*(..))")
        public void webLog() {
        }
    
        // when 什么时候切入
        @Before("webLog()")
        public void before(JoinPoint joinPoint) {
            // what 作什么
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            log.info("request remote ip: [{}]", request.getRemoteAddr());
        }
    
    }
    

    AOP 重点名词:

    Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。

    Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。

    Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。

    Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。(共有五种:before,after (无论是否发生异常,都进行执行的通知),around ,afterRunning,afterThrowing)

    Target(目标对象):织入 Advice 的目标对象.。

    Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。

    • Advice 的种类:
      • 前置通知 Before
      • 后置通知 AfterReturning
      • 异常通知 AfterThrowing
      • 最终通知 After
      • 环绕通知 Around

    AOP 将业务代码和通用代码分离之后,如何让通用代码在相应的代码上起作用的?(AOP 的三种织入方式)

    • 编译时织入:需要特殊的Java编译器 如AspectJ
    • 类加载的时候织入:需要特殊的Java编译器,如AspectJ 和ApspectWerkz
    • 运行时织入: Spring采用的方式,通过动态代理的方式,实现简单

    Spring在使用动态代理时如何选择的?

    • 目标类是接口时,Spring就会用JDK的动态代理,否则默认使用Spring使用Cglib代理。

    JDK 和 CGLib比较

    JDK动态代理生成代理对象速度比cglib快;cglib生成的代理对象比JDK动态代理生成的代理对象执行效率高。

    JDK以接口形式接收代理实例,而不是代理类。CGLib以类或接口形式接收代理实例

    总结

    代理模式就是在原有的对象上,在不影响原有的对象下进行增强。这样可以功职责分明,分别测试。和适配器模式不同的是,适配器模式和原有对象实现的接口是不同的,而代理模式是相同的。

    动态代理中 JDK的方式需要目标对象未继承其他类的。而CGLib要求目标对象未被final修饰。

    JDK动态代理用Java内部的反射机制,CGLib以继承的方式通过ASM创建字节码。

    参考博客

  • 相关阅读:
    Java实现 LeetCode 56 合并区间
    JQuery实现对html结点的操作(创建,添加,删除)
    JQuery实现对html结点的操作(创建,添加,删除)
    JQuery实现对html结点的操作(创建,添加,删除)
    Java实现 LeetCode 55 跳跃游戏
    Java实现 LeetCode 55 跳跃游戏
    Java实现 LeetCode 55 跳跃游戏
    Java实现 LeetCode 54 螺旋矩阵
    Java实现 LeetCode 54 螺旋矩阵
    Java实现 LeetCode 54 螺旋矩阵
  • 原文地址:https://www.cnblogs.com/wei57960/p/12679254.html
Copyright © 2011-2022 走看看