美文网首页JavaWeb技术
Spring框架特性之一——IOC详解

Spring框架特性之一——IOC详解

作者: 问题_解决_分享_讨论_最优 | 来源:发表于2019-11-02 00:06 被阅读0次

    1.IoC是什么

    Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

    谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

    为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

    用图例说明一下,传统程序设计如图1,都是主动去创建相关对象然后再组合起来:

    图1 传统应用程序示意图  

    当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图2所示:

    图2-2有IoC/DI容器后程序结构示意图  

    2.IoC能做什么

    IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

    其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

    IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

    3.IoC和DI

    DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

    理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

    谁依赖于谁:当然是应用程序依赖于IoC容器;

    为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

    谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

    ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

    IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

    4.DI 动态代理 反射

    DI(Dependency Injection),依赖注入主要是通过setter注入和构造器注入,产生一个类Class的bean(实例)。DI主要是通过动态代理和反射技术来实现的。

      我们先来了解代理的概念。何为代理呢?顾名思义,代理可以帮助我们去完成某项事务,而且可以在完成事务中增强、完善相应的功能。举例来说,我国多个地区已经实施适龄公民可以直接报考驾照,但在实际中,我们在考取驾照时,往往会委托一家驾校帮助我们报考驾照,在这之中,驾校就充当了我们报考驾照的代理角色。显然,在很多时候,通过代理可以帮助我们节省时间,获得更加便捷的服务。

      其实,我们在Java开发中运用代理,与生活中的代理概念相似。

    ,  接下来,我们在介绍动态代理之前,先了解静态代理。我们接下来以生活中报考驾照为例写一个简单的案例,来了解静态代理。

    package bkjz;

    /**

    * 考驾照的人

    */

    public class Examiner {

        //身份证号,姓名

        private String id,name;

        public Examiner(String id, String name) {

            this.id = id;

            this.name = name;

        }

        @Override

        public String toString() {

            return "考生(姓名:" +name+

                    ",身份证号:" + id + ")";

        }

    }


    package bkjz;

    /**

    * 考驾照,核心业务功能接口

    * 传递入一个具体报考驾照的人

    */

    public interface GetDriverLicense {

        public void getDriverLicense(Examiner examiner);

    }


    package bkjz.impl;

    import bkjz.Examiner;

    import bkjz.GetDriverLicense;

    /**

    * 实现报考驾照的核心功能接口的类

    */

    public class GetDriverLicenseImpl implements GetDriverLicense {

        @Override

        public void getDriverLicense(Examiner examiner) {

            System.out.println("报考驾照核心业务:您的各项报名指标合格,可以报考驾照");

        }

    }


    package bkjz.impl;

    import bkjz.Examiner;

    import bkjz.GetDriverLicense;

    /**

    * 驾校代理,帮助报考者报考驾照

    */

    public class OrgProxy implements GetDriverLicense {

        //调用核心功能的接口实现类

        private GetDriverLicense getDriverLicense;

        //构造器传参

        public OrgProxy(GetDriverLicenseImpl getDriverLicense){

            this.getDriverLicense=getDriverLicense;

        }

        @Override

        public void getDriverLicense(Examiner examiner) {

            System.out.println("增强功能1:你好,"+examiner+",我来帮你收集报名需要提交哪些资料");

            System.out.println("增强功能2:你好,"+examiner+",我来帮你推荐体检医院,帮你节省时间");

            System.out.println("增强功能3:你好,"+examiner+",我来帮你到车管所递交报名资料,帮你节省时间");

            System.out.println("===========================");

            getDriverLicense.getDriverLicense(examiner);

            System.out.println("===========================");

            System.out.println("增强功能4:你好,"+examiner+",报名成功后,我帮你把报考所需身份资料的原件取回,帮你节省时间");

            System.out.println("增强功能5:你好,"+examiner+",我来帮你安排培训学习时间,帮你通过考试");

            System.out.println("增强功能6:你好,"+examiner+",你通过驾照考试后,我帮你邮寄驾照,帮你节省时间");

        }

    }


    package bkjz;

    import bkjz.impl.GetDriverLicenseImpl;

    import bkjz.impl.OrgProxy;

    /**

    * 测试类

    */

    public class Test {

        public static void main(String[] args) {

            //一个准备报考驾照的考生

            Examiner examiner=new Examiner("320923198901142757","张三");

            //找个驾校来报考驾照,以及安排后续的学习取照

            OrgProxy orgProxy=new OrgProxy(new GetDriverLicenseImpl());

            orgProxy.getDriverLicense(examiner);

        }

    }

      执行测试类后,运行结果如下:

    增强功能1:你好,考生(姓名:张三,身份证号:320923198901142757),我来帮你收集报名需要提交哪些资料

    增强功能2:你好,考生(姓名:张三,身份证号:320923198901142757),我来帮你推荐体检医院,帮你节省时间

    增强功能3:你好,考生(姓名:张三,身份证号:320923198901142757),我来帮你到车管所递交报名资料,帮你节省时间

    ===========================

    报考驾照核心业务:您的各项报名指标合格,可以报考驾照

    ===========================

    增强功能4:你好,考生(姓名:张三,身份证号:320923198901142757),报名成功后,我帮你把报考所需身份资料的原件取回,帮你节省时间

    增强功能5:你好,考生(姓名:张三,身份证号:320923198901142757),我来帮你安排培训学习时间,帮你通过考试

    增强功能6:你好,考生(姓名:张三,身份证号:320923198901142757),你通过驾照考试后,我帮你邮寄驾照,帮你节省时间

      可见,通过代理的确能够为我们带来很多便利,还可以增强很多功能。实际上,我们平时遇到的过滤器、拦截器从本质上来说,也是通过代理的方式实现的,通过代理增强一些功能,从而控制对被代理的业务的访问。当然,过滤器、拦截器运用的代理技术要比静态代理更灵活。

      静态代理有个弊端,那就是如果具体业务改变时,我们需要改写其他的代理类,在需要代理的功能很多时,就会显著增加我们的工作量,不利于效率的提升。为此,我们引入动态代理的概念。

      所谓动态代理,显然,我们需要增加一些增强型的功能时,不必再多次创建代理类,而是动态地进行相应的扩展即可。我们通过案例来看基于业务接口的动态代理。

    package test2_proxy.proxy;

    import test2_proxy.service.OrderServiceImpl;

    import test2_proxy.service.UserServiceImpl;

    import java.lang.reflect.InvocationHandler;

    import java.lang.reflect.Method;

    import java.lang.reflect.Proxy;

    /**

    * 基于接口的动态代理

    * 对于一些需要重复执行的验证功能或下一步操作,

    * 在核心方法执行前后都会执行增强的功能

    */

    public class ServiceProxy {

        public static Object newProxyInstance(Object target){

            return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InvocationHandler() {

                @Override

                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                    System.out.println("动态增强功能1:登录权限认证");

                    System.out.println("动态增强功能2:黑名单验证");

                    System.out.println("=====================");

                    Object invoke = method.invoke(target, args);//执行被代理的核心业务功能

                    System.out.println("=====================");

                    System.out.println("动态增强功能3:进入订单中心");

                    System.out.println("此处省略一万行(●—●)...");

                    return invoke;

                }

            });

        }

    }


    package test2_proxy.service;

    /**

    * 用户登录、注册、找回密码等一系列服务的接口

    */

    public interface UserService {

        public void testUserService();

    }


    package test2_proxy.service;

    import org.springframework.stereotype.Service;

    /**

    * 实现用户服务接口的类

    */

    @Service

    public class UserServiceImpl implements UserService {

        @Override

        public void testUserService() {

            System.out.println("UserService核心功能");

        }

    }


    package test2_proxy.service;

    /**

    * 订单服务接口,如下单、取消订单、放入购物车等

    */

    public interface OrderService {

        public void testOrderService();

    }


    package test2_proxy.service;

    /**

    * 实现订单核心功能接口的类

    */

    public class OrderServiceImpl implements OrderService {

        @Override

        public void testOrderService() {

            System.out.println("OrderService核心功能");

        }

    }


    package test2_proxy;

    import test2_proxy.proxy.ServiceProxy;

    import test2_proxy.service.OrderService;

    import test2_proxy.service.OrderServiceImpl;

    import test2_proxy.service.UserService;

    import test2_proxy.service.UserServiceImpl;

    /**

    * 测试类

    */

    public class Test {

        public static void main(String[] args) {

            UserService userService=new UserServiceImpl();

            userService = (UserService)ServiceProxy.newProxyInstance(userService);

            userService.testUserService();

            System.out.println("***************************");

            OrderService orderService=new OrderServiceImpl();

            orderService = (OrderService)ServiceProxy.newProxyInstance(orderService);

            orderService.testOrderService();

        }

    }

      上述代码执行完后,执行结果如下:

    动态增强功能1:登录权限认证

    动态增强功能2:黑名单验证

    =====================

    UserService核心功能

    =====================

    动态增强功能3:进入订单中心

    此处省略一万行(●—●)...

    ***************************

    动态增强功能1:登录权限认证

    动态增强功能2:黑名单验证

    =====================

    OrderService核心功能

    =====================

    动态增强功能3:进入订单中心

    此处省略一万行(●—●)...

      可见,动态代理比静态代理更加灵活,它的核心是 java.lang.reflect.Proxy代理类,该类位于reflect反射包下,可知动态代理当中是用了反射的技术来实现的。

      接下来,我们再看基于类实现的动态代理。此处需要在项目中添加 cglib-2.2.2.jar包的依赖,运用cglib的方法,使代码更简洁。我们通过案例来实现。

    package test3_proxy_cglib.proxy;

    import net.sf.cglib.proxy.Enhancer;

    import net.sf.cglib.proxy.MethodInterceptor;

    import net.sf.cglib.proxy.MethodProxy;

    import java.lang.reflect.Method;

    import java.util.Arrays;

    /**

    * 动态代理类

    */

    public class ServiceProxy {

        public static Object newProxyInstance(Object target) {

            //1.工具类

            Enhancer enhancer = new Enhancer();

            //2.设置父类,传递类对象

            enhancer.setSuperclass(target.getClass());

            //3.设置回调函数

            enhancer.setCallback(new MethodInterceptor() {

                @Override

                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

                    System.out.println("cglib增强功能1");

                    System.out.println("cglib增强功能2");

                    System.out.println("此处省略一万行(●—●)...");

                    System.out.println("==================");

                    return method.invoke(target, objects);

                }

            });

            //4.功能增强后,返回代理对象

            return enhancer.create();

        }

    }


    package test3_proxy_cglib;

    /**

    * 用户服务功能类

    */

    public class UserService {

        public void testUserService(){

            System.out.println("UserService核心功能");

        }

    }


    package test3_proxy_cglib;

    /**

    * 订单服务功能类

    */

    public class OrderService {

        public void testOrderService(){

            System.out.println("OrderService核心功能");

        }

    }


    package test3_proxy_cglib;

    import test3_proxy_cglib.proxy.ServiceProxy;

    /**

    * 测试类

    */

    public class Test {

        public static void main(String[] args) {

            UserService userService=new UserService();

            userService= (UserService)ServiceProxy.newProxyInstance(userService);

            userService.testUserService();

            System.out.println("***************************");

            OrderService orderService=new OrderService();

            orderService=(OrderService)ServiceProxy.newProxyInstance(orderService);

            orderService.testOrderService();

        }

    }

      上述代码的执行结果如下:

    cglib增强功能1

    cglib增强功能2

    此处省略一万行(●—●)...

    ==================

    UserService核心功能

    ***************************

    cglib增强功能1

    cglib增强功能2

    此处省略一万行(●—●)...

    ==================

    OrderService核心功能

    小结:

    静态代理类优缺点

    优点:

    代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码,newUserManagerImpl()可以应用工厂将它隐藏,如上只是举个例子而已。

    缺点:

    1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

    2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。如上的代码是只为UserManager类的访问提供了代理,但是如果还要为其他类如Department类提供代理的话,就需要我们再次添加代理Department的代理类。

    动态代理优点:

    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强

     接下来,我们看下反射。

      Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。Java关于反射的定义主要在 java.lang.reflect包中。Java反射需要用到Class对象,Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

      Class 类的实例表示正在运行的 Java 应用程序中的类和接口,也就是jvm中有N多的实例,每个类都有对应的Class对象(包括基本数据类型)。Class 没有公共构造方法,Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是说,Class 对象不需要我们自己去创建,JVM已经帮我们创建好了。

      在使用反射时,我们需要先获取Class对象。获取Class对象有三种方式:

      第一,Object对象.getClass()

      第二,任何数据类型(包括基本数据类型)都有一个静态static的class属性

      第三,通过Class类的静态方法forName(String className)来获取  Class.forName(String className)    ------>[这是我们常用的获取类对象的方法]

      需要注意的是,在程序运行期间,一个类只有一个Class对象产生,该Class对象由JVM管理。获得了类对象,我们就随之可以使用这个类对象的所有属性和方法。

      通过反射,我们可以获取一个类的任意方法(包括构造方法)和属性,从而充分利用一个类所提供的一切资源。

      另外,我们在使用各类集合(如List,Set,Map)时,为了指明集合中元素的类型,通常会用到泛型,泛型是作用于程序编译期,编译过后,泛型的约束就会失效;反射则作用于程序编译期之后的运行期,因此,我们可以通过反射来越过泛型的检查。我们通过下面的案例来看:

    package test9;

    import java.lang.reflect.InvocationTargetException;

    import java.lang.reflect.Method;

    import java.util.ArrayList;

    import java.util.List;

    /**

    * 泛型作用于编译期,会在编译之前对程序进行检查

    * 编译期过后,泛型约束失效

    * 反射作用于运行期

    * 演示通过反射越过泛型的检查

    */

    public class Test {

        public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {

            //1.先声明一个List集合,元素泛型约束为String类型

            List<String> strList=new ArrayList<>();

            //2.向集合中添加String型的元素

            strList.add("aa");

            strList.add("bb");

            //注:在编译期之前,向集合中添加非泛型元素,程序会在编译期就报错

            //strList.add(123);

            //这时,我们通过反射来越过泛型检查

            Class strListClass = strList.getClass();//先获取Class对象

            //获取add方法,为了保障程序清晰,在main方法上抛出异常

            Method method = strListClass.getMethod("add", Object.class);

            //通过反射调用add方法,添加一个int型数据

            method.invoke(strList,123);

            System.out.println("strList="+strList);

        }

    }

      上述代码打印结果如下:

    strList=[aa, bb, 123]

      可见,定义元素泛型为String的List集合成功越过了泛型检查,添加了一个int型数据。

    5.两种动态代理JDK动态代理和CGLIB动态代理


    JDK动态代理

    jdk动态代理是jre提供给我们的类库,可以直接使用,不依赖第三方。先看下jdk动态代理的使用代码,再理解原理。

    首先有个“明星”接口类,有唱、跳两个功能:

    package proxy;

    public interface Star

    {

        String sing(String name);

        String dance(String name);

    }

    再有个明星实现类“刘德华”:

    package proxy;

    public class LiuDeHua implements Star

        @Override

        public String sing(String name)

        {

            System.out.println("给我一杯忘情水");

            return "唱完" ;

        }

        @Override

        public String dance(String name)

        {

            System.out.println("开心的马骝");

            return "跳完" ;

        }

    }

    明星演出前需要有人收钱,由于要准备演出,自己不做这个工作,一般交给一个经纪人。便于理解,它的名字以Proxy结尾,但他不是代理类,原因是它没有实现我们的明星接口,无法对外服务,它仅仅是一个wrapper。

    package proxy;

    import java.lang.reflect.InvocationHandler;

    import java.lang.reflect.Method;

    import java.lang.reflect.Proxy;

    public class StarProxy implements InvocationHandler

    {

        // 目标类,也就是被代理对象

        private Object target;

        public void setTarget(Object target)

        {

            this.target = target;

        }

        @Override

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

        {

            // 这里可以做增强

            System.out.println("收钱");

            Object result = method.invoke(target, args);

            return result;

        }

        // 生成代理类

        public Object CreatProxyedObj()

        {

            return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);

        } 

    }

    上述例子中,方法CreatProxyedObj返回的对象才是我们的代理类,它需要三个参数,前两个参数的意思是在同一个classloader下通过接口创建出一个对象,该对象需要一个属性,也就是第三个参数,它是一个InvocationHandler。需要注意的是这个CreatProxyedObj方法不一定非得在我们的StarProxy类中,往往放在一个工厂类中。上述代理的代码使用过程一般如下:

    1、new一个目标对象

    2、new一个InvocationHandler,将目标对象set进去

    3、通过CreatProxyedObj创建代理对象,强转为目标对象的接口类型即可使用,实际上生成的代理对象实现了目标接口。

            Star ldh = new LiuDeHua();

            StarProxy proxy = new StarProxy();

            proxy.setTarget(ldh);

            Object obj = proxy.CreatProxyedObj();

            Star star = (Star)obj;

    Proxy(jdk类库提供)根据B的接口生成一个实现类,我们成为C,它就是动态代理类(该类型是 $Proxy+数字 的“新的类型”)。生成过程是:由于拿到了接口,便可以获知接口的所有信息(主要是方法的定义),也就能声明一个新的类型去实现该接口的所有方法,这些方法显然都是“虚”的,它调用另一个对象的方法。当然这个被调用的对象不能是对象B,如果是对象B,我们就没法增强了,等于饶了一圈又回来了。

    所以它调用的是B的包装类,这个包装类需要我们来实现,但是jdk给出了约束,它必须实现InvocationHandler,上述例子中就是StarProxy, 这个接口里面有个方法,它是所有Target的所有方法的调用入口(invoke),调用之前我们可以加自己的代码增强。

    看下我们的实现,我们在InvocationHandler里调用了对象B(target)的方法,调用之前增强了B的方法。

        @Override

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

        {

            // 这里增强

            System.out.println("收钱");

            Object result = method.invoke(target, args);

            return result;

        }

    所以可以这么认为C代理了InvocationHandler,InvocationHandler代理了我们的类B,两级代理。

    整个JDK动态代理的秘密也就这些,简单一句话,动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。由于我们需要增强,这个增强是需要留给开发人员开发代码的,因此代理类不能直接包含被代理对象,而是一个InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。从原理可以看出,JDK动态代理是“对象”的代理。

    下面看下动态代理类到底如何调用的InvocationHandler的,为什么InvocationHandler的一个invoke方法能为分发target的所有方法。C中的部分代码示例如下,通过反编译生成后的代码查看,摘自链接地址。Proxy创造的C是自己(Proxy)的子类,且实现了B的接口,一般都是这么修饰的:

    public final class XXX extends Proxy implements XXX

    一个方法代码如下:

      public final String SayHello(String paramString)

      {

        try

        {

          return (String)this.h.invoke(this, m4, new Object[] { paramString });

        }

        catch (Error|RuntimeException localError)

        {

          throw localError;

        }

        catch (Throwable localThrowable)

        {

          throw new UndeclaredThrowableException(localThrowable);

        }

    可以看到,C中的方法全部通过调用h实现,其中h就是InvocationHandler,是我们在生成C时传递的第三个参数。这里还有个关键就是SayHello方法(业务方法)跟调用invoke方法时传递的参数m4一定要是一一对应的,但是这些对我们来说都是透明的,由Proxy在newProxyInstance时保证的。留心看到C在invoke时把自己this传递了过去,InvocationHandler的invoke的第一个方法也就是我们的动态代理实例类,业务上有需要就可以使用它。(所以千万不要在invoke方法里把请求分发给第一个参数,否则很明显就死循环了)

    C类中有B中所有方法的成员变量

      private static Method m1;

      private static Method m3;

      private static Method m4;

      private static Method m2;

      private static Method m0;

    这些变量在static静态代码块初始化,这些变量是在调用invocationhander时必要的入参,也让我们依稀看到Proxy在生成C时留下的痕迹。

    static

      {

        try

        {

          m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });

          m3 = Class.forName("jiankunking.Subject").getMethod("SayGoodBye", new Class[0]);

          m4 = Class.forName("jiankunking.Subject").getMethod("SayHello", new Class[] { Class.forName("java.lang.String") });

          m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);

          m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);

          return;

        }

        catch (NoSuchMethodException localNoSuchMethodException)

        {

          throw new NoSuchMethodError(localNoSuchMethodException.getMessage());

        }

        catch (ClassNotFoundException localClassNotFoundException)

        {

          throw new NoClassDefFoundError(localClassNotFoundException.getMessage());

        }

      }

    从以上分析来看,要想彻底理解一个东西,再多的理论不如看源码,底层的原理非常重要。

    jdk动态代理类图如下

    cglib动态代理

    我们了解到,“代理”的目的是构造一个和被代理的对象有同样行为的对象,一个对象的行为是在类中定义的,对象只是类的实例。所以构造代理,不一定非得通过持有、包装对象这一种方式。

    通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是cglib的思想。根据里氏代换原则(LSP),父类需要出现的地方,子类可以出现,所以cglib实现的代理也是可以被正常使用的。

    先看下代码

    package proxy;

    import java.lang.reflect.Method;

    import net.sf.cglib.proxy.Enhancer;

    import net.sf.cglib.proxy.MethodInterceptor;

    import net.sf.cglib.proxy.MethodProxy;

    public class CglibProxy implements MethodInterceptor

    {

        // 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中

        public Object CreatProxyedObj(Class<?> clazz)

        {

            Enhancer enhancer = new Enhancer();

            enhancer.setSuperclass(clazz);

            enhancer.setCallback(this);

            return enhancer.create();

        }

        @Override

        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable

        {

            // 这里增强

            System.out.println("收钱");

            return arg3.invokeSuper(arg0, arg2);

        }

    }

    从代码可以看出,它和jdk动态代理有所不同,对外表现上看CreatProxyedObj,它只需要一个类型clazz就可以产生一个代理对象, 所以说是“类的代理”,且创造的对象通过打印类型发现也是一个新的类型。不同于jdk动态代理,jdk动态代理要求对象必须实现接口(三个参数的第二个参数),cglib对此没有要求。

    cglib的原理是这样,它生成一个继承B的类型C(代理类),这个代理类持有一个MethodInterceptor,我们setCallback时传入的。 C重写所有B中的方法(方法名一致),然后在C中,构建名叫“CGLIB”+“$父类方法名$”的方法(下面叫cglib方法,所有非private的方法都会被构建),方法体里只有一句话super.方法名(),可以简单的认为保持了对父类方法的一个引用,方便调用。

    这样的话,C中就有了重写方法、cglib方法、父类方法(不可见),还有一个统一的拦截方法(增强方法intercept)。其中重写方法和cglib方法肯定是有映射关系的。

    C的重写方法是外界调用的入口(LSP原则),它调用MethodInterceptor的intercept方法,调用时会传递四个参数,第一个参数传递的是this,代表代理类本身,第二个参数标示拦截的方法,第三个参数是入参,第四个参数是cglib方法,intercept方法完成增强后,我们调用cglib方法间接调用父类方法完成整个方法链的调用。

    这里有个疑问就是intercept的四个参数,为什么我们使用的是arg3而不是arg1?

        @Override

        public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable

        {

            System.out.println("收钱");

            return arg3.invokeSuper(arg0, arg2);

        }

     因为如果我们通过反射 arg1.invoke(arg0, ...)这种方式是无法调用到父类的方法的,子类有方法重写,隐藏了父类的方法,父类的方法已经不可见,如果硬调arg1.invoke(arg0, ...)很明显会死循环。

    所以调用的是cglib开头的方法,但是,我们使用arg3也不是简单的invoke,而是用的invokeSuper方法,这是因为cglib采用了fastclass机制,不仅巧妙的避开了调不到父类方法的问题,还加速了方法的调用。

    fastclass基本原理是,给每个方法编号,通过编号找到方法执行避免了通过反射调用。

    对比JDK动态代理,cglib依然需要一个第三者分发请求,只不过jdk动态代理分发给了目标对象,cglib最终分发给了自己,通过给method编号完成调用。cglib是继承的极致发挥,本身还是很简单的,只是fastclass需要另行理解。

    测试

      public static void main(String[] args)

        {

            int times = 1000000;

            Star ldh = new LiuDeHua();

            StarProxy proxy = new StarProxy();

            proxy.setTarget(ldh);

            long time1 = System.currentTimeMillis();

            Star star = (Star)proxy.CreatProxyedObj();

            long time2 = System.currentTimeMillis();

            System.out.println("jdk创建时间:" + (time2 - time1));

            CglibProxy proxy2 = new CglibProxy();

            long time5 = System.currentTimeMillis();

            Star star2 = (Star)proxy2.CreatProxyedObj(LiuDeHua.class);

            long time6 = System.currentTimeMillis();

            System.out.println("cglib创建时间:" + (time6 - time5));

            long time3 = System.currentTimeMillis();

            for (int i = 1; i <= times; i++)

            {

                star.sing("ss");

                star.dance("ss");

            }

            long time4 = System.currentTimeMillis();

            System.out.println("jdk执行时间" + (time4 - time3));

            long time7 = System.currentTimeMillis();

            for (int i = 1; i <= times; i++)

            {

                star2.sing("ss");

                star2.dance("ss");

            }

            long time8 = System.currentTimeMillis();

            System.out.println("cglib执行时间" + (time8 - time7)); 

        }

    经测试,jdk创建对象的速度远大于cglib,这是由于cglib创建对象时需要操作字节码。cglib执行速度略大于jdk,所以比较适合单例模式。另外由于CGLIB的大部分类是直接对Java字节码进行操作,这样生成的类会在Java的永久堆中。如果动态代理操作过多,容易造成永久堆满,触发OutOfMemory异常。spring默认使用jdk动态代理,如果类没有接口,则使用cglib。

    打个广告,本人博客地址是:风吟个人博客

    相关文章

      网友评论

        本文标题:Spring框架特性之一——IOC详解

        本文链接:https://www.haomeiwen.com/subject/xhrqbctx.html