深谈 java 动态代理

1:jdk 动态代理实现原理

1:拿到被代理类的引用,并且通过反射获取他的所有接口( JDK 动态代理必须要有接口 只有有接口才知道这个类里面有什么方法,有什么方法要重写 )
2:JDK Proxy类重新生成一个新的类,并且实现被代理类所有接口的方法
3:动态生成java 代码,把增加逻辑加入到增强代码中
package com.example.demo.proxy.dynamicProxy.JDKProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JDKMeipo implements InvocationHandler {
    private Person target;
    public Object getInstance(Person person){
        this.target = person;
        Class<? extends Person> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	//前置增强
        before();
        //调用JDK生成的代理类的方法
        Object invoke = method.invoke(this.target, args);
        //后置增强
        after();
        return invoke;
    }
    private void before(){
        System.out.println("我是媒婆,开始找对象");
        System.out.println("开始物色");
    }
    private void after(){
        System.out.println("ok 的话准备交钱");
    }
}
4:编译生成新的java 代码的 class 文件
5:加载并重新运行新的class,得到的类就是全新类

2:spring 中的动态代理

前言

Spring是Java程序员基本不可能绕开的一个框架,它的核心思想是IOC(控制反转)和AOP(面向切面编程)。在Spring中这两个核心思想都是基于设计模式实现的,IOC思想的实现基于工厂模式,AOP思想的实现则是基于代理模式。

代理模式:

代理类和被代理类实现共同的接口(或继承),代理类中存有指向被代理类的索引,实际执行时通过调用代理类的方法、实际执行的是被代理类的方法。
代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。
代理模式常见的实现有两种,静态代理和动态代理。

静态代理与动态代理

静态代理,是编译时增强,AOP 框架会在编译阶段生成 AOP 代理类,在程序运行前代理类的.class文件就已经存在了。常见的实现:JDK静态代理,AspectJ 。
动态代理,是运行时增强,它不修改代理类的字节码,而是在程序运行时,运用反射机制,在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。常见的实现:JDK、CGLIB、Javassist(Hibernate中的使用动态代理)

不过Spring AOP的实现没有用到静态代理,而是采用了动态代理的方式,有两种,JDK动态代理和CGLIB动态代理。下面简述二者的差异。

Spring中如何判断使用哪种动态代理方式?
version:spring-aop-5.0.7.RELEASE.jar
class:org.springframework.aop.framework.ProxyFactoryBean
类源码上对类的说明:

implementation that builds an AOP proxy based on beans in Spring
翻译一下:
在Spring中基于bean构建AOP代理的实现

这个类就是Spring中对于bean构建AOP代理的实现,跟踪下源码流程,翻译下执行流程:

/**
     * Return a proxy. Invoked when clients obtain beans from this factory bean.
     * Create an instance of the AOP proxy to be returned by this factory.
     * The instance will be cached for a singleton, and create on each call to
     * {@code getObject()} for a proxy.
     * @return a fresh AOP proxy reflecting the current state of this factory
     */
    @Override
    @Nullable
    public Object getObject() throws BeansException {
        //初始化拦截器链
        initializeAdvisorChain();
        //Spring中有singleton类型和prototype类型这两种不同的Bean
        //是否是singleton类型,是,返回singleton类型的代理对象
        if (isSingleton()) {
            return getSingletonInstance();
        }
        //否,返回prototype类型的代理对象
        else {
            if (this.targetName == null) {
                logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " +
                        "Enable prototype proxies by setting the 'targetName' property.");
            }
            return newPrototypeInstance();
        }
    }
1. 什么是单例、多例:
     所谓单例就是所有的请求都用一个对象来处理,比如我们常用的service和dao层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理,比如action;
 
     单例模式和多例模式说明:
     1. 单例模式和多例模式属于对象模式。
     2. 单例模式的对象在整个系统中只有一份,多例模式可以有多个实例。
     3. 它们都不对外提供构造方法,即构造方法都为私有。
 
2. 如何产生单例、多例:
    在通用的SSH中,单例在spring中是默认的,如果要产生多例,则在配置文件的bean中添加scope="prototype";
 
3. 为什么用单例、多例:
    之所以用单例,是因为没必要每个请求都新建一个对象,这样子既浪费CPU又浪费内存;
   之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态,此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;
    用单例和多例的标准只有一个:
    当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),则多例,否则单例;
 
4. 何时用单例?何时用多例?
    对于struts2来说,action必须用多例,因为action本身含有请求参数的值,即可改变的状态;
    而对于STRUTS1来说,action则可用单例,因为请求参数的值是放在actionForm中,而非action中的;
    另外要说一下,并不是说service或dao一定是单例,标准同第3点所讲的,就曾见过有的service中也包含了可改变的状态,同时执行方法也依赖该状态,但一样用的单例,这样就会出现隐藏的BUG,而并发的BUG通常很难重现和查找;

