美文网首页Java成长之路
为什么Spring能这么牛逼!我们来掰扯看看。

为什么Spring能这么牛逼!我们来掰扯看看。

作者: 路人甲java | 来源:发表于2020-10-26 17:16 被阅读0次

    前言

    Spring中有个非常重要的知识点——AOP,即面向切面编程,spring中提供的一些非常牛逼的功能都是通过aop实现的,比如

    • spring事务管理:@Transactional
    • spring异步处理:@EnableAsync
    • spring缓存技术的使用:@EnableCaching
    • spring中各种拦截器:@EnableAspectJAutoProxy

    spring中的aop功能主要是通过2种代理来实现的:1、jdk动态代理,2、cglib代理。

    JDK动态代理

    1.使用方式
    第一种简介的创建动态代理的方式,通过重写InvocationHandler#invoke方法

    @Test
    public void m2() throws Exception {
        // 1\. 创建代理类的处理器
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("InvocationHandler被调用的方法是:" + method.getName());
                return null;
            }
        };
        // 2\. 创建代理实例
        IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(), 
            new Class[]{IService.class}, invocationHandler);
        // 3\. 调用代理的方法
        proxyService.m1();
        proxyService.m2();
        proxyService.m3();
    }
    
    

    第二种可以是通过实现InvocationHandler接口,如:

    public class CostTimeInvocationHandler implements InvocationHandler {
    
        private Object target;
    
        public CostTimeInvocationHandler(Object target){
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            long starTime = System.nanoTime();
            Object result = method.invoke(this.target, args);
            long endTime = System.nanoTime();
            System.out.println(this.target.getClass() + 
                    ".m1()方法耗时(纳秒):" + (endTime - starTime));
            return result;
        }
    
        /**
         * 用来创建targetInterface接口的代理对象
         * @param target          需要被代理的对象
         * @param targetInterface 被代理的接口
         * @param <T>
         * @return
         */
        public static <T> T createProxy(Object target, Class<T> targetInterface) {
            if (!targetInterface.isInterface()) {
                throw new IllegalStateException("targetInterface必须是接口类型!");
            } else if (!targetInterface.isAssignableFrom(target.getClass())) {
                throw new IllegalStateException("target必须是targetInterface接口的实现类!");
            }
            return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                    target.getClass().getInterfaces(), new CostTimeInvocationHandler(target));
        }
    }
    
    

    流程总结和原理

    Java中的开发流程一般如下:

    1、首先必须要通过实现 InvocationHandler 接口创建自己的调用处理器;这个接口中只定义了一个方法:

    Interface InvocationHandler(Object proxy, Method method, Object[] args)
    
    

    2、创建被代理的接口和类;

    3、通过Proxy的静态方法
    newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理;

    4、通过代理调用方法;

    JDK 动态代理具体的原理主要也是在InvocationHandler类和Proxy类中实现:

    1、Invocation负责对方法进行包装增强。
    动态代理就是要生成一个包装类对象,由于代理的对象是动态的,所以叫动态代理。这个包装类需要我们来实现,但是jdk给出了约束,它必须实现InvocationHandler,该InvocationHandler包含被代理对象,并负责分发请求给被代理对象,分发前后均可以做增强。

    2、Proxy负责生成具体的动态代理类
    动态代理生成的类型是 $Proxy+数字的“新的类型”,继承自Proxy,且实现了被代理的接口(所以JDK的代理需要有接口)。代理类持有InvocationHandler(继承自Proxy),继承了被代理的接口(能拿到所有的方法信息),在执行每个接口的方法直接会去调用Invocation中增强过的方法。

    public final class $Proxy0 extends Proxy implements 被代理的Interface
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
    

    Cglib增强

    使用流程

    public class CglibTest {
    
        @Test
        public void test1() {
            //使用Enhancer来给某个类创建代理类,步骤
            //1.创建Enhancer对象
            Enhancer enhancer = new Enhancer();
            //2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
            enhancer.setSuperclass(Service1.class);
            /*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
            此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,
            也是一个接口,实现了Callback接口,
            当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
            enhancer.setCallback(new MethodInterceptor() {
                /**
                 * 代理对象方法拦截器
                 * @param o 代理对象
                 * @param method 被代理的类的方法,即Service1中的方法
                 * @param objects 调用方法传递的参数
                 * @param methodProxy 方法代理对象
                 * @return
                 * @throws Throwable
                 */
                @Override
                public Object intercept(Object o, Method method, Object[] objects, 
                            MethodProxy methodProxy) throws Throwable {
                    System.out.println("调用方法:" + method);
                    //可以调用MethodProxy的invokeSuper调用被代理类的方法
                    Object result = methodProxy.invokeSuper(o, objects);
                    return result;
                }
            });
            //4.获取代理对象,调用enhancer.create方法获取代理对象,
            //这个方法返回的是Object类型的,所以需要强转一下
            Service1 proxy = (Service1) enhancer.create();
            //5.调用代理对象的方法
            proxy.m1();
            proxy.m2();
        }
    }
    
    

    2.CGLIB原理

    CGLIB动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。主要工作流程分为4个步骤:

    • 首先生成代理对象。【创建增强类enhancer,设置代理类的父类,设置回调拦截方法,返回创建的代理对象】

    • 调用代理类中的方法。【这里调用的代理类中的方法实际上是重写的父类的拦截。重写的方法中会去调用intercept方法】

    • 调用intercept,方法中会对调用代理方法中的invokeSuper方法。我们去调用该方法的时候,在代理类中会先判断是否实现了方法拦截的接口,没实现的话直接调用目标类的方法;如果实现了那就会被方法拦截器拦截,在方法拦截器中会对目标类中所有的方法建立索引,其实大概就是将每个方法的引用保存在数组中,我们就可以根据数组的下标直接调用方法,而不是用反射;索引建立完成之后,方法拦截器内部就会调用invoke方法(这个方法在生成的FastClass中实现),在invoke方法内就是调用CGLIB这种方法,也就是调用对应的目标类的方法一;

    • 调用代理类中的代理方法,代理方法中通过super.method来实际真正的调用要执行的方法

    CGLIB创建代理的过程,相当于创建了一个新的类,可以通过CGLIB来配置这个新的类需要实现的接口,以及需要继承的父类

    CGLIB可以为类创建代理,但是这个类不能是final类型的,CGLIB为类创建代理的过程,实际上为通过继承来实现的,相当于给需要被代理的类创建了一个子类,然后会重写父类中的方法,来进行增强,继承的特性大家应该都知道,final修饰的类是不能被继承的,final修饰的方法不能被重写,static修饰的方法也不能被重写,private修饰的方法也不能被子类重写,而其他类型的方法都可以被子类重写,被重写的这些方法可以通过CGLIB进行拦截增强

    JDK和CGLIB的区别

    Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承);CGLIB非常强大,不管是接口还是类,都可以来创建代理。

    Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效

    生成类上,JDK动态代理只能对实现了接口的类生成代理,而不能针对类。CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法

    代理的就介绍到这里,spring中很多地方会用到这块,所以大家一定要熟悉,欢迎大家留言交流

    Spring AOP

    概念

    • Aspect(切面):通常是一个类,里面可以定义切入点和通知

    • JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用

    • Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around

    • Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式

    • AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

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

    最后

    如果你看到这里,觉得本文对你有帮助的话,不要吝啬的三连哦!

    提莫在这里也还整理了一些平时自己学习的技术文档与群友平时去面试的面试资料。
    如果看完后对你有帮助,记得点赞支持一下哦!

    Ps:有需要的小伙伴可以点点我,即可领取,免费获取。

    面试专题文档。

    在这里插入图片描述

    技术文档

    在这里插入图片描述

    真实大厂面经

    在这里插入图片描述

    相关文章

      网友评论

        本文标题:为什么Spring能这么牛逼!我们来掰扯看看。

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