zoukankan      html  css  js  c++  java
  • 动态代理的不同实现

    动态代理实现

    1.JDK动态代理

    jdk动态代理实现步骤:

    前提:jdk动态代理有限制条件,要代理的目标对象必须要实现接口

    实现:使用反射API实现,具体实现原理这里不做详细讲解,这里只讲解动态代理的实现。

    以下为代码列表,所有涉及到的类有三个

    • Calculator.java 【目标对象实现的接口】

    • CalculatorImpl.java 【目标对象】

    • Main.java 【程序入口类】

    //目标对象实现的接口
    public interface Calculator {
        int add(int a, int b);
    }
    //目标对象
    public class CalculatorImpl implements Calculator {
        @Override
        public int add(int a, int b) {
            return a + b;
        }
    }
    //程序入口
    import java.lang.reflect.Proxy;
    ​
    public class Main {
        public static void main(String[] args) {
            //创建目标对象
            Calculator calculator = new CalculatorImpl();
            //创建代理对象
            Calculator o = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(), new Class[]{Calculator.class}, (proxy, method, args1) -> {
                //定义其他的程序流程
                System.out.println("the method " + method.getName() + " is running ...");
                //执行目标方法
                return method.invoke(calculator, args1);
            });
            o.add(2, 3);
        }
    }

    这三个类中,接口和目标对象没什么说的,都很简单。核心代码其实就一行

    Calculator o = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(), new Class[]{Calculator.class}, (proxy, method, args1) -> {
                System.out.println("the method " + method.getName() + " is running ...");
                return method.invoke(calculator, args1);
            });

    对应的API就是

    //返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
    public static Object newProxyInstance(ClassLoader loader,//类加载器
                                          Class<?>[] interfaces,//目标对象实现的接口
                                          InvocationHandler h)//调用处理程序
                                   throws IllegalArgumentException

     

    2.CGLIB动态代理

    cglib动态代理不需要目标类继承其它类或者实现接口,利用asm开源包,对目标对象类的class文件加载进来,通过修改其字节码生成子类来处理。实际上cglib的api和jdk动态代理api很类似。

    //目标对象,这里没有实现接口,所以用CGLIB代理实现
    public class Calculator{
        public int add(int a, int b) {
            return a + b;
        }
    }
    //程序入口
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    ​
    public class Main {
        public static void main(String[] args) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Calculator.class);
            //public void setCallback(final Callback callback)
            //这个方法需要Callback回调,这是一个接口,常用的两个实现
            //1.MethodInterceptor接口,内部是intercept方法,形参如下:
                //o为返回的代理对象,不常用,容易抛出堆栈溢出
                //method为目标方法的反射类型,可以获取当前执行的目标方法的方法名
                //objects为object数组,实际为目标方法的参数列表
                //methodProxy用于调用初始方法,或者是调用其它类的同名方法【不同于第二个参数method,不用创建目标对象】
                
            //2.InvocationHandler接口,内部是invoke方法,形参如下:
            //invoke(java.lang.Object o, java.lang.reflect.Method method, java.lang.Object[] objects)
                //o为返回的代理对象,不常用,容易抛出堆栈溢出
                //method为目标方法的反射类型
                //objects为参数列表
            
            enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
                String name = method.getName();
                //目标方法
                System.out.println("方法" + name + "正在运行");
                //目标方法的参数列表
                System.out.println("参数列表:"+objects);
                //Object result = method.invoke(o, objects);  //方法抛出堆栈异常,需要创建目标对象作为形参
                Object result = methodProxy.invokeSuper(o, objects);
                System.out.println("执行结果:"+result);
                return result;
            });
            //创建代理对象
            Calculator proxyObject = (Calculator) enhancer.create();
           // System.out.println(proxyObject);
            proxyObject.add(1, 2);
        }
    }

    3.总结jdk动态代理和cglib代理

    jdk动态代理需要目标对象实现接口

    cglib代理不需要目标对象实现接口或者继承对象

    jdk动态代理使用的是反射的API

    cglib代理使用asm解析并修改字节码文件,生成的代理对象本质是目标对象的子类

    两种方式都是运行时动态绑定的。

    4.aspectj代理实现

    Spring AOP本质就是配合使用JDK Proxy动态代理和CGLIB工具,从而实现方法的切入。Spring会优先使用JDK动态代理,当调用方法不是接口方法时,选择使用CGLIB。这一章节使用aspectj的注解配置来实现动态代理。

    Spring AOP 和 Aspectj对比文章请参考:https://juejin.im/post/5a695b3cf265da3e47449471

    //目标对象
    @Component
    public class Calculator2 {
    ​
        public int add(int a, int b) {
            return a + b;
        }
    }
    //切面
    @Component
    @Aspect
    public class LoggingAspect2 {
        /**
         * 定义切入点表达式,方便管理和代码复用
         */
        @Pointcut(value = "execution(* aspectj.*.*.*(..))")
        public void declarePointcutExpression() {
            //此方法内部不能有任何代码,只是用于标记Pointcut注解
        }
    ​
        /**
         * 前置通知,在方法被执行前调用
         *
         * @param joinPoint 连接点
         */
        @Before(value = "declarePointcutExpression()")
        public void beforeExecute(JoinPoint joinPoint) {
            String name = joinPoint.getSignature().getName();
            System.out.println("前置通知:在" + name + "方法【前】被执行...");
        }
    ​
        /**
         * 后置通知,在方法执行后调用,不论方法是否抛出异常,都会调用
         *
         * @param joinPoint 连接点
         */
        @After(value = "declarePointcutExpression()")
        public void afterExecute(JoinPoint joinPoint) {
            String name = joinPoint.getSignature().getName();
            System.out.println("后置通知:在" + name + "方法【后】被执行...");
        }
    ​
        /**
         * 返回通知,在方法正常执行完毕,返回后调用,可以获取返回值
         *
         * @param joinPoint 连接点
         */
        @AfterReturning(value = "declarePointcutExpression()", returning = "result")
        public void afterReturning(JoinPoint joinPoint, Object result) {
            String name = joinPoint.getSignature().getName();
            System.out.println("正常返回通知:在" + name + "方法【正常执行返回后通知】被执行...");
            System.out.println("----------------------方法返回值:---" + result + "----------");
        }
    ​
        /**
         * 抛出异常通知,在方法抛出异常后执行,可以限定方法在抛出指定的异常之后才执行,可以获取异常信息
         *
         * @param joinPoint 连接点
         */
        @AfterThrowing(value = "declarePointcutExpression()", throwing = "e")
        public void afterThrowing(JoinPoint joinPoint, Throwable e) {
            String name = joinPoint.getSignature().getName();
            System.out.println("发出异常通知:在" + name + "方法【抛出异常】被执行");
            System.out.println("----------------------获取异常信息:---" + e.toString() + "-----------");
        }
    ​
        /**
         * 环绕通知:在方法的前后都可以定义代码块执行,功能最强
         */
        @Around(value = "declarePointcutExpression()")
        public Object around(ProceedingJoinPoint proceedingJoinPoint) {
            String name = proceedingJoinPoint.getSignature().getName();
            System.out.println("环绕通知:在" + name + "方法前后都可以执行代码块");
            System.out.println("环绕通知:此为方法调用前的输出日志....");
            Object result = null;
            try {
                //调用目标对象的对应方法
                result = proceedingJoinPoint.proceed();
                System.out.println("环绕通知:此为方法调用后的输出日志....");
                System.out.println("-----------------------------------环绕通知获取返回值-------" + result + "------------");
            } catch (Throwable throwable) {
                System.out.println("环绕通知:此为方法抛出异常信息之后的输出日志....");
                System.out.println("------------------------------------环绕通知获取异常信息-----------" + throwable + "---------------------------");
                throwable.printStackTrace();
            }
            System.out.println("环绕通知:此为方法正常返回时的输出日志...");
            return result;
        }
    }
    //测试类
    @SpringBootTest
    @RunWith(SpringJUnit4ClassRunner.class)
    public class CalculatorImpl2Test {
        @Autowired
        private Calculator2 calculator;
    ​
        @Test
        public void add() {
            calculator.add(1, 1);
        }
    }    

    项目源码:https://github.com/lingEric/dynamicproxy


     

     

     

     

     

  • 相关阅读:
    CVE-2020-0796 SMBv3 RCE漏洞检测+复现
    Tomcat文件包含漏洞的搭建与复现:CVE-2020-1938
    Web for pentester_writeup之XML attacks篇
    Web for pentester_writeup之LDAP attacks篇
    PhpStudy2018后门漏洞预警及漏洞复现&检测和执行POC脚本
    2016ACM/ICPC亚洲区沈阳站 E
    CodeForces 599C Day at the Beach(贪心)
    CodeForces 652C Foe Pairs(思维题)
    Codeforces 557D. Vitaly and Cycle(二分图判断)
    poj 1091跳蚤(容斥定理+质因子分解)
  • 原文地址:https://www.cnblogs.com/ericling/p/11519414.html
Copyright © 2011-2022 走看看