singleton(单例)作用域:当把一个Bean定义设置为singleton作用域是,Spring IoC容器中只会存在一个共享的Bean实例,并且所有对Bean的请求,只要id与该Bean定义相匹配,则只会返回该Bean的同一实例。值得强调的是singleton作用域是Spring中的缺省作用域。
prototype作用域:prototype作用域的Bean会导致在每次对该Bean请求(将其注入到另一个Bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的Bean实例。根据经验,对有状态的Bean应使用prototype作用域,而对无状态的Bean则应该使用singleton作用域。
对于具有prototype作用域的Bean,有一点很重要,即Spring不能对该Bean的整个生命周期负责。具有prototype作用域的Bean创建后交由调用者负责销毁对象回收资源。
简单的说:
singleton只有一个实例,也即是单例模式。
prototype访问一次创建一个实例,相当于new。
由于singleton作用域是Spring中的缺省作用域,则继续追踪getSingletonInstance()方法。

/**
     * Return the singleton instance of this class's proxy object,
     * lazily creating it if it hasn't been created already.
     * @return the shared singleton proxy
     */
    private synchronized Object getSingletonInstance() {
        if (this.singletonInstance == null) {
            this.targetSource = freshTargetSource();
            if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) {
                // Rely on AOP infrastructure to tell us what interfaces to proxy.
                Class<?> targetClass = getTargetClass();
                if (targetClass == null) {
                    throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy");
                }
                setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader));
            }
            // Initialize the shared singleton instance.
            super.setFrozen(this.freezeProxy);
            //获取代理对象实例
            this.singletonInstance = getProxy(createAopProxy());
        }
        return this.singletonInstance;
    }

很明显,核心就在getProxy(createAopProxy())方法中的createAopProxy(),追踪createAopProxy()创建AOP代理实例方法

/**
     * Subclasses should call this to get a new AOP proxy. They should <b>not</b>
     * create an AOP proxy with {@code this} as an argument.
     */
    protected final synchronized AopProxy createAopProxy() {
        //active:在创建第一个AOP代理时设置为true
        if (!this.active) {
            //激活此代理配置。
            activate();
        }
        //this:AdvisedSupport config,AOP形式配置
        //获取AOP代理工厂,以指定AOP形式配置创建代理实例
        return getAopProxyFactory().createAopProxy(this);
    }

很明显,createAopProxy(AdvisedSupport config)就是创建AOP代理实例,不过这里戳进去是接口,Spring中对默认实现类是org.springframework.aop.framework.DefaultAopProxyFactory,看看里面的实现逻辑

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || 		
    			hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                    "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}

终于看到JDK和CGLIB的字样了,这个方法决定了是使用JDK动态代理还是CGLIB动态代理。下面对if中的判断逻辑逐个翻译解释

config.isOptimize():是否优化
config.isProxyTargetClass():是否直接代理目标类以及任何接口
hasNoUserSuppliedProxyInterfaces(config):是否没有指定代理接口
targetClass.isInterface():确定指定的对象是否表示接口类型
Proxy.isProxyClass(targetClass):是否是代理类
这个可以看出 spring优选 JDKProxy 只有在没有接口的情况下选择 CGLIB 动态代理

再看看这个类的说明:

In general, specify {@code proxyTargetClass} to enforce a CGLIB proxy,or specify one or more interfaces to use a JDK dynamic proxy.
谷歌翻译一下
通常,指定{@code proxyTargetClass}来强制执行CGLIB代理,或指定一个或多个接口以使用JDK动态代理

结合类说明和判断逻辑,可以得出结论:

在代理对象不是借口类型或不是代理类时,指定proxyTargetClass=true后,执行CGLIB代理
代理对象是接口类型或是代理类,使用JDK代理
两种动态代理的使用
下方代码是使用JDK动态代理和CGLIB动态代理的示例,这里不探究其底层实现,而是从API的使用比较二者的差异。

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;

import java.lang.reflect.Proxy;

public class ProxyService {

    /**
     * jdk动态代理
     *
     * @param object 被代理类对象
     * @return 代理实例
     */
    public static Object jdkProxyObject(Object object) {
        //拦截器
        SimpleInterceptor interceptor = new SimpleInterceptor();
        return Proxy.newProxyInstance(
                object.getClass().getClassLoader(),
                object.getClass().getInterfaces(),
                (proxy, method, args) -> {
                    //拦截器 - 前置处理
                    interceptor.before();
                    Object result = method.invoke(object, args);
                    //拦截器 - 后置处理
                    interceptor.after();
                    return result;
                });
    }

