动态代理的不同实现

2019-09-17 10:39:49来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

动态代理的不同实现

动态代理实现

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


?

 

 

 

 

 


原文链接:https://www.cnblogs.com/ericling/p/11519414.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Springboot源码分析之TypeFilter魔力

下一篇:编程必备基础知识|计算机组成原理篇(06):计算机的字符与编码集