Spring IoC

什么是IoC

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

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

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

IoC能做什么

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

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

IoC和DI

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

注:如果想要更加深入的了解IoC和DI,请参考大师级人物Martin Fowler的一篇经典文章《Inversion of Control Containers and the Dependency Injection pattern》,原文地址:http://www.martinfowler.com/articles/injection.html。

AOP:面向切面编程

什么是AOP:

  • 概念:在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。(百度百科)
  • 简单的说:就是将程序中重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

AOP的作用和优势:

  • 作用:从定义中来看,就是为了在程序运行期间,不修改源码对已有方法进行增强。

  • 优势:减少重复代码 提交了开发效率 维护方便

  • 实现方式: 就是动态代理的技术

  • 具体的作用:实现事务的控制 日志 和 安全模块

动态代理:

  • 实现动态代理的两种常用的方式:

    • 基于接口的动态代理:

      • jdk 官方的Proxy类

      • 要求:被代理的类至少实现一个接口

      • 实现:基于接口的动态代理

        接口名 新对象名 = (接口名)Proxy.newProxyInstance(
         	//表示的是被代理对象使用相同的类加载器
            被代理的对象.getClass().getClassLoader(),	// 被代理对象的类加载器,固定写法 
           //和被代理对象具有相同的行为。实现相同的接口
            被代理的对象.getClass().getInterfaces(),	// 被代理对象实现的所有接口,固定写法
            new InvocationHandler() {	// 匿名内部类,通过拦截被代理对象的方法来增强被代理对象
                /* 被代理对象的任何方法执行时,都会被此方法拦截到
                	其参数如下:
                        proxy: 代理对象的引用,不一定每次都用得到
                        method: 被拦截到的方法对象
                        args: 被拦截到的方法对象的参数
                	返回值:
                		被增强后的返回值
        		*/
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if("方法名".equals(method.getName())) {
                    	// 增强方法的操作
                        rtValue = method.invoke(被代理的对象, args);
                        // 增强方法的操作
                        return rtValue;
                    }          
                }
            });
        
    • 基于子类的动态代理:

      • 第三方的CGlib (如果想要使用的话 需要导入依赖 asm.jar)

      • 要求:被代理类不能用 final 修饰的类(最终类)

      • 实现:

        Actor cglibActor = (Actor) Enhancer.create(actor.getClass(),
        	new MethodInterceptor() {
        	/**
        	* 执行被代理对象的任何方法,都会经过该方法。在此方法内部就可以对被代理对象的任何
        	方法进行增强。
        	* 
        	* 参数:
        	* 前三个和基于接口的动态代理是一样的。
        	* MethodProxy:当前执行方法的代理对象。
        	* 返回值:
        	* 当前执行方法的返回值
        	*/
        	@Override
        	public Object intercept(Object proxy, Method method, Object[] args, 
        	MethodProxy methodProxy) throws Throwable {// 注意的是这里不能使用Exception
        		// 进行方法的增强的代码
        		Object invoke = null;
                    System.out.println("teacher ma  qi fei le  ");
                    if("teach".equals(method.getName())){
                        invoke = method.invoke(teacherDao, objects);
                    }
                    System.out.println("wuhu wuhu wuhu wuhu wuhu wuhu");
                    System.out.println("teacher ma shang dang le ");
                    return invoke;
        	}
        
  • JDK和CGLIB动态代理原理:

    • JDK动态代理: 利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
    • CGLIB动态代理:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理(所以 滥用CGLIB动态代理会导致方法区内存泄漏)
  • 何时使用JDK还是CGLIB?

    • 如果目标对象实现了接口默认情况下会采用JDK的动态代理实现AOP(默认使用JDK代理)
    • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
    • 如果目标对象没有实现了接口必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换
    • 如何强制使用CGLIB实现AOP?
      • 添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
      • 在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
  • JDK动态代理和CGLIB字节码生成的区别?

    • JDK动态代理只能对实现了接口的类生成代理,而不能针对类。在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐
    • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的 同样的 static 方法也是不能实现代理的 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类
  • AOP相关术语:

    • Joinpoint(连接点): 被拦截到的点,在Spring中指的是方法,且Spring中只支持方法类型的连接点.
    • Pointcut(切入点): 我们对其进行增强的方法.
    • Advice(通知/增强): 对切入点进行的增强操作包括前置通知,后置通知,异常通知,最终通知,环绕通知 五种
    • Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 属性
    • Target(目标对象):代理的目标对象
    • Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程
      • 目标对象的生命周期里有多个点可以进行织入:
        • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译 器。AspectJ的织入编译器就是以这种方式织入切面的
        • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特 殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-time weaving,LTW)就支持以这种方式织入切面
        • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织 入切面时,AOP容器会为目标对象动态地创建一个代理对象。 Spring AOP就是以这种方式织入切面的
    • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
    • Aspect(切面): 是切入点和通知的结合
  • Spring 中代理的选择:Spring中会通过是否是实现了接口来进行那种动态代理方式的选择

  • Spring提供了4种类型的AOP支持: 基于代理的经典Spring AOP(这种方式现在比较的笨重不再详解)纯POJO切面; @AspectJ注解驱动的切面; 注入式AspectJ切面(适用于Spring各版本)前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础 之上,因此,Spring对AOP的支持局限于方法拦截
    20200204101128411

Logo

Authing 是一款以开发者为中心的全场景身份云产品,集成了所有主流身份认证协议,为企业和开发者提供完善安全的用户认证和访问管理服务

更多推荐