    /**
     * cglib动态代理
     *
     * @param object 被代理类对象
     * @return 代理实例
     */
    public static Object cglibProxyObject(Object object) {
        //模拟拦截器
        SimpleInterceptor interceptor = new SimpleInterceptor();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(object.getClass());
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
            //拦截器 - 前置处理
            interceptor.before();
            Object result = method.invoke(object, objects);
            //拦截器 - 后置处理
            interceptor.after();
            return result;
        });
        return enhancer.create();
    }

}

public class SimpleInterceptor {

    public void before() {
        System.out.println("-----" + this.getClass().getSimpleName() + "do before" + "-----");
    }

    public void after() {
        System.out.println("-----" + this.getClass().getSimpleName() + "do after" + "-----");
    }

JDK动态代理

使用JDK动态代理需要使用:
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
源码方法说明:

Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler
谷歌翻译一下:
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

参数说明:

ClassLoader loader: the class loader to define the proxy class,用于定义代理类的类加载器
Class<?>[] interfaces: the list of interfaces for the proxy class,代理类的接口列表
InvocationHandler h: to implement,由代理实例的调用处理程序实现的接口
根据说明传参,可以估摸出,jdk动态代理的实现原理是实现代理对象的接口生成兄弟类。所以使用jdk动态代理必须满足以下条件:

  1. 代理对象必须实现一个或多个接口
  2. 返回的代理实例是指定接口的代理类的实例,也就是必须以对象实现的接口接收实例,而不是代理类

CGLIB动态代理

使用Spring cglib动态代理需要使用:
org.springframework.cglib.proxy.Enhancer
由于spring的Sources下载下来并没有Javadoc,没法展示源码上的方法说明。。。
不过从enhancer.setSuperclass(Class superclass) 可以看出cglib代理的特点:

代理对象不能被final修饰,因为cglib代理的实现原理是操作字节码生成代理对象子类,而被final修饰的类不能被继承
因为是子类,所以不必像jdk代理一样必须以对象实现的接口接收实例,代理对象类同样可以接收代理实例

实现原理

JDK动态代理:基于反射,生成实现代理对象接口的匿名类,通过生成代理实例时传递的InvocationHandler处理程序实现方法增强。
CGLIB动态代理:基于操作字节码,通过加载代理对象的类字节码,为代理对象创建一个子类,并在子类中拦截父类方法并织入方法增强逻辑。底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的。

性能比较

性能比较分为两个部分:生成代理实例性能、代理实例运行性能
由于看到网上有博客提到jdk版本升级会提高动态代理的性能,秉持着实事求是的原则,必须要测试下版本升级后的比较结果,测试的jdk版本为jdk1.8.0_171和jdk-10.0.1。
count数分别定义为100、1000、10000,100000,为避免干扰,方法都是单独执行,每个count执行三次,结果聚合,方便比较。

定义一个简单的service及实现

public interface SimpleService {
   void consumer();
}
import java.util.Date;

public class SimpleServiceImpl implements SimpleService {
    @Override
    public void consumer() {
        new Date();
    }
}

比较生成代理实例性能
JDK

public class JdkTest {

    public static void main(String[] args) {
        /*----------jdk----------*/
        int count = 100;
        long jdkStart = System.currentTimeMillis();
        for (int j = 0; j < count; j++) {
            SimpleService service = new SimpleServiceImpl();
            SimpleService proxy = (SimpleService) ProxyService.jdkProxyObject(service);
        }
        long jdkEnd = System.currentTimeMillis();

        System.out.println("==================================================");
        System.out.println("java.version:" + System.getProperty("java.version"));
        System.out.println("new count:" + count);
        System.out.println("jdk new proxy spend time(ms):" + (jdkEnd - jdkStart));
    }

}



CGLIB

public class CglibTest {
    public static void main(String[] args) {
        /*----------cglib----------*/
        int count = 100000;
        long cglibStart = System.currentTimeMillis();
        for (int j = 0; j < count; j++) {
            SimpleService service = new SimpleServiceImpl();
            SimpleService proxy = (SimpleService) ProxyService.cglibProxyObject(service);
        }
        long cglibEnd = System.currentTimeMillis();

        System.out.println("==================================================");
        System.out.println("java.version:" + System.getProperty("java.version"));
        System.out.println("new count:" + count);
        System.out.println("cglib new proxy spend time(ms):" + (cglibEnd - cglibStart));
    }
}

count分别为100,1000,10000,100000,打印结果聚合:
java.version:1.8.0_161

对比结果,生成代理实例性能:JDK > CGLIB;JDK版本升级后对动态代理生成实例性能有提升。

比较生成代理实例性能

JDK动态代理测试

public class JdkTest {

