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创建字节码。

    参考博客

  • 相关阅读:
    tomcat并发个题-未解决
    tengine安装
    nginx获得自定义参数
    nginx限流
    树形背包——hdu1561
    树形dp专题
    单调队列——P1725 琪露诺
    单调队列,dp——POJ
    记忆化搜索——HDU
    区间dp——POJ
  • 原文地址:https://www.cnblogs.com/wei57960/p/12679254.html
Copyright © 2011-2022 走看看