精华内容
下载资源
问答
  • 一直以为spring的ioc容器生成的对象都是代理对象,其实这个是错误的。spring ioc默认的都是原生对象 只有通过aop增强的对象才是代理对象有@Transactional 注解或者配置文件<aop:config> <...

        一直以为spring的ioc容器生成的对象都是代理对象,其实这个是错误的。spring ioc默认的都是原生对象  只有通过aop增强的对象才是代理对象

    有@Transactional  注解或者配置文件

    <aop:config>
       <aop:pointcut id="txPointcut" expression="execution(* com..service.impl.*.*(..))" />
       <aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice" />
    </aop:config>

    有配置接口aop增强的类   得到的对象都是代理对象。

    设置cglib动态代理针对于类的:

    <aop:aspectj-autoproxy proxy-target-class="true"/>

    注意:事物的传播机制也只有是代理对象操作的方法才起作用  

    展开全文
  • 静态代理是代理模式实现方式之一,比较简单,主要分为三个角色...在动态代理中,不需要我们再手动创建代理类,只需要编写一个动态处理器及指定要代理的目标对象实现的接口类型,真正的代理对象由JDK在运行时为我们创建

    在这里插入图片描述

    前言

    近期在研究Hook技术,需要用到动态代理,说到动态代理就会聊到它的兄弟静态代理,那它们到底是怎么一回事呢?实现方式有哪些呢?一起来看下

    代理模式

    代理在我们生活中随处可见,比如我们生活中的各种中介公司,以买一辆二手车为例:如果我买车,我可以自己去网上找车源,然后做质量检测,车辆过户等一系列的流程,但是这太费时间和精力了,我就是想花钱买个车而已,最后我居然吭哧吭哧的干了这么多活;于是我可以通过中介买车,我只用给钱,选车就行了,其它活都交给中介干,这就是代理的一种实现;还有网络代理(不管是正向还是反向代理),房屋租赁公司等都是代理的具体实现
    在Java编程里就有一种设计模式,即代理模式,提供了一种对目标对象的访问方式,即通过代理对象访问目标对象,代理对象是指具有与被代理对象相同的接口的类,客户端必须通过代理对象与被代理的目标类进行交互

    为啥使用代理模式:

    • 中间隔离:某些情况下,客户端不想或者不能直接引用一个目标对象,而代理类可以在客户端和目标类之前起到中介作用
    • 开闭原则,扩展功能:代理类除了是客户类和目标类的中介,还可以通过给代理类增加额外的功能来扩展目标类的功能,这样我们只需要修改代理类而不需要再修改目标类,
      符合代码设计的开闭原则(对扩展开放,对修改关闭)。代理类主要负责为目标类预处理消息、过滤消息、把消息转发给目标类,以及事后对返回结果的处理等。
      代理类本身并不真正实现服务,而是同过调用目标类的相关方法,来提供特定的服务。真正的业务功能还是由目标类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的目标类。

    代理实现方式:
    如果按照代理创建的时期来进行分类的话, 可以分为静态代理、动态代理

    • 静态代理是由程序员创建或特定工具自动生成代理类,再对其编译,在程序运行之前,代理类.class文件就已经被创建了
    • 动态代理是在程序运行时通过反射机制动态创建代理对象

    如图:
    在这里插入图片描述

    开源框架应用:
    Spring框架是时下很流行的Java开源框架,Spring之所有如此流行,跟它自身的特性是分不开的,一个是IOC,一个是AOP

    • IOC是Inverse Of Control,即控制反转,也有人把IOC称作依赖注入。我觉得依赖注入这种说法很好理解,但不完全对;依赖注入是Dependency Injection的缩写,是实现IOC的一种方法,但不等同于IOC,IOC是一种思想,DI只是一种实现

    • AOP是Aspect Oriented Programming的缩写,即面向切面编程;与面向过程和面向对象的编程方式相比,面向切面编程提供了一种全新的思路,解决了OOP编程过程中的一些痛点。

    • IOC的实现原理是利用了JAVA的反射技术,那么AOP的实现原理是什么呢?就是动态代理技术,目前动态代理技术主要分为Java自己提供的JDK动态代理技术和CGLIB技术

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

    Retrofit框架应该是时下Android端最流行的网络访问框架了,而动态代理技术在里面就运用的非常多,对接口进行解析,获取到接口里的注解信息,参数等,然后封装成Http请求,最后再由底层网络请求框架OKHttp执行

    静态代理

    静态代理是代理模式实现方式之一,比较简单,主要分为三个角色:客户端,代理类,目标类;而代理类需要与目标类实现同一个接口,并在内部维护目标类的引用,进而执行目标类的接口方法,并实现在不改变目标类的情况下前拦截,后拦截等所需的业务功能。

    假设现在项目经理给你提了一个需求:在项目所有类的有关用户操作的方法前后打印日志;这种情况下就可以通过静态代理实现

    做法就是:为每个相关类都编写一个代理类(业务重合的可以共用一个代理类),并且让它们实现相同的接口

    定义接口:

    public interface ILogin {
        void userLogin();
    }
    

    定义目标类:

    public class UserLogin implements ILogin {
    
        @Override
        public void userLogin() {
            System.out.print("用户登录");
        }
    }
    

    定义代理类:

    public class UserLoginProxy implements ILogin {
    
        private UserLogin mLogin;
        public UserLoginProxy() {
            mLogin = new UserLogin();
        }
    
        @Override
        public void userLogin() {
            System.out.print("登录前。。。");
            mLogin.userLogin();
            System.out.print("登录后。。。");
        }
    }
    

    客户端:

    public class Test {
        public static void main(String[] args){
            ILogin loginProxy = new UserLoginProxy();
            loginProxy.userLogin();
        }
    }
    

    这样我们就在尽量不修改现有类的基础上实现了这个需求,同时客户端只用跟代理类打交道,完全不用了解代理类与目标类的实现细节,也不需要知道目标类是谁;当然你如果想指定目标类,将在外部创建目标类,传给代理类
    这里只是在同步情况下的一种实现,如果是异步操作,就需要进行一些改动

    静态代理总结:

    • 优点:在符合开闭原则的情况下,对目标对象功能进行扩展和拦截
    • 缺点:需要为每个目标类创建代理类和接口,导致类的数量大大增加,工作量大;接口功能一旦修改,代理类和目标类也得相应修改,不易维护

    动态代理

    从静态代理的实现过程可以知道工作量太大,如果是在项目早期同步做还好,要是在接手老项目或者项目晚期再做,你可能要为成百上千个类创建对应的代理对象,那真的挺让人崩溃的;所以我们就需要想有没有别的方法:如何少写或者不写代理类却能完成这些功能

    做Java的都知道一个.java文件会先被编译成.class文件,然后被类加载器加载到JVM的方法区,存在形式是一个Class对象(所谓的Class对象就是Class文件在内存中的实例);我们构造出的任何对象实例是保存在Heap中,而实例是由其Class对象创建的(可以通过任意实例的getClass方法获取对应的Class对象),比如平时使用new关键字加构造方法Person p = new Person()就是将这个Class对象在Heap中创建一个实例;可以看出要创建一个实例,最关键的是得到对应的Class对象,而得到Class对象追根溯源需要一个Java文件;那么我们要做的就是不写Java文件,而是直接得到代理Class对象,然后根据它通过反射创建代理实例

    Class对象里面包含了一个类的所有信息,比如构造器,方法,字段等;那我们怎么在不写代理类的前提下获取到这些信息呢?从静态代理的实现知道目标类和代理类实现了同一个接口,这是为了尽可能保证代理对象的内部结构和目标对象一致,这样代理对象只需要专注于代码增强部分的编写,所以接口拥有代理对象和目标对象共同的类信息,那么我们就可以从接口获取本来应该由代理类提供的信息,但是要命的是接口是不能实例化的啊

    这就要讲到动态代理了:在动态代理中,不需要我们再手动创建代理类,只需要编写一个动态处理器及指定要代理的目标对象实现的接口,真正的代理对象由JDK在运行时为我们创建;JDK提供了java.lang.reflect.InvocationHandler和java.lang.reflect.Proxy来实现动态代理

    JDK代理

    Proxy.getProxyClass(ClassLoader, interfaces)方法只需要接收一个类加载器和一组接口就可以返回一个代理Class对象,然后就可以通过反射创建代理实例;其原理就是从传入的接口Class中,拷贝类结构信息到一个新的Class对象中,并继承Proxy类,拥有构造方法;站在我们的角度就是通过接口Class对象创建代理类Class对象

    这里通过一个很骚的比喻来说明下:一个东厂太监(接口Class对象)有一家子财产,但是为了侍奉皇帝这一伟大事业,毅然决然的割了DD(没有构造方法),虽然实现了自己的理想,但是不能繁育下一代(不能构造器创建对象),也就没有后人继承自己的家业;但是好在华佗在世,江湖上有一个高人(Proxy),发明了一个克隆大法(getProxyClass),不仅克隆出了几乎和太监一样的下一代(新的Class),还拥有自己的小DD(构造方法),这样这个下一代就能继承太监的家产(类结构信息,其实是实现了该接口),同时还能娶妻生子,传给下一代(创建实例)

    用一副图展示动态代理和静态代理实现思路:

    在这里插入图片描述

    那这样就很简单了

        public static Object loadProxy(Object target) throws Exception {
            //通过接口Class对象创建代理Class对象
            Class<?> proxyClass = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
            //拿到代理Class对象的有参构造方法
            Constructor<?> constructors = proxyClass.getConstructor(InvocationHandler.class);
            //反射创建代理实例
            Object proxy = constructors.newInstance(new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("执行前日志..."+"\n");
                    //执行目标类的方法
                    Object result = method.invoke(target, args);
                    System.out.println("执行后日志..."+"\n");
                    return result;
                }
            });
            return proxy;
        }
        
        public static void main(String[] args) throws Exception {
            ILogin proxy = (ILogin) loadProxy(new UserLogin());
            proxy.userLogin();
        }
    

    看看打印结果
    在这里插入图片描述
    这样无论系统有多少目标类,通过传进来的目标类都可以获取到对应的代理对象,就达到我们在执行目标类前后加日志的效果了,同时还不需要编写代理类

    Proxy类还有个更简单的方法newProxyInstance,直接返回代理对象,如下

        public static Object loadProxy(Object object) {
            return Proxy.newProxyInstance(
                    object.getClass().getClassLoader(), //和目标对象的类加载器保持一致
                    object.getClass().getInterfaces(), //目标对象实现的接口,因为需要根据接口动态生成代理对象
                    new InvocationHandler() { //事件处理器,即对目标对象方法的执行
    
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("执行前日志...");
    
                            Object result = method.invoke(object, args);
    
                            System.out.println("执行后日志...");
                            return result;
                        }
                    });
        }
    

    JDK动态代理总结:

    • 优点:相对于静态代理,极大的减少类的数量,降低工作量,减少对业务接口的依赖,降低耦合,便于后期维护;同时在某些情况下是最大的优势,即可以统一修改代理类的方法逻辑,而不需要像静态代理需要修改每个代理类
    • 缺点:因为使用的是反射,所以在运行时会消耗一定的性能;同时JDK代理只支持interface的动态代理,如果你再继续深究源码,会发现,所有动态生成的代理对象都有一个共同的父类,即都继承于Proxy;Java的单继承机制决定了无法支持class的动态代理,也就意味着你拿到动态生成的代理对象,只能调用其实现的接口里的方法,无法像静态代理中的代理类可以在内部扩展更多的功能

    动态生成代理对象原理

    在动态代理的过程中,我们不能清晰的看到代理类的实际样子,而且被代理对象和代理对象是通过InvocationHandler来完成的代理过程,其中代理对象是如何生成的,具体什么样,为什么代理对象执行的方法都会走到InvocationHandler中的invoke方法,要想了解这些就需要对Java是如何动态生成代理对象源码进行分析,继续往下看

    在这里插入图片描述
    你们有没有好奇getProxyClass这个方法是怎么通过【目标类实现的接口】生成代理Class对象的呢?

    如果你在第一个方法输入如下代码

    System.out.println("执行日志..."+(proxy instanceof Proxy)+"\n");
    System.out.println("执行日志..."+(proxyClass.getName())+"\n");
    System.out.println("执行日志..."+(proxyClass.getSuperclass().getName())+"\n");
    System.out.println("执行日志..."+(proxyClass.getSuperclass().getSimpleName())+"\n");
    System.out.println("执行日志..."+(proxyClass.getInterfaces()[0].getSimpleName())+"\n");
    

    得到的结果是
    在这里插入图片描述

    • 第一个结果为true,说明代理对象属于Proxy类型
    • 第二个结果是代理对象的全限定类名
    • 第三个结果是代理对象的父类的全限定名,第四个结果是父类名
    • 第四个结果是代理对象实现的接口名

    从这四点是不是能知道点啥了:动态生成的代理对象的父类是Proxy,实现了ILogin接口;这也就是为什么能将代理对象强转成ILogin,从而调用其接口方法;也说明了为什么只能支持动态生成接口代理,不能动态生成class代理,因为最终生成的代理对象肯定会继承Proxy类,如果我们提供的类已经继承了其它类,那就不能继承Proxy类了,动态代理也就无从谈起了

    接下来再从源码分析下其原理,鉴于篇幅原因,这里只分析重点代码

    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
            proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
     /**
     * 如果存在实现给定接口的给定加载器定义的代理类,则只返回缓存副本; 否则,它将通过ProxyClassFactory创建代理类
     */
     private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
         if (interfaces.length > 65535) {
             throw new IllegalArgumentException("interface limit exceeded");	
         }
         return proxyClassCache.get(loader, interfaces);
     }
        
    private static final class ProxyClassFactory
            implements BiFunction<ClassLoader, Class<?>[], Class<?>>
        {
            // 所有代理类名称的前缀
            private static final String proxyClassNamePrefix = "$Proxy";
    
            // 用于生成唯一代理类名称的下一个数字
            private static final AtomicLong nextUniqueNumber = new AtomicLong();
    
            @Override
            public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
    
                Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
                for (Class<?> intf : interfaces) {//通过传入的接口clone后的接口
    
                ......
    
                String proxyPkg = null;     // 定义代理class的包名
                int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    
    
                for (Class<?> intf : interfaces) {
                    int flags = intf.getModifiers();//获取接口修饰符
                    if (!Modifier.isPublic(flags)) {//我们定义的接口修饰符都是public,所以这里不会进去
                        accessFlags = Modifier.FINAL;
                        String name = intf.getName();
                        int n = name.lastIndexOf('.');
                        String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                        if (proxyPkg == null) {
                            proxyPkg = pkg;
                        } else if (!pkg.equals(proxyPkg)) {
                            throw new IllegalArgumentException(
                                "non-public interfaces from different packages");
                        }
                    }
                }
    
                if (proxyPkg == null) {
                    // 如果没有非public代理接口,请使用com.sun.proxy包
                    proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
                }
    
                /*
                 * 生成代理class名称
                 * 比如com.sun.proxy.$Proxy0
                 */
                long num = nextUniqueNumber.getAndIncrement();
                String proxyName = proxyPkg + proxyClassNamePrefix + num;
    
                /*
                 * 通过接口和class名称生成代理class数据
                 * 如果继续看generateProxyClass,会发现里面是生成class数据,包括写入Object的三个初始方法、写入实现的接口、写入继承Proxy类等到ByteArrayOutputStream中,然后转换成byte数组返回,至于是否会将Byte数据写到class文件保存在本地,视情况而定(默认不保存到硬盘)   
                 */
                byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
                try {
                    //将动态生成的class数据加载到内存中 生成一个代理class对象
                    return defineClass0(loader, proxyName,
                                        proxyClassFile, 0, proxyClassFile.length);
                } catch (ClassFormatError e) {
                    throw new IllegalArgumentException(e.toString());
                }
            }
        }
    

    生成class数据源码

    ProxyGenerator.generateProxyClass()方法属于sun.misc包下,Oracle并没有提供源代码,但是我们可以使用JD-GUI这样的反编译软件打开jre\lib\rt.jar来一探究竟,以下是其核心代码的分析:

    
    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
            ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
            final byte[] var4 = var3.generateClassFile();
            //是否将生成的class数据保存到硬盘,默认不保存
            if (saveGeneratedFiles) {
                ......
            }
            return var4;
        }
        
    private byte[] generateClassFile() {
            //添加hashCode方法
            this.addProxyMethod(hashCodeMethod, Object.class);
            //添加equals方法
            this.addProxyMethod(equalsMethod, Object.class);
            //添加toString方法
            this.addProxyMethod(toStringMethod, Object.class);
            Class[] var1 = this.interfaces;
            int var2 = var1.length;
    
            int var3;
            Class var4;
            //遍历接口数组
            for(var3 = 0; var3 < var2; ++var3) {
                var4 = var1[var3];
                Method[] var5 = var4.getMethods();
                int var6 = var5.length;
                //添加接口里的方法,此时方法体还为空
                for(int var7 = 0; var7 < var6; ++var7) {
                    Method var8 = var5[var7];
                    this.addProxyMethod(var8, var4);
                }
            }
    
            Iterator var11 = this.proxyMethods.values().iterator();
    
            List var12;
            while(var11.hasNext()) {
                var12 = (List)var11.next();
                checkReturnTypes(var12);
            }
    
            Iterator var15;
            try {
                //添加一个带有InvocationHandler的构造方法
                this.methods.add(this.generateConstructor());
                var11 = this.proxyMethods.values().iterator();
                //生成方法体代码
                while(var11.hasNext()) {
                    var12 = (List)var11.next();
                    var15 = var12.iterator();
    
                    while(var15.hasNext()) {
                        ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
                        this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
                        //方法体里生成调用InvocationHandler的invoke方法代码
                        this.methods.add(var16.generateMethod());
                    }
                }
    
                this.methods.add(this.generateStaticInitializer());
            } catch (IOException var10) {
                throw new InternalError("unexpected I/O Exception", var10);
            }
    
            if (this.methods.size() > 65535) {
                throw new IllegalArgumentException("method limit exceeded");
            } else if (this.fields.size() > 65535) {
                throw new IllegalArgumentException("field limit exceeded");
            } else {
                this.cp.getClass(dotToSlash(this.className));
                this.cp.getClass("java/lang/reflect/Proxy");
                var1 = this.interfaces;
                var2 = var1.length;
                //生成实现接口,继承Proxy类代码
                for(var3 = 0; var3 < var2; ++var3) {
                    var4 = var1[var3];
                    this.cp.getClass(dotToSlash(var4.getName()));
                }
    
                this.cp.setReadOnly();
                ByteArrayOutputStream var13 = new ByteArrayOutputStream();
                DataOutputStream var14 = new DataOutputStream(var13);
    
                try {
                    var14.writeInt(-889275714);
                    var14.writeShort(0);
                    var14.writeShort(49);
                    this.cp.write(var14);
                    var14.writeShort(this.accessFlags);
                    var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
                    var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
                    var14.writeShort(this.interfaces.length);
                    Class[] var17 = this.interfaces;
                    int var18 = var17.length;
    
                    for(int var19 = 0; var19 < var18; ++var19) {
                        Class var22 = var17[var19];
                        var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
                    }
    
                    var14.writeShort(this.fields.size());
                    var15 = this.fields.iterator();
    
                    while(var15.hasNext()) {
                        ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
                        var20.write(var14);
                    }
    
                    var14.writeShort(this.methods.size());
                    var15 = this.methods.iterator();
    
                    while(var15.hasNext()) {
                        ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                        var21.write(var14);
                    }
    
                    var14.writeShort(0);
                    return var13.toByteArray();
                } catch (IOException var9) {
                    throw new InternalError("unexpected I/O Exception", var9);
                }
            }
        }
    

    从源码可以得出:

    • JDK帮我们生成了这样一个class数据,它继承了Proxy类,添加了一个带InvocationHandler参数的构造方法,这样也就明白了为什么使用构造方法反射创建代理对象的时候传入了一个InvocationHandler参数,因为默认会调用到Proxy类的构造方法,其参数正好是InvocationHandler,赋值给内部的成员变量h
      protected Proxy(InvocationHandler h) {
          Objects.requireNonNull(h);
          this.h = h;
      }
      
    • 实现了我们指定的接口,并实现了接口里的方法,同时接口中的方法调用了InvocationHandler的invoke方法
    • 当代理对象执行方法的时候,方法里面都会执行InvocationHandler的invoke方法,我们就可以在这里执行目标类方法

    这样可以说动态生成代理class对象其实是动态生成代理class数据

    动态代理类真身

    使用如下代码将动态生成的class数据保存到硬盘

        public static void write(){
            byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", UserLogin.class.getInterfaces());
            String path = "D:/Workspaces/com/sun/proxy/LoginProxy.class";
            try(FileOutputStream fos = new FileOutputStream(path)) {
                fos.write(classFile);
                fos.flush();
                System.out.println("代理类class文件写入成功");
            } catch (Exception e) {
                System.out.println("写文件错误");
            }
        }
    

    接下来将这个class文件反编译成Java文件看看:如果你没有jad工具,可以通过JAD Java Decompiler下载,使用如下命令

    /**
    * -d :用户指定输出文件保存目录
    * d:\  :具体目录目录
    * -sjava :输出文件扩展名 这里保存成Java文件
    * D:\Workspaces\com\sun\proxy\LoginProxy.class :class文件
    */
    jad -d d:\ -sjava D:\Workspaces\com\sun\proxy\LoginProxy.class
    /**
    * 上面的命令没有指定保存的Java文件名,下面的命令可以指定保存文件名
    * -p 输出详细文件信息
    */
    jad -p D:\Workspaces\com\sun\proxy\LoginProxy.class > D:\Workspaces\com\sun\proxy\LoginProxy.java
    

    代理类:

    public final class $Proxy0 extends Proxy implements ILogin
    {
    
        public $Proxy0(InvocationHandler invocationhandler)
        {
            super(invocationhandler);
        }
    
        public final boolean equals(Object obj)
        {
            try
            {
                return ((Boolean)super.h.invoke(this, m1, new Object[] {
                    obj
                })).booleanValue();
            }
            catch(Error _ex) { }
            catch(Throwable throwable)
            {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final String toString()
        {
            try
            {
                return (String)super.h.invoke(this, m2, null);
            }
            catch(Error _ex) { }
            catch(Throwable throwable)
            {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final void userLogin()
        {
            try
            {
                super.h.invoke(this, m3, null);
                return;
            }
            catch(Error _ex) { }
            catch(Throwable throwable)
            {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        public final int hashCode()
        {
            try
            {
                return ((Integer)super.h.invoke(this, m0, null)).intValue();
            }
            catch(Error _ex) { }
            catch(Throwable throwable)
            {
                throw new UndeclaredThrowableException(throwable);
            }
        }
    
        private static Method m1;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        static 
        {
            try
            {
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                    Class.forName("java.lang.Object")
                });
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m3 = Class.forName("com.mango.demo.proxy.ILogin").getMethod("userLogin", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            }
            catch(NoSuchMethodException nosuchmethodexception)
            {
                throw new NoSuchMethodError(nosuchmethodexception.getMessage());
            }
            catch(ClassNotFoundException classnotfoundexception)
            {
                throw new NoClassDefFoundError(classnotfoundexception.getMessage());
            }
        }
    }
    
    • JDK为我们生成了一个$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类
    • 代理类实现了ILogin接口,继承了Proxy类,并有一个带InvocationHandler参数的构造方法,使用super调用Proxy类的构造方法,证实了上面的分析
    • 实现了接口的userLogin方法,并在其内部调用InvocationHandler的invoke方法,其h正是Proxy类定义的成员变量
    • 最下面是通过反射拿到类中的几个方法,作为参数传递到InvocationHandler.invoke方法中,即调用动态代理对象的任何方法,最终都是走到InvocationHandler.invoke方法中(所以在invoke方法中写日志需要判断下,是否是调用代理对象指定的方法走到这里)

    至此就解答了这一节【动态生成代理对象原理】刚开始提出的几个疑问了

    总结

    JDK动态代理是实现AOP编程的一种途径,可以在执行指定方法前后贴入自己的逻辑,像Spring、Struts2就有用到该技术(拦截器设计就是基于AOP的思想);只不过JDK动态代理只能实现基于接口的动态代理,也算是一个遗憾,还有这种实现方式也不能避免类数量增加,因为你必须要为每个业务类编写业务接口;那么有没有不用写代理类、也不用写业务接口的代理方法呢?

    方法是有的,需要用到CGLib了:

    CGLIB(Code Generation Library)是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口
    CGLIB比JDK的代理更加强大,不只可以实现接口,还可以扩展类,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理
    CGLIB底层封装了ASM,通过对字节码的操作来生成类,具有更高的性能,但是CGLIB创建代理对象时所花费的时间却比JDK多;ASM是一套JAVA字节码生成架构,能够动态生成.class文件并在加载进内存之前进行修改
    使用CGLIB需要引用jar包cglib-nodep-3.2.5.jar(如果引入cglib.jar,还需要引入asm的jar包)

    像Spring框架都支持这两种动态代理方式,但Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。

    至于在我们自己的业务里需要选择哪种方式实现AOP,还是需要视情况而定,两种方式组合使用或许能支持更多的业务需求

    展开全文
  • Java中生成代理对象有两种方式,一种是Jdk原生的动态代理,另一种就是利用Cglib。Jdk原生的动态代理只能实现对接口的代理,无法实现对Class类的代理,因此Spring中引入Cglib来实现Class类的动态代理。 Aop中代理...

    Java中生成代理对象有两种方式,一种是Jdk原生的动态代理,另一种就是利用Cglib。Jdk原生的动态代理只能实现对接口的代理,无法实现对Class类的代理,因此Spring中引入Cglib来实现Class类的动态代理。
    Aop中代理对象是在ProxyFactoryBean类的getSingletonInstance方法中生成的。
    这里写图片描述
    进入createAopProxy方法。createAopProxy方法中只是简单的调用了下DefaultAopProxyFactory类的createAopProxy方法。那么重点来分析下createAopProxy方法。
    这里写图片描述
    createAopProxy方法中有两种代理方式,分别是Jdk和Cglib,如果被代理的是接口,就用Jdk代理方式,如果是类,就用Cglib代理方式。
    回到getProxy方法,查看使用这两种方式生成代理对象的具体过程。
    这里写图片描述
    这里以Cglib的方式为例。
    这里写图片描述
    这里写图片描述
    这里写图片描述
    首先从advised中获取配置文件中配置的目标对象,即target对象。然后创建并配置Cglib的Enhancer对象,Enhancer对象是Cglib中实现动态代理的主要操作类。设置Enhancer对象的过程中,设置其setCallbacks方法是重点。Enhancer中的Callbacks就跟Jdk的Proxy对象的invoke方法一样,代理相关逻辑的实现都在该方法中,也就是说Aop相关的功能都封装在里面。最后使用Enhancer的create方法生成代理对象。经过这一步之后,通过getBean方法获得的对象就不是目标对象原始的对象了,而是代理对象。
    这里写图片描述
    这个时候调用目标对象相应的方法就会被拦截器所拦截,对于不同的代理对象生成方式会调用不同的拦截回调入口。对于Jdk代理方式,因为实现了InvocationHandler接口,所以回调入口就是invoke。而对于Cglib方式,使用的则是回调方法中设置好的DynamicUnadvisedExposedInterceptor类的intercept方法。
    拦截器和回调方法都已设置好,只要调用目标对象被代理的方法就会触发这些拦截器并调用回调方法。那么拦截器是如何实现的?回调方法里面有些什么呢?下回分解。。。。。。

    展开全文
  • 我们平常在使用mybatis的时候只需要生成mapper...原因是虽然我们没有实现接口,但是通过配置文件,spring为我们生成了接口的代理类。 让我们从配置文件入手,从源码中一探究竟。 MapperScannerConfigurer这个类...

    我们平常在使用mybatis的时候只需要生成mapper接口和与其对应的xml文件就行了,我们就可以把这个接口当作一个bean,可以往其他的bean中注入了。我们没有实现mapper接口,为什么可以使用接口中的方法呢?原因是虽然我们没有实现接口,但是通过配置文件,spring为我们生成了接口的代理类。
    让我们从配置文件入手,从源码中一探究竟。
    在这里插入图片描述
    MapperScannerConfigurer这个类是负责扫描mapper接口所在的包的,它把扫描到的接口解析成一个个的bean定义(BeanDefinition),来看一下源码:
    在这里插入图片描述
    MapperScannerConfigurer实现了两个重要接口,如图所示。
    实现BeanDefinitionRegistryPostProcessor接口,我们可以自定义注册bean过程,要实现的方法是
    postProcessBeanDefinitionRegistry()
    实现InitializingBean接口的afterPropertiesSet()方法,可以在bean创建之后初始化的时候做一些操作。
    现在进入MapperScannerConfigurer的postProcessBeanDefinitionRegistry()方法
    在这里插入图片描述
    该方法内部把扫描mapper接口的工作委托给了ClassPathMapperScanner类,该类继承自ClassPathBeanDefinitionScanner,进入它的scan()方法:
    在这里插入图片描述
    ClassPathBeanDefinitionScanner中的doScan()方法:
    在这里插入图片描述
    doScan方法真正的负责生成bean定义。
    ClassPathMapperScanner重载的doScan()方法:
    在这里插入图片描述
    重载的doScan方法对生成的mapper的bean定义做了进一步处理,进入processBeanDefinitions()方法:
    在这里插入图片描述
    红色部分为该方法的核心,mapperInterface设置的值是mapper接口的带包名的路径名称;
    definition.setBeanClass()把原来的BeanClass的类型替换成了MapperFactoryBean类型,这个是Mapper接口加载定义阶段最重要的一步。是生成代理类的关键。
    查看MapperFactoryBean的定义:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    MapperFactoryBean实现了FactoryBean接口,实现了FactoryBean接口的类型在调用getBean(beanName)既通过名称获取对象时,返回的对象不是本身类型的对象,而是通过实现接口中的getObject()方法返回的对象。
    在这里插入图片描述
    MapperFactoryBean实现了FactoryBean接口InitializingBean接口,在对象初始化的时候会调用它的afterPropertiesSet方法,该方法中首先调用了checkDaoConfig()方法,MapperFactoryBean重载的checkDaoConfig()如下:
    在这里插入图片描述
    Configuration configuration = getSqlSession().getConfiguration();这一句中的getSqlSession()获取的SqlSession
    对象就是文章开头的配置文件中配置的。
    Configuration 是一个重要的配置类在这里插入图片描述
    进入 configuration.addMapper(this.mapperInterface)方法中:
    在这里插入图片描述
    在configuration内部对Mapper的操作都委托给了mapperRegistry对象,进入它的addMapper(type)方法,这里的参数type就是一个mapper接口的类型(如:com.st.mapper.UserMapper.java):
    在这里插入图片描述
    knownMappers.put(type, new MapperProxyFactory(type));这一步为我们创建了mapper 的代理工厂类对象,并把它放入了knownMappers这个Map中。MapperProxyFactory这个类我们下面再讲。
    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
    parser.parse();
    这两句完成了mapper接口对应的xml文件的解析,xml文件中的每一个方法都被解析成了一个MappedStatement对象(代码太长,不再展开),并添加到了configuration对象中:
    在这里插入图片描述

    对上面的内容的总结:
    mapper接口的定义在bean加载阶段会被替换成MapperFactoryBean类型,在spring容器初始化的时候会给我们生成MapperFactoryBean类型的对象,在该对象生成的过程中调用afterPropertiesSet()方法,为我们生成了一个
    MapperProxyFactory类型的对象存放于Configuration里的MapperRegistry对象中,同时解析了mapper接口对应的xml文件,把每一个方法解析成一个MappedStatement对象,存放于Configuration里的mappedStatements
    这个Map集合中。

    下面看一下MapperFactoryBean的getObject()方法,看看mapper代理对象是如何生成的:
    在这里插入图片描述
    在这里插入图片描述
    跟踪代码最后调用的是MapperRegistry.getMapper()方法给我们返回了mapper代理对象。

    现在来看一下MapperProxyFactory这个类:
    在这里插入图片描述
    MapperProxy是被代理的对象,看下这个类:
    在这里插入图片描述
    在invoke()方法中最终执行的是mapperMethod.execute(sqlSession, args);方法。
    来看一下MapperMethod这个类:
    在这里插入图片描述
    execute()这个方法最终负责执行我们mapper接口中方法,它会判断要执行的方法的类型,然后调用sqlSession对应的方法类型来执行,并放回结果。

    展开全文
  • Spring IOC---AOP代理对象生成的时机

    千次阅读 热门讨论 2020-12-15 20:52:37
    前置知识3.Spring AOP代理对象生成的时机3.1非提前生成代理对象3.2 提前生成代理对象4. 为什么需要两种动态代理的时机 1.概述 Spring AOP可以采用注解或者xml配置的方式实现,那么在spring的生命周期当中,是在什么...
  • Mybatis是如何通过mapper接口生成代理对象的 SSM中整体衔接要点Controller—Service接口—ServiceImpl实现类—Mapper接口 这里做了一个简单的例子以方便我们理解,示例代码如下: 1.新建两个dao接口 package ...
  • Spring中的aop(生成代理对象

    千次阅读 2019-04-27 14:04:50
    Spring能够为容器中管理的对象创建代理对象。 以前我们创建动态代理对象要调用该方法: proxy.newProxyInstance(xx,xx,xx)。 Spring中是使用动态代理和cglib代理混合使用,优先使用动态代理,如果不能使用动态...
  • 深入理解Spring AOP之二代理对象生成

    万次阅读 2015-06-30 08:51:00
    深入理解Spring AOP之二代理对象生成 springyuanm 上一篇博客中讲到了Spring的一些基本概念和初步讲了实现方法,其中提到了动态代理技术,包括JDK动态代理技术和Cglib动态代理 动态代理这部分我有过...
  • `feign.Feign`它是最上层的API,是使用者直接使用的对象,它能完成对接口生成动态代理对象,从而很方面的让你面向接口编程,而不用太过例会Feign内部的实现细节。 如果说前面7篇都是在打基础,那么它们均是在帮你...
  • spring有非常多的地方都是使用代理的,但是这些什么使用代理,使用哪种代理代理是在什么时候创建的,怎么运行起效的。这些问题都会是下面的源码中实现的。 1、代理是怎么创建的,什么时候创建的,为什么创建 ...
  • AOP(Aspect-Oriented Programming)面向切面或者面向方面编程的简称,将一些分散在对象或者类中的与业务逻辑无关的代码分离出来进行独立的管理编码,例如日志,事务处理,异常等。几个相关的重要概念: 1、方面/切面...
  • 引言 mybatis版本:3.5.1 mybatis-spring:2.0.1 ...Mybatis是如何为mapper接口生成代理对象的? Mybatis又是如何将mapper对象交给Spring管理? 我们在整合mybatis与spring时,都会使用MyBatis-Spring 将 MyBatis
  • Advice简介 1. Before:在目标方法执行之前执行织入,如果Before的处理中没有进行特殊的处理,那么目标...2. AfterReturning: 返回之后执行(前提是目标方法执行成功),可以访问到目标对象的返回值,但是不可以改变返
  • 织入:将切面应用到目标对象来创建代理对象的过程。 通过JDK代理生成AOP代理对象 Spring中的AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理,...
  • 使用动态代理生成的对象如何动态注入到spring? 如同mybatis的mapper接口自动生成代理类,亦如同spring data jpa生成接口...自己写了demo使用@Bean可以将生成的动态代理对象注入到spring容器,但无法动态的注入,求解?
  • 生成COM存根代理对象-进程透明性

    千次阅读 2013-05-29 20:01:07
    对于进程内组件还是进程外组件,无论是创建过程,还是客户程序对接口的调用,我们都可 以按照一般的同一进程内部函数调用的... 而存根代理对象则只是用于进程该组件,由于客户程序和对象在不同的进程空间内部,所以所
  • 在网上看了一遍文章《PB环境下分布式应用程序的开发》,里面提到在分布式服务器上生成NVO的代理对象,原文如下: 对于NVO和NVO-Proxy对象,在DTS中建好NVO后,为了在客户端设置其代理,可以先在DTS中设置本地代理...
  • Spring AOP源码分析(生成代理对象

    千次阅读 2016-05-08 23:36:55
    代理对象生成过程总览 以singleTon为例: 代理对象获取入口: ProxyFactoryBean的getObject AopProxy生成过程: ProxyFactoryBean的getObject获取代理对象 @Override public Object ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 286,339
精华内容 114,535
关键字:

代理对象是新生成的