精华内容
下载资源
问答
  • JSONObject相关依赖架包

    2017-12-14 18:59:27
    JSONObject相关依赖架包,包含1、com.alibaba.fastjson.JSONArray依赖jar 2、net.sf.json.JSONObject依赖jar
  • MyBatis 之 各种依赖

    热门讨论 2017-02-01 21:39:22
    本资源包括MyBatis框架下的各种依赖包,通过本资源,可以快速搭建MyBatis框架。
  • WPF经典教程之依赖项属性和路由事件

    千次下载 热门讨论 2014-10-05 01:10:07
    本概述介绍 WPF 属性系统以及依赖项属性的功能,这包括如何在可扩展应用程序标记语言 (XAML) 中和代码中使用现有的依赖项属性。..... (更多资源:http://cleopard.download.csdn.net/) (福利:...
  • 面试必杀技,讲一讲Spring中的循环依赖

    万次阅读 多人点赞 2020-07-06 17:59:24
    为了测试循环依赖的解决情况跟注入方式的关系,我们做如下四种情况的测试 依赖情况 依赖注入方式 循环依赖是否被解决 AB相互依赖(循环依赖) 均采用setter方法注入 是 AB相互依赖(循环依赖) 均采用构造器注入 否...

    本系列文章:

    听说你还没学Spring就被源码编译劝退了?30+张图带你玩转Spring编译

    读源码,我们可以从第一行读起

    你知道Spring是怎么解析配置类的吗?

    配置类为什么要添加@Configuration注解?

    谈谈Spring中的对象跟Bean,你知道Spring怎么创建对象的吗?

    这篇文章,我们来谈一谈Spring中的属性注入

    Spring中AOP相关的API及源码解析,原来AOP是这样子的

    你知道Spring是怎么将AOP应用到Bean的生命周期中的吗?

    推荐阅读:

    Spring官网阅读 | 总结篇

    Spring杂谈

    本系列文章将会带你一行行的将Spring的源码吃透,推荐阅读的文章是阅读源码的基础!

    前言

    Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃不掉。如果你回答得好,那么这就是你的必杀技,反正,那就是面试官的必杀技,这也是取这个标题的原因,当然,本文的目的是为了让你在之后的所有面试中能多一个必杀技,专门用来绝杀面试官!

    本文的核心思想就是,

    当面试官问:

    “请讲一讲Spring中的循环依赖。”的时候,

    我们到底该怎么回答?

    主要分下面几点

    1. 什么是循环依赖?
    2. 什么情况下循环依赖可以被处理?
    3. Spring是如何解决的循环依赖?

    同时本文希望纠正几个目前业界内经常出现的几个关于循环依赖的错误的说法

    1. 只有在setter方式注入的情况下,循环依赖才能解决(
    2. 三级缓存的目的是为了提高效率(

    OK,铺垫已经做完了,接下来我们开始正文

    什么是循环依赖?

    从字面上来理解就是A依赖B的同时B也依赖了A,就像下面这样

    image-20200705175322521

    体现到代码层次就是这个样子

    @Component
    public class A {
        // A中注入了B
    	@Autowired
    	private B b;
    }
    
    @Component
    public class B {
        // B中也注入了A
    	@Autowired
    	private A a;
    }
    

    当然,这是最常见的一种循环依赖,比较特殊的还有

    // 自己依赖自己
    @Component
    public class A {
        // A中注入了A
    	@Autowired
    	private A a;
    }
    

    虽然体现形式不一样,但是实际上都是同一个问题----->循环依赖

    什么情况下循环依赖可以被处理?

    在回答这个问题之前首先要明确一点,Spring解决循环依赖是有前置条件的

    1. 出现循环依赖的Bean必须要是单例
    2. 依赖注入的方式不能全是构造器注入的方式(很多博客上说,只能解决setter方法的循环依赖,这是错误的)

    其中第一点应该很好理解,第二点:不能全是构造器注入是什么意思呢?我们还是用代码说话

    @Component
    public class A {
    //	@Autowired
    //	private B b;
    	public A(B b) {
    
    	}
    }
    
    
    @Component
    public class B {
    
    //	@Autowired
    //	private A a;
    
    	public B(A a){
    
    	}
    }
    

    在上面的例子中,A中注入B的方式是通过构造器,B中注入A的方式也是通过构造器,这个时候循环依赖是无法被解决,如果你的项目中有两个这样相互依赖的Bean,在启动时就会报出以下错误:

    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
    

    为了测试循环依赖的解决情况跟注入方式的关系,我们做如下四种情况的测试

    依赖情况依赖注入方式循环依赖是否被解决
    AB相互依赖(循环依赖)均采用setter方法注入
    AB相互依赖(循环依赖)均采用构造器注入
    AB相互依赖(循环依赖)A中注入B的方式为setter方法,B中注入A的方式为构造器
    AB相互依赖(循环依赖)B中注入A的方式为setter方法,A中注入B的方式为构造器

    具体的测试代码跟简单,我就不放了。从上面的测试结果我们可以看到,不是只有在setter方法注入的情况下循环依赖才能被解决,即使存在构造器注入的场景下,循环依赖依然被可以被正常处理掉。

    那么到底是为什么呢?Spring到底是怎么处理的循环依赖呢?不要急,我们接着往下看

    Spring是如何解决的循环依赖?

    关于循环依赖的解决方式应该要分两种情况来讨论

    1. 简单的循环依赖(没有AOP)
    2. 结合了AOP的循环依赖

    简单的循环依赖(没有AOP)

    我们先来分析一个最简单的例子,就是上面提到的那个demo

    @Component
    public class A {
        // A中注入了B
    	@Autowired
    	private B b;
    }
    
    @Component
    public class B {
        // B中也注入了A
    	@Autowired
    	private A a;
    }
    

    通过上文我们已经知道了这种情况下的循环依赖是能够被解决的,那么具体的流程是什么呢?我们一步步分析

    首先,我们要知道Spring在创建Bean的时候默认是按照自然排序来进行创建的,所以第一步Spring会去创建A

    与此同时,我们应该知道,Spring在创建Bean的过程中分为三步

    1. 实例化,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法

    2. 属性注入,对应方法:AbstractAutowireCapableBeanFactorypopulateBean方法

    3. 初始化,对应方法:AbstractAutowireCapableBeanFactoryinitializeBean

    这些方法在之前源码分析的文章中都做过详细的解读了,如果你之前没看过我的文章,那么你只需要知道

    1. 实例化,简单理解就是new了一个对象
    2. 属性注入,为实例化中new出来的对象填充属性
    3. 初始化,执行aware接口中的方法,初始化方法,完成AOP代理

    基于上面的知识,我们开始解读整个循环依赖处理的过程,整个流程应该是以A的创建为起点,前文也说了,第一步就是创建A嘛!

    image-20200706092738559

    创建A的过程实际上就是调用getBean方法,这个方法有两层含义

    1. 创建一个新的Bean
    2. 从缓存中获取到已经被创建的对象

    我们现在分析的是第一层含义,因为这个时候缓存中还没有A嘛!

    调用getSingleton(beanName)

    首先调用getSingleton(a)方法,这个方法又会调用getSingleton(beanName, true),在上图中我省略了这一步

    public Object getSingleton(String beanName) {
        return getSingleton(beanName, true);
    }
    

    getSingleton(beanName, true)这个方法实际上就是到缓存中尝试去获取Bean,整个缓存分为三级

    1. singletonObjects,一级缓存,存储的是所有创建好了的单例Bean
    2. earlySingletonObjects,完成实例化,但是还未进行属性注入及初始化的对象
    3. singletonFactories,提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象

    因为A是第一次被创建,所以不管哪个缓存中必然都是没有的,因此会进入getSingleton的另外一个重载方法getSingleton(beanName, singletonFactory)

    调用getSingleton(beanName, singletonFactory)

    这个方法就是用来创建Bean的,其源码如下:

    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
    
                // ....
                // 省略异常处理及日志
                // ....
    
                // 在单例对象创建前先做一个标记
                // 将beanName放入到singletonsCurrentlyInCreation这个集合中
                // 标志着这个单例Bean正在创建
                // 如果同一个单例Bean多次被创建,这里会抛出异常
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = new LinkedHashSet<>();
                }
                try {
                    // 上游传入的lambda在这里会被执行,调用createBean方法创建一个Bean后返回
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                // ...
                // 省略catch异常处理
                // ...
                finally {
                    if (recordSuppressedExceptions) {
                        this.suppressedExceptions = null;
                    }
                    // 创建完成后将对应的beanName从singletonsCurrentlyInCreation移除
                    afterSingletonCreation(beanName);
                }
                if (newSingleton) {
                    // 添加到一级缓存singletonObjects中
                    addSingleton(beanName, singletonObject);
                }
            }
            return singletonObject;
        }
    }
    

    上面的代码我们主要抓住一点,通过createBean方法返回的Bean最终被放到了一级缓存,也就是单例池中。

    那么到这里我们可以得出一个结论:一级缓存中存储的是已经完全创建好了的单例Bean

    调用addSingletonFactory方法

    如下图所示:

    image-20200706105535307

    在完成Bean的实例化后,属性注入之前Spring将Bean包装成一个工厂添加进了三级缓存中,对应源码如下:

    // 这里传入的参数也是一个lambda表达式,() -> getEarlyBeanReference(beanName, mbd, bean)
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
            if (!this.singletonObjects.containsKey(beanName)) {
                // 添加到三级缓存中
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }
    

    这里只是添加了一个工厂,通过这个工厂(ObjectFactory)的getObject方法可以得到一个对象,而这个对象实际上就是通过getEarlyBeanReference这个方法创建的。那么,什么时候会去调用这个工厂的getObject方法呢?这个时候就要到创建B的流程了。

    当A完成了实例化并添加进了三级缓存后,就要开始为A进行属性注入了,在注入时发现A依赖了B,那么这个时候Spring又会去getBean(b),然后反射调用setter方法完成属性注入。

    image-20200706114501300

    因为B需要注入A,所以在创建B的时候,又会去调用getBean(a),这个时候就又回到之前的流程了,但是不同的是,之前的getBean是为了创建Bean,而此时再调用getBean不是为了创建了,而是要从缓存中获取,因为之前A在实例化后已经将其放入了三级缓存singletonFactories中,所以此时getBean(a)的流程就是这样子了

    image-20200706115959250

    从这里我们可以看出,注入到B中的A是通过getEarlyBeanReference方法提前暴露出去的一个对象,还不是一个完整的Bean,那么getEarlyBeanReference到底干了啥了,我们看下它的源码

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }
    

    它实际上就是调用了后置处理器的getEarlyBeanReference,而真正实现了这个方法的后置处理器只有一个,就是通过@EnableAspectJAutoProxy注解导入的AnnotationAwareAspectJAutoProxyCreator也就是说如果在不考虑AOP的情况下,上面的代码等价于:

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        return exposedObject;
    }
    

    也就是说这个工厂啥都没干,直接将实例化阶段创建的对象返回了!所以说在不考虑AOP的情况下三级缓存有用嘛?讲道理,真的没什么用,我直接将这个对象放到二级缓存中不是一点问题都没有吗?如果你说它提高了效率,那你告诉我提高的效率在哪?

    image-20200706124118108

    那么三级缓存到底有什么作用呢?不要急,我们先把整个流程走完,在下文结合AOP分析循环依赖的时候你就能体会到三级缓存的作用!

    到这里不知道小伙伴们会不会有疑问,B中提前注入了一个没有经过初始化的A类型对象不会有问题吗?

    答:不会

    这个时候我们需要将整个创建A这个Bean的流程走完,如下图:

    image-20200706133018669

    从上图中我们可以看到,虽然在创建B时会提前给B注入了一个还未初始化的A对象,但是在创建A的流程中一直使用的是注入到B中的A对象的引用,之后会根据这个引用对A进行初始化,所以这是没有问题的。

    结合了AOP的循环依赖

    之前我们已经说过了,在普通的循环依赖的情况下,三级缓存没有任何作用。三级缓存实际上跟Spring中的AOP相关,我们再来看一看getEarlyBeanReference的代码:

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }
    

    如果在开启AOP的情况下,那么就是调用到AnnotationAwareAspectJAutoProxyCreatorgetEarlyBeanReference方法,对应的源码如下:

    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        // 如果需要代理,返回一个代理对象,不需要代理,直接返回当前传入的这个bean对象
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
    

    回到上面的例子,我们对A进行了AOP代理的话,那么此时getEarlyBeanReference将返回一个代理后的对象,而不是实例化阶段创建的对象,这样就意味着B中注入的A将是一个代理对象而不是A的实例化阶段创建后的对象。image-20200706161709829

    看到这个图你可能会产生下面这些疑问

    1. 在给B注入的时候为什么要注入一个代理对象?

    答:当我们对A进行了AOP代理时,说明我们希望从容器中获取到的就是A代理后的对象而不是A本身,因此把A当作依赖进行注入时也要注入它的代理对象

    1. 明明初始化的时候是A对象,那么Spring是在哪里将代理对象放入到容器中的呢?

    image-20200706160542584

    在完成初始化后,Spring又调用了一次getSingleton方法,这一次传入的参数又不一样了,false可以理解为禁用三级缓存,前面图中已经提到过了,在为B中注入A时已经将三级缓存中的工厂取出,并从工厂中获取到了一个对象放入到了二级缓存中,所以这里的这个getSingleton方法做的时间就是从二级缓存中获取到这个代理后的A对象。exposedObject == bean可以认为是必定成立的,除非你非要在初始化阶段的后置处理器中替换掉正常流程中的Bean,例如增加一个后置处理器:

    @Component
    public class MyPostProcessor implements BeanPostProcessor {
    	@Override
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		if (beanName.equals("a")) {
    			return new A();
    		}
    		return bean;
    	}
    }
    

    不过,请不要做这种骚操作,徒增烦恼!

    1. 初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗?

    答:不会,这是因为不管是cglib代理还是jdk动态代理生成的代理类,内部都持有一个目标类的引用,当调用代理对象的方法时,实际会去调用目标对象的方法,A完成初始化相当于代理对象自身也完成了初始化

    1. 三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个引用不行吗?

    答:这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象

    我们思考一种简单的情况,就以单独创建A为例,假设AB之间现在没有依赖关系,但是A被代理了,这个时候当A完成实例化后还是会进入下面这段代码:

    // A是单例的,mbd.isSingleton()条件满足
    // allowCircularReferences:这个变量代表是否允许循环依赖,默认是开启的,条件也满足
    // isSingletonCurrentlyInCreation:正在在创建A,也满足
    // 所以earlySingletonExposure=true
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    // 还是会进入到这段代码中
    if (earlySingletonExposure) {
    	// 还是会通过三级缓存提前暴露一个工厂对象
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    

    看到了吧,即使没有循环依赖,也会将其添加到三级缓存中,而且是不得不添加到三级缓存中,因为到目前为止Spring也不能确定这个Bean有没有跟别的Bean出现循环依赖。

    假设我们在这里直接使用二级缓存的话,那么意味着所有的Bean在这一步都要完成AOP代理。这样做有必要吗?

    不仅没有必要,而且违背了Spring在结合AOP跟Bean的生命周期的设计!Spring结合AOP跟Bean的生命周期本身就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

    三级缓存真的提高了效率了吗?

    现在我们已经知道了三级缓存的真正作用,但是这个答案可能还无法说服你,所以我们再最后总结分析一波,三级缓存真的提高了效率了吗?分为两点讨论:

    1. 没有进行AOP的Bean间的循环依赖

    从上文分析可以看出,这种情况下三级缓存根本没用!所以不会存在什么提高了效率的说法

    1. 进行了AOP的Bean间的循环依赖

    就以我们上的A、B为例,其中A被AOP代理,我们先分析下使用了三级缓存的情况下,A、B的创建流程

    image-20200706171514327

    假设不使用三级缓存,直接在二级缓存中

    image-20200706172523258

    上面两个流程的唯一区别在于为A对象创建代理的时机不同,在使用了三级缓存的情况下为A创建代理的时机是在B中需要注入A的时候,而不使用三级缓存的话在A实例化后就需要马上为A创建代理然后放入到二级缓存中去。对于整个A、B的创建过程而言,消耗的时间是一样的

    综上,不管是哪种情况,三级缓存提高了效率这种说法都是错误的!

    总结

    面试官:”Spring是如何解决的循环依赖?“

    答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

    面试官:”为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?“

    答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

    一道思考题

    为什么在下表中的第三种情况的循环依赖能被解决,而第四种情况不能被解决呢?

    提示:Spring在创建Bean时默认会根据自然排序进行创建,所以A会先于B进行创建

    依赖情况依赖注入方式循环依赖是否被解决
    AB相互依赖(循环依赖)均采用setter方法注入
    AB相互依赖(循环依赖)均采用构造器注入
    AB相互依赖(循环依赖)A中注入B的方式为setter方法,B中注入A的方式为构造器
    AB相互依赖(循环依赖)B中注入A的方式为setter方法,A中注入B的方式为构造器

    小伙伴们有任何问题都可以给我留言,扫描左侧二维码可以关注我的公众号哦~
    码字不易,本文要是对你有帮助的话,记得点个赞吧!

    展开全文
  • pagehelper插件及依赖jar

    2016-12-15 16:30:42
    pagehelper插件及依赖jar jsqlparser-0.9.5.jar pagehelper-4.2.1.jar
  • Spring MVC框架的各种依赖包下载

    热门讨论 2017-01-24 01:51:20
    本资源包括Spring MVC框架下的各种依赖包,同过本资源,可以快速搭建Spring MVC框架。
  • kafka的java依赖

    千次下载 热门讨论 2015-10-18 21:58:29
    kafka的java依赖包,包含kafka本地调用的所有jar包
  • shiro-1.2.3所有依赖包以及源码

    千次下载 热门讨论 2015-09-30 16:06:16
    apache shiro 所有依赖包以及源码。
  • Maven 依赖范围和依赖传递详解

    千次阅读 2018-11-08 18:38:36
    Maven 依赖范围和依赖传递详解 18.10.8 松江图书馆 Maven 依赖范围和依赖传递详解 依赖基本配置 依赖范围 依赖传递 maven默认依赖体制 第一原则:以短路径长度为准 第二原则:相同路径长度时...

    Maven 依赖范围和依赖传递详解

    18.10.8 松江图书馆

    依赖基本配置

    在pom.xml文件 根元素project下的 dependencies标签中,配置依赖信息,内可以包含多个 dependence元素,以声明多个依赖。每个依赖dependence标签都应该包含以下元素:

    1. groupId, artifactId, version : 依赖的基本坐标, 对于任何一个依赖来说,基本坐标是最重要的, Maven根据坐标才能找到需要的依赖。

    这3个属性,可以理解成一个3维的x-y-z坐标,可以通过3个值,来确定一个库中维一个依赖

    gooupId 可以理解成一个java的包名(java包名中默认以公司域名倒写),在对应的.m2仓库,它是一个可以有很多层的目录,以. 来进行目录分级

    artifactID 可以理解是项目或模块的名称

    version 表示不同的版本号

    1. type: 依赖的类型,大部分情况下不需要声明。 默认值为jar

    2. Scope: 依赖范围(compile,test,provided,runtime,system 五种状态)

    3. Optional:标记依赖是否可选

    4. exclusions: 用来排除传递性依赖 其中可配置多个exclusion标签,每个exclusion标签里面对应的有groupId, artifactId, version三项基本元素

    example Code:

       <project>   
    
       
           <groupId>groupA</groupId>  
           <artifactId>artifactA</artifactId>  
           <version>1.0</version> 
           <type>jar<type>
           <scope>complie<scope> 
        
           <dependencies>  
                  <dependency>  
                         <groupId>groupC</groupId>  
                         <artifactId>artifactC</artifactId>  
                         <version>1.0</version>  
                  </dependency>  
                  <dependency>  
                         <groupId>groupD</groupId>  
                         <artifactId>artifactD</artifactId>  
                         <version>1.0</version>  
                  </dependency>  
    
                         <exclusions> 
                                 <exclusion> 
                                 <groupId>T</groupId> 
                                 <artifactId>jartifactD</artifactId> 
                                 </exclusion> 
     
                                 <exclusion> 
                                <groupId>TS</groupId> 
                                <artifactId>asm</artifactId> 
                                </exclusion> 
    
                          </exclusions>
              
                </dependencies>  
             
        </project> 
    

    依赖范围

    1. compile: 编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效

    2. system: 系统依赖范围。该依赖与编译、测试、运行三种classpath的关系,和provided依赖范围完全一致。但是,使用system范围依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用。

    example code:

    
        <dependencies>
        <dependency>
          <groupId>sun.jdk</groupId>
          <artifactId>tools</artifactId>
          <version>1.5.0</version>
          <scope>system</scope>
          <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>
          </dependencies>
    
    1. provided: 已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍(如:servlet-api)。

    2. runtime: 运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。

    3. test: 测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。典型的例子就是JUnit
      ,它只有在编译测试代码及运行测试的时候才需要。

    依赖传递

    maven默认依赖体制

    maven引入的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。但有时候,当传递性依赖造成问题的时候,我们就需要清楚地知道该传递性依赖是从哪条依赖路径引入的。

    maven对于依赖相同的资源,默认会做出以下优化:

    第一原则:以短路径长度为准

    例如,项目A有这样的依赖关系 : A–>B–>C–>X(1.0)、A–>D–>X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版 本的X,那么哪个X会被maven解析使用呢?两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。maven依赖调解的第一原则:路径最近者优先。该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。

    第二原则:相同路径长度时,以pom中声明顺序先者为准

    依赖调解第一原则不能解决所有问题,比如这样的依赖关系:A–>B–>Y(1.0),A–>C–>Y(2.0),Y(1.0)和Y(2.0)的依赖路径长度是一样的,都为2。那么到底谁会被解析使用呢?在maven2.0.8及之前的版本中,这是不确定的,但是maven2.0.9开始,为了尽可能避免构建的不确定性,maven定义了依赖调解的第二原则:第一声明者优先。在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用。顺序最靠前的那个依赖优胜。

    2020.1.14 修改
    关于第二条原则,有些歧义

    对于在同一个pom文件中,如果引入同一jar包,但不同版本时,后者会覆盖前者,则以后者为准. (当然这种情况一般不会出现)

    另外,还有一种解决冲突的办法: 版本锁定原则.

    版本锁定原则

    面对众多的依赖,版本锁定这一种方式不需要考虑依赖的路径、声明优化等因素,可以直接锁定所依赖jar包的版本,锁定后不会考虑声明顺序及路径。
    主要是使用dependencyManageme’标签来是实现

    下面以锁定Struts2、Spring、Hibernate版本为例:

           <properties>
    		<spring.version>4.2.4.RELEASE</spring.version>
    		<hibernate.version>5.0.7.Final</hibernate.version>
    		<struts.version>2.3.24</struts.version>
    	</properties>
     
    	<!-- 锁定版本,struts2-2.3.24、spring4.2.4、hibernate5.0.7 -->
    	<dependencyManagement>
    		<dependencies>
    		    <dependency>
    			    <groupId>org.apache.struts</groupId>
    			    <artifactId>struts2-core</artifactId>
    			    <version>${struts.version}</version>
    		    </dependency>
    			<dependency>
    				<groupId>org.springframework</groupId>
    				<artifactId>spring-context</artifactId>
    				<version>${spring.version}</version>
    			</dependency>
    		    <dependency>
    			    <groupId>org.hibernate</groupId>
    			    <artifactId>hibernate-core</artifactId>
    			    <version>${hibernate.version}</version>
    		    </dependency>
            </dependencies>
        </dependencyManagement>
    

    注意:在工程中锁定依赖的版本并不代表在工程中添加了依赖, 如果工程需要添加锁定版本的依赖则需要单独添加标签,依然需要使用dependencies标签来引入依赖,只是不必再指定版本号.

    <dependencies>
        		    <dependency>
        			    <groupId>org.apache.struts</groupId>
        			    <artifactId>struts2-core</artifactId>
        			    <version>${struts.version}</version>
        		    </dependency>
    </dependencise>
    

    maven pom中配置 exclusion标签 手动去除依赖

    场景: 比如,A 和 B 使用需都需要引用c jar包,A引用c的版本为1.0,B引用C的版本为2.0
    原则上,我们使用最新版本作为支持,按照maven依赖系统默认原则,其实会引用的是C的1.0版本,此时我可以用exclusion来排除依赖

    <dependencies>  
    <dependency>  
    <groupId>A</groupId>  
    <artifactId>A</artifactId>  
    <version>xxx</version>  
    <exclusions>  
    <exclusion>  
    <groupId>C</groupId>  
    <artifactId>C</artifactId> 
    </exclusion>  
    </exclusions>  
    </dependency> 
     
    <dependency>  
    <groupId>B</groupId>  
    <artifactId>B</artifactId>  
    </dependency>  
    </dependencies>  
    

    dependencyManagement和dependencise的区别

    dependencyManagement标签和denpendencise标签和可作为同级标签,也可作为上下级标签;

    同级:dependencyManagement标签功能在于“申明”依赖,denpendencise标签在于“申明和使用”依赖

    上下级: 因为dependencyManagement 申明一个依赖需要借助denpendencise标签,此时denpendencise标签为子标签

    关于“申明”和“申明和使用”依赖的解释:

    dependencyManagement标签申明依赖,主要功能为是版本version控制;

    尤其在多模构建项目时,在统一的父模块中(父模块配置为<packaging>pom</packaging>),通过dependencyManagement标签,申明依赖,确认了依赖的版本version信息,子模块只需要通过denpendencise标签来引入依赖,不用指定版本,默认用的就是父类版本;
    此时要注意: 子项目依赖父类没有申明的依赖,一定要加版本号,此时子类会仓库自行找对应的依赖信息;子项目依赖父类申明的依赖,version版本和父类不同,此时子类也会在仓库中找对应自己的依赖信息;


    在这里插入图片描述

    展开全文
  • json-lib-2.4和相应的依赖

    千次下载 热门讨论 2016-05-10 00:28:37
    json-2.4所依赖的jar包:commons-beanutils-1.8.0.jar,commons-collections-3.2.1.jar,commons-lang-2.5.jar,commons-logging-1.1.1.jar,ezmorph-1.0.6.jar;以及和struts2整合所需的jar包:struts2-json-plugin...
  • gradle引入依赖:_Gradle入门:依赖管理

    千次阅读 2020-06-16 05:45:52
    gradle引入依赖: 即使不是没有可能,创建没有任何外部依赖关系的现实应用程序也是一项挑战。 这就是为什么依赖性管理是每个软件项目中至关重要的部分的原因。 这篇博客文章描述了我们如何使用Gradle管理项目的...

    gradle引入依赖:

    即使不是没有可能,创建没有任何外部依赖关系的现实应用程序也是一项挑战。 这就是为什么依赖性管理是每个软件项目中至关重要的部分的原因。

    这篇博客文章描述了我们如何使用Gradle管理项目的依赖关系。 我们将学习配置已使用的存储库和所需的依赖项。 我们还将通过实现一个简单的示例应用程序将此理论应用于实践。

    让我们开始吧。

    补充阅读:

    储存库管理简介

    存储库实质上是依赖项容器,每个项目可以使用零个或多个存储库。

    Gradle支持以下存储库格式:

    让我们了解如何在构建中配置每种存储库类型。

    将常春藤存储库添加到我们的构建中

    我们可以使用它的url地址或它在本地文件系统中的位置将Ivy存储库添加到我们的构建中。

    如果我们想使用其URL地址添加一个Ivy存储库,则必须将以下代码片段添加到build.gradle文件中:

    repositories {
        ivy {
            url "http://ivy.petrikainulainen.net/repo"
        }
    }

    如果要使用文件系统中的位置添加常春藤存储库,则必须将以下代码片段添加到build.gradle文件中:

    repositories {
        ivy {       
            url "../ivy-repo"
        }
    }

    如果要获取有关配置Ivy存储库的更多信息,则应检查以下资源:

    让我们继续前进,了解如何将Maven存储库添加到我们的构建中。

    将Maven存储库添加到我们的构建中

    我们可以使用其URL地址或在本地文件系统中的位置将Maven存储库添加到我们的构建中。

    如果要使用其URL添加Maven存储库,则必须将以下代码片段添加到build.gradle文件中:

    repositories {
        maven {
            url "http://maven.petrikainulainen.net/repo"
        }
    }

    如果要使用文件系统中的Maven存储库来添加Maven存储库,则必须将以下代码段添加到build.gradle文件中:

    repositories {
        maven {       
            url "../maven-repo"
        }
    }

    在将Maven存储库添加到构建中时,Gradle可以使用三个“别名”。 这些别名是:

    如果要在构建中添加中央Maven 2存储库,则必须将以下代码段添加到build.gradle文件中:

    repositories {
        mavenCentral()
    }

    如果要获取有关配置Maven存储库的更多信息,则应查阅Gradle用户指南的第50.6.4节“ Maven存储库”

    让我们继续前进,了解如何向构建中添加平面目录存储库。

    将平面目录存储库添加到我们的版本中

    如果要使用平面目录存储库,则必须将以下代码片段添加到build.gradle文件中:

    repositories {
        flatDir {
            dirs 'lib'
        }
    }

    这意味着从lib目录中搜索依赖项。 另外,如果需要,可以通过将以下代码段添加到build.gradle文件中来使用多个目录:

    repositories {
        flatDir {
            dirs 'libA', 'libB'
        }
    }

    如果要获取有关平面目录存储库的更多信息,则应检查以下资源:

    让我们继续前进,了解如何使用Gradle管理项目的依赖关系。

    依赖管理简介

    配置项目的存储库后,我们可以声明其依赖项。 如果我们要声明一个新的依赖关系,我们必须执行以下步骤:

    1. 指定依赖项的配置。
    2. 声明所需的依赖项。

    让我们仔细看看这些步骤。

    将依赖项分组到配置中

    在Gradle中,依赖项被分组为一组命名的依赖项。 这些组称为配置,我们使用它们来声明项目的外部依赖关系。

    Java插件指定了几种依赖项配置 ,下面对此进行了描述:

    • 当我们编译项目的源代码时,需要将添加到编译配置的依赖项。
    • 运行时配置包含运行时所需的依赖项。 此配置包含添加到编译配置的依赖项。
    • testCompile配置包含编译项目测试所需的依赖项。 此配置包含项目的已编译类以及添加到编译配置中的依赖项。
    • testRuntime配置包含运行我们的测试时所需的依赖项。 此配置包含添加到compileruntimetestCompile配置的依赖项。
    • 档案配置包含我们项目产生的工件(例如Jar文件)。
    • 默认配置组包含运行时所需的依赖项。

    让我们继续前进,找出如何声明Gradle项目的依赖项。

    声明项目的依存关系

    最常见的依赖项称为外部依赖项,可从外部存储库中找到。 通过使用以下属性来标识外部依赖项:

    • group属性标识依赖项的组(Maven用户将此属性称为groupId )。
    • name属性标识依赖项的名称(Maven用户将此属性称为artifactId )。
    • version属性指定外部依赖项的版本(Maven用户将此属性称为version )。

    使用Maven存储库时,这些属性是必需的。 如果使用其他存储库,则某些属性可能是可选的。

    例如,如果使用平面目录存储库,则可能必须仅指定name和version

    假设我们必须声明以下依赖关系:

    • 依赖项的组为“ foo”。
    • 依赖项的名称为“ foo”。
    • 依赖项的版本为0.1。
    • 编译我们的项目时需要依赖项。

    我们可以通过将以下代码片段添加到build.gradle文件中来声明此依赖性:

    dependencies {
    	compile group: 'foo', name: 'foo', version: '0.1'
    }

    我们还可以使用遵循以下语法的快捷方式形式声明项目的依赖项: [group]:[name]:[version] 。 如果要使用快捷方式表单,则必须将以下代码片段添加到build.gradle文件中:

    dependencies {
    	compile	'foo:foo:0.1'
    }

    我们还可以将多个依赖项添加到同一配置中。 如果要在声明依赖项时使用“常规”语法,则必须将以下代码片段添加到build.gradle文件中:

    dependencies {
    	compile (
    		[group: 'foo', name: 'foo', version: '0.1'],
    		[group: 'bar', name: 'bar', version: '0.1']
    	)
    }

    另一方面,如果我们要使用快捷方式表单, build.gradle文件的相关部分如下所示:

    dependencies {
    	compile 'foo:foo:0.1', 'bar:bar:0.1'
    }

    自然可以声明属于不同配置的依赖项。 例如,如果我们要声明属于compiletestCompile配置的依赖项,则必须向build.gradle文件中添加以下代码片段:

    dependencies {
    	compile group: 'foo', name: 'foo', version: '0.1'
    	testCompile group: 'test', name: 'test', version: '0.1'
    }

    同样,可以使用快捷方式表格。 如果要使用快捷方式表单声明相同的依赖项,则build.gradle文件的相关部分如下所示:

    dependencies {
    	compile 'foo:foo:0.1'
    	testCompile 'test:test:0.1'
    }

    您可以通过阅读50.4如何在Gradle用户指南中声明依赖项来获取有关声明依赖项的更多信息。

    现在,我们已经学习了依赖管理的基础知识。 让我们继续并实现示例应用程序。

    创建示例应用程序

    以下是我们的示例应用程序的要求:

    • 示例应用程序的构建脚本必须使用Maven中央存储库。
    • 示例应用程序必须使用Log4j将接收到的消息写入日志。
    • 示例应用程序必须包含单元测试,以确保返回正确的消息。 这些单元测试必须使用JUnit编写。
    • 我们的构建脚本必须创建一个可执行的jar文件。

    让我们找出如何满足这些要求。

    配置我们的版本库

    示例应用程序的要求之一是其构建脚本必须使用Maven中央存储库。 在配置好构建脚本以使用Maven中央存储库之后,其源代码如下所示(相关部分已突出显示):

    apply plugin: 'java'
    
    repositories {
        mavenCentral()
    }
    
    jar {
        manifest {
            attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld'
        }
    }

    让我们继续并声明示例应用程序的依赖项。

    声明示例应用程序的依赖关系

    我们必须在build.gradle文件中声明两个依赖

    1. Log4j(版本1.2.17)用于将接收到的消息写入日志。
    2. JUnit(4.11版)用于为示例应用程序编写单元测试。

    声明这些依赖关系后, build.gradle文件如下所示(相关部分突出显示):

    apply plugin: 'java'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        compile 'log4j:log4j:1.2.17'
        testCompile 'junit:junit:4.11'
    }
    
    jar {
        manifest {
            attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld'
        }
    }

    让我们继续写一些代码。

    编写代码

    为了满足示例应用程序的要求,“我们必须对其进行过度设计”。 我们可以按照以下步骤创建示例应用程序:

    1. 创建一个MessageService类,该类返回字符串“ Hello World!”。 调用其getMessage()方法时。
    2. 创建这确保了MessageServiceTest类的MessageService类返回字符串“世界,你好!”的getMessage()方法。
    3. 创建我们的应用程序的主类,该类从MessageService对象获取消息,并使用Log4j将消息写入日志。
    4. 配置Log4j。

    让我们一步一步地完成这些步骤。

    首先 ,我们必须在src / main / java / net / petrikainulainen / gradle目录中创建一个MessageService类并实现它。 完成此操作后,其源代码如下所示:

    package net.petrikainulainen.gradle;
    
    public class MessageService {
    
        public String getMessage() {
            return "Hello World!";
        }
    }

    其次 ,我们在src / main / test / net / petrikainulainen / gradle目录中创建了一个MessageServiceTest ,并将单元测试写入了MessageService类的getMessage()方法。 MessageServiceTest类的源代码如下所示:

    package net.petrikainulainen.gradle;
    
    import org.junit.Before;
    import org.junit.Test;
    
    import static org.junit.Assert.assertEquals;
    
    public class MessageServiceTest {
    
        private MessageService messageService;
    
        @Before
        public void setUp() {
            messageService = new MessageService();
        }
    
        @Test
        public void getMessage_ShouldReturnMessage() {
            assertEquals("Hello World!", messageService.getMessage());
        }
    }

    第三 ,我们在src / main / java / net / petrikainulainen / gradle目录中创建了一个HelloWorld类。 此类是我们应用程序的主要类别。 它从MessageService对象获取消息,然后使用Log4j将其写入日志。 HelloWorld类的源代码如下所示:

    package net.petrikainulainen.gradle;
    
    import org.apache.log4j.Logger;
    
    public class HelloWorld {
    
        private static final Logger LOGGER = Logger.getLogger(HelloWorld.class);
    
        public static void main(String[] args) {
            MessageService messageService = new MessageService();
    
            String message = messageService.getMessage();
            LOGGER.info("Received message: " + message);
        }
    }

    第四 ,我们必须使用从src / main / resources目录中找到的log4j.properties来配置Log4j。 log4j.properties文件如下所示:

    log4j.appender.Stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.Stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.Stdout.layout.conversionPattern=%-5p - %-26.26c{1} - %m\n
    
    log4j.rootLogger=DEBUG,Stdout

    这就对了。 让我们找出如何运行示例应用程序的测试。

    运行单元测试

    我们可以使用以下命令运行单元测试:

    gradle test

    测试通过后,我们将看到以下输出:

    > gradle test
    :compileJava
    :processResources
    :classes
    :compileTestJava
    :processTestResources 
    :testClasses
    :test
    
    BUILD SUCCESSFUL
    
    Total time: 4.678 secs

    但是,如果我们的单元测试失败,则会看到以下输出(突出显示了有趣的部分):

    > gradle test
    :compileJava
    :processResources
    :classes
    :compileTestJava
    :processTestResources
    :testClasses
    :test
    
    net.petrikainulainen.gradle.MessageServiceTest > getMessage_ShouldReturnMessageFAILED
        org.junit.ComparisonFailure at MessageServiceTest.java:22
    
    1 test completed, 1 failed
    :test FAILED
    
    FAILURE: Build failed with an exception.
    
    * What went wrong:
    Execution failed for task ':test'.
    > There were failing tests. See the report at: file:///Users/loke/Projects/Java/Blog/gradle-examples/dependency-management/build/reports/tests/index.html
    
    * Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
    
    BUILD FAILED
    
    Total time: 4.461 secs

    如我们所见,如果我们的单元测试失败,则描述:

    • 哪些测试失败。
    • 运行了多少测试,失败了多少测试。
    • 测试报告的位置,该报告提供有关失败(和通过)测试的其他信息。

    当我们运行单元测试时,Gradle将测试报告创建到以下目录:

    • build / test-results目录包含每个测试运行的原始数据。
    • build / reports / tests目录包含一个HTML报告,该报告描述了我们的测试结果。

    HTML测试报告是非常有用的工具,因为它描述了测试失败的原因 。 例如,如果我们的单元测试期望MessageService类的getMessage()方法返回字符串“ Hello Worl1d!”,则该测试用例HTML测试报告将如下所示:

    测试失败

    让我们继续前进,了解如何打包和运行示例应用程序。

    打包并运行我们的示例应用程序

    我们可以使用以下命令之一打包应用程序:em> gradle assembly或gradle build 。 这两个命令都会在build / libs目录中创建dependency-management.jar文件。

    通过使用命令java -jardependency-management.jar运行示例应用程序时,我们看到以下输出:

    > java -jar dependency-management.jar
     
    Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/log4j/Logger
    	at net.petrikainulainen.gradle.HelloWorld.<clinit>(HelloWorld.java:10)
    Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Logger
    	at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
    	at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    	at java.security.AccessController.doPrivileged(Native Method)
    	at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    	... 1 more

    发生此异常的原因是,当我们运行应用程序时,从类路径中找不到Log4j依赖项。

    解决此问题的最简单方法是创建一个所谓的“胖” jar文件。 这意味着我们会将所需的依赖项打包到创建的jar文件中。

    在遵循Gradle Cookbook中给出的说明之后,我们的构建脚本如下所示(相关部分已突出显示):

    apply plugin: 'java'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        compile 'log4j:log4j:1.2.17'
        testCompile 'junit:junit:4.11'
    }
    
    jar {
        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
        manifest {
            attributes 'Main-Class': 'net.petrikainulainen.gradle.HelloWorld'
        }
    }

    现在,我们可以运行示例应用程序(打包后),并且可以看到一切正常:

    > java -jar dependency-management.jar 
    INFO  - HelloWorld                 - Received message: Hello World!

    今天就这些。 让我们总结一下我们从此博客文章中学到的知识。

    摘要

    这篇博客文章教会了我们四件事:

    • 我们学习了如何配置构建所使用的存储库。
    • 我们了解了如何声明所需的依赖关系并将这些依赖关系分组为配置。
    • 我们了解到,Gradle在运行测试时会创建HTML测试报告。
    • 我们学习了如何创建一个所谓的“胖” jar文件。

    如果您想玩这个博客文章的示例应用程序,可以从Github获得

    翻译自: https://www.javacodegeeks.com/2014/07/getting-started-with-gradle-dependency-management.html

    gradle引入依赖:

    展开全文
  • Java 依赖注入标准(JSR-330,Dependency Injection for Java)1.0 规范已于2009年10 月份发布。该规范主要是面向依赖注入使用者,而对注入器实现、配置并未作详细要求。Spring、Guice 已经开始兼容该规范,JSR-299...

    IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序,而Dagger2框架是依赖注入思想践行者的优秀代表。

    依赖注入框架Dagger2详解(一),依赖注入和控制反转的深入理解
    依赖注入框架Dagger2详解(二),Java依赖注入标准JSR-330规范
    依赖注入框架Dagger2详解(三),Java注解处理器APT入门
    依赖注入框架Dagger2详解(四),初级篇
    依赖注入框架Dagger2详解(五),中级篇
    依赖注入框架Dagger2详解(六),高级篇

    这阵子,一直打算写几篇关于关于依赖注入框架Dagger2的文章,我也在网上看了很多这方面的文章,大部分文章都是直接讲Dagger2怎么使用的,而很少有讲它背后的思想,要么有讲,也是寥寥几笔,一带而过,我们不但要学习这个框架怎么用,更重要的是要知道他背后所体现的编程思想,否则你永远都是一个框架的调用者,而不是一名程序设计师,我记得上高中的时候,我们的物理老师说过一句话:“物理学的尽头是数学,数学的尽头是哲学,哲学的尽头是哲学”,越是抽象的东西越高深,思想是行动的指南,因此我们今天先从理论开始,用单独一篇文章的篇幅来讲Dagger2框架背后所体现的编程思想:依赖注入和反转控制。

    学习过Spring框架的人一定都会听过Spring的IoC(Inversion of Control ,控制反转) 、DI(Dependency Injection,依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC 、DI这两个概念是模糊不清的,是很难理解的,今天和大家分享网上的一些技术大牛们对Spring框架的IoC的理解。

    1.分享Iteye的一位大神对Ioc的精彩讲解

    首先要分享的是Iteye的这位技术牛人对Spring框架的Ioc的理解,上个周我在网上查阅相关资料的时候,偶然间发现了这篇文章,我觉得这位大神写的非常好,以至于我爱不释手的看了好几篇,最后我决定把它分享出来因此,写得非常通俗易懂,原文地址:http://jinnianshilongnian.iteye.com/blog/1413846

    1.1 IoC是什么

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

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

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

    用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:
    在这里插入图片描述

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

    1.2 IoC能做什么

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

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

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

    1.3 IoC和DI

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

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

    • 谁依赖于谁:当然是应用程序依赖于IoC容器;
    • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
    • 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
    • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

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

    看过很多对Spring的Ioc理解的文章,好多人对Ioc和DI的解释都晦涩难懂,反正就是一种说不清,道不明的感觉,读完之后依然是一头雾水,感觉就是开涛这位技术牛人写得特别通俗易懂,他清楚地解释了IoC(控制反转) 和DI(依赖注入)中的每一个字,读完之后给人一种豁然开朗的感觉。我相信对于初学Spring框架的人对Ioc的理解应该是有很大帮助的。

    2.分享Bromon的blog上对IoC与DI浅显易懂的讲解

    2.1 IoC(控制反转)

    首先想说说IoC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。

    那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。

    2.2 DI(依赖注入)

    IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

    理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。

    3.分享bestone0213对IoC(控制反转)和DI(依赖注入)的理解

    在平时的java应用开发中,我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成,在没有使用Spring的时候,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起,而使用了Spring之后就不一样了,创建合作对象B的工作是由Spring来做的,Spring创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题(你是什么时候生的,怎么生出来的我可不关心,能帮我干活就行),A得到Spring给我们的对象之后,两个人一起协作完成要完成的工作即可。

    所以控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。

    DI(依赖注入)其实就是IOC的另外一种说法,DI是由Martin Fowler 在2004年初的一篇论文中首次提出的。他总结:控制的什么被反转了?就是:获得依赖对象的方式反转了。

    小结

    对于Spring Ioc这个核心概念,我相信每一个学习Spring的人都会有自己的理解。这种概念上的理解没有绝对的标准答案,仁者见仁智者见智。

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

    展开全文
  • Spring解决循环依赖的方法

    万次阅读 多人点赞 2019-07-25 00:21:15
    从逻辑上说明Spring是怎么解决循环依赖的,然后从源码上看Spring是怎么做的。
  • 范式 函数依赖 多值依赖

    千次阅读 多人点赞 2020-12-30 22:11:33
    第一范式、第二范式、第三范式、第四范式、第五范式、扩展第三范式、函数依赖、完全函数依赖、部分函数依赖、传递函数依赖、平凡函数依赖、多值依赖,定义、举例、图解、关系
  • 数据库部分函数依赖 完全函数依赖 传递函数依赖 第一范式、第二范式、第三范式、BCNF范式区别 在理解函数依赖之前,先来看一下函数依赖分析: 在关系中,包括在任何候选码中的属性称为主属性;不包括在任何候选...
  • 这个是android-support-v7的依赖库包,希望对你有用
  • selenium及依赖jar包(java)

    热门讨论 2015-07-21 15:43:27
    selenium及所有依赖jar包(java),selenium,java,模拟浏览器,模拟登录
  • 写这篇文章的原因是这两天在编写关于 Dagger2 主题的博文时,花了大量的精力来解释依赖注入这个概念。后来想一下,这些在面向对象开发过程中与依赖相关的诸多术语和概念实际情况下非常的抽象,因此独立成文也有就...
  • tomcat+memcached依赖

    热门讨论 2013-12-14 23:38:41
    多个tomcat利用memcached(v1.4.13)共享session时用到的依赖包。
  • 文章目录0.思维导图1.为什么要学习关系数据库规范化理论?(1)基本概念回顾(2)关系模式的形式化定义(3)什么是数据依赖F?(4)数据依赖F对关系模式的影响1️⃣ 数据冗余(Data ...(2)函数依赖① 函数依赖② 平
  • java poi 读写word文件的Demo,内含所有依赖jar包,分享出来免资源分可下载。
  • Maven依赖管理总结

    千次阅读 2018-11-06 16:28:09
    本文主要总结Maven依赖管理中依赖范围和依赖冲突的解决。 依赖范围 依赖是maven项目引用的资源架包,依赖范围就是这些资源架包在maven项目中的作用范围,反过来说,maven项目通过依赖范围来控制何时引用资源架包。...
  • Spring框架依赖jar包

    千次下载 热门讨论 2013-04-06 17:17:07
    Spring框架依赖jar包,其中最小依赖包:org.springframework.core、org.springframework.context、org.springframework.beans、org.springframework.asm、org.springframework.expression、...
  • Hibernate3的依赖

    千次下载 热门讨论 2013-08-20 08:27:03
    HIbernate3框架的依赖包。 使用说明:下载完成后,直接将解压缩后文件夹中的jar包引入到Eclipse中即可
  • JSONObject对象相关的jar包依赖(6个)

    千次下载 热门讨论 2014-10-05 15:58:35
    JSONObject对象相关的jar包依赖,一共有6个!JSONObject对象相关的jar包依赖,一共有6个!JSONObject对象相关的jar包依赖,一共有6个!
  • Spring三级缓存解决循环依赖

    万次阅读 多人点赞 2020-02-28 09:53:05
    循环依赖:就是N个类循环(嵌套)引用。 通俗的讲就是N个Bean互相引用对方,最终形成闭环。用一副经典的图示可以表示成这样(A、B、C都代表对象,虚线代表引用关系): 其实可以N=1,也就是极限情况的循环依赖...
  • Maven依赖详细理解

    千次阅读 2019-06-08 15:26:38
    Maven依赖理解1 简介2 依赖的配置3 依赖的范围4 传递性依赖4.1 传递性依赖依赖调解5 依赖调解6 可选依赖7 总结 1 简介  在Maven项目中,有一个核心文件pom.xml。POM项目对象模型定义了项目的基本信息,用于描述心...
  • json-lib-2.4-jdk15及其全部依赖jar包

    千次下载 热门讨论 2014-04-18 10:23:15
    json-lib-2.4-jdk15及其全部依赖jar包 commons-beanutils-1.8.0.jar、commons-collections-3.2.1.jar、commons-lang-2.6.jar、commons-logging-1.1.1.jar、ezmorph-1.0.6.jar、json-lib-2.4-jdk15.jar、xom-1.2.6....
  • 根据函数依赖求最小依赖

    万次阅读 多人点赞 2019-06-26 13:37:36
    【例1】关系模式R<U,F>...第二步:逐个去掉X→A依赖后,设剩下函数依赖集为G,求属性集X关于G的闭包,如果闭包包含右边属性A,则去掉该函数依赖。 A→B:(A)+=AC,不包含B,保留。 A→C:(A)+...
  • Gradle 依赖&解决依赖冲突

    万次阅读 2018-09-18 11:37:16
    # Gradle 依赖&amp;解决依赖冲突 如何定义一个依赖。 DependencyHandler,Dependency,Dependencies,Configuration,ConfigurationContainer 的关系。 什么是传递依赖? 如何定位依赖冲突? 如何解决依赖冲突...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,464,564
精华内容 1,385,825
关键字:

依赖