    public static void main(String[] args) {
        /*----------jdk----------*/
        int count = 100;
        long jdkStart = System.currentTimeMillis();
        SimpleService service = new SimpleServiceImpl();
        SimpleService proxy = (SimpleService) ProxyService.jdkProxyObject(service);
        for (int j = 0; j < count; j++) {
            proxy.consumer();
        }
        long jdkEnd = System.currentTimeMillis();

        System.out.println("==================================================");
        System.out.println("java.version:" + System.getProperty("java.version"));
        System.out.println("new count:" + count);
        System.out.println("jdk proxy consumer spend time(ms):" + (jdkEnd - jdkStart));
    }
}



CGLIB动态代理测试

public class CglibTest {

    public static void main(String[] args) {
        /*----------cglib----------*/
        int count = 100;
        long cglibStart = System.currentTimeMillis();
        SimpleService service = new SimpleServiceImpl();
        SimpleService proxy = (SimpleService) ProxyService.cglibProxyObject(service);
        for (int j = 0; j < count; j++) {
            proxy.consumer();
        }
        long cglibEnd = System.currentTimeMillis();

        System.out.println("==================================================");
        System.out.println("java.version:" + System.getProperty("java.version"));
        System.out.println("new count:" + count);
        System.out.println("cglib proxy consumer spend time(ms):" + (cglibEnd - cglibStart));
    }
}

count分别为100,1000,10000,100000,打印结果聚合:

java.version:1.8.0_161

==================================================
java.version:1.8.0_171
new count:100
jdk proxy consumer spend time(ms):148
jdk proxy consumer spend time(ms):137
jdk proxy consumer spend time(ms):168
cglib proxy consumer spend time(ms):464
cglib proxy consumer spend time(ms):447
cglib proxy consumer spend time(ms):438

==================================================
java.version:1.8.0_171
new count:1000
jdk proxy consumer spend time(ms):141
jdk proxy consumer spend time(ms):170
jdk proxy consumer spend time(ms):150
cglib proxy consumer spend time(ms):408
cglib proxy consumer spend time(ms):410
cglib proxy consumer spend time(ms):422

==================================================
java.version:1.8.0_171
new count:10000
jdk proxy consumer spend time(ms):179
jdk proxy consumer spend time(ms):173
jdk proxy consumer spend time(ms):165
cglib proxy consumer spend time(ms):416
cglib proxy consumer spend time(ms):463
cglib proxy consumer spend time(ms):415

==================================================
java.version:1.8.0_171
new count:100000
jdk proxy consumer spend time(ms):176
jdk proxy consumer spend time(ms):158
jdk proxy consumer spend time(ms):187
cglib proxy consumer spend time(ms):637
cglib proxy consumer spend time(ms):459
cglib proxy consumer spend time(ms):436
java.version:10.0.1
==================================================
java.version:10.0.1
new count:100
jdk proxy consumer spend time(ms):79
jdk proxy consumer spend time(ms):90
jdk proxy consumer spend time(ms):85
cglib proxy consumer spend time(ms):281
cglib proxy consumer spend time(ms):352
cglib proxy consumer spend time(ms):338

==================================================
java.version:10.0.1
new count:1000
jdk proxy consumer spend time(ms):89
jdk proxy consumer spend time(ms):97
jdk proxy consumer spend time(ms):106
cglib proxy consumer spend time(ms):304
cglib proxy consumer spend time(ms):303
cglib proxy consumer spend time(ms):359

==================================================
java.version:10.0.1
new count:10000
jdk proxy consumer spend time(ms):113
jdk proxy consumer spend time(ms):99
jdk proxy consumer spend time(ms):108
cglib proxy consumer spend time(ms):347
cglib proxy consumer spend time(ms):339
cglib proxy consumer spend time(ms):349

==================================================
java.version:10.0.1
new count:100000
jdk proxy consumer spend time(ms):106
jdk proxy consumer spend time(ms):102
jdk proxy consumer spend time(ms):107
cglib proxy consumer spend time(ms):342
cglib proxy consumer spend time(ms):380
cglib proxy consumer spend time(ms):385

对比结果,代理实例运行性能:JDK > CGLIB;JDK版本升级后对动态代理的代理实例运行性能有提升。

说实话看到对比结果我是震惊的,我之前一直认为CGLIB动态代理的性能应该优于JDK动态代理,通过对比结果,可以看出,动态代理总体性能:JDK > CGLIB,难怪Spring默认的动态代理方式为JDK。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值