Spring官网阅读(二)(依赖注入及方法注入)

上篇文章咱们学习了官网中的1.2,1.3两小节,主要是涉及了容器,以及Spring实例化对象的一些知识。这篇文章咱们继续学习Spring官网,主要是针对1.4小节,主要涉及到Spring的依赖注入。虽然只有一节,可是涉及的东西确很多。话很少说,开始正文。java

依赖注入测试setter方法注入测试构造函数注入疑问:区别:方法注入:为何须要方法注入:经过注入上下文(applicationContext对象)经过@LookUp的方式(也分为注解跟XML两种方式,这里只演示注解的)方法注入 之  replace-method依赖注入跟方法注入的总结:程序员

依赖注入

根据官网介绍,依赖注入主要分为两种方式web

  1. 构造函数注入spring

  2. Setter方法注入微信

    官网:app

在这里插入图片描述

咱们分别对以上两种方式进行测试,官网上用的是XML的方式,我这边就采用注解的方式了:编辑器

测试代码以下,咱们经过在Service中注入LuBanService这个过程来ide

public class Main02 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new 
            // config类主要完成对类的扫描
            AnnotationConfigApplicationContext(Config.class);
        Service service = (Service) ac.getBean("service");
        service.test();
    }
}

@Component
public class LuBanService {
    LuBanService(){
        System.out.println("luBan create ");
    }
}
测试setter方法注入
@Component
public class Service {

    private LuBanService luBanService;

    public Service() {
        System.out.println("service create");
    }

    public void test(){
        System.out.println(luBanService);
    }
    // 经过autowired指定使用set方法完成注入
    @Autowired
    public void setLuBanService(LuBanService luBanService) {
        System.out.println("注入luBanService by setter");
        this.luBanService = luBanService;
    }
}

输出以下:svg


luBan create 
service create
注入luBanService by setter  // 验证了确实是经过setter注入的
com.dmz.official.service.LuBanService@5a01ccaa

测试构造函数注入
@Component
public class Service {

    private LuBanService luBanService;

    public Service() {
        System.out.println("service create by no args constructor");
    }

    // 经过Autowired指定使用这个构造函数,不然默认会使用无参
    @Autowired
    public Service(LuBanService luBanService) {
        System.out.println("注入luBanService by constructor with arg");
        this.luBanService = luBanService;
        System.out.println("service create by constructor with arg");
    }

    public void test(){
        System.out.println(luBanService);
    }
}

输出以下:函数

luBan create 
注入luBanService by constructor // 验证了确实是经过constructor注入的
service create by constructor
com.dmz.official.service.LuBanService@1b40d5f0

疑问:

在上面的验证中,你们可能会有如下几个疑问:

  1. @Autowired直接加到字段上跟加到set方法上有什么区别?为何咱们验证的时候须要将其添加到setter方法上?

  • 首先咱们明确一点,直接添加@Autowired注解到字段上,不须要提供setter方法也能完成注入。以上面的例子来讲,Spring会经过反射获取到Service中luBanService这个字段,而后经过反射包的方法,Filed.set(Service,luBanService)这种方式来完成注入

  • 咱们将@Autowired添加到setter方法时,咱们能够经过断点看一下方法的调用栈,以下:

在这里插入图片描述

对于这种方式来讲,最终是经过Method.invoke(object,args)的方式来完成注入的,这里的method对象就是咱们的setter方法

  1. @Autowired为何加到构造函数上能够指定使用这个构造函数?

  • 咱们先能够测试下,若是咱们不加这个注解会怎么样呢?我把前文中的@Autowired注解注释,而后运行发现


java   luBan create   service create by no args constructor  // 能够看到执行的是空参构造   null


先不急得出结论,咱们再进行一次测试,就是两个函数上都添加@Autowired注解呢?

java   Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'service': Invalid autowire-marked constructor: public com.dmz.official.service.Service(com.dmz.official.service.LuBanService). Found constructor with 'required' Autowired annotation already: public com.dmz.official.service.Service()

发现直接报错了,报错的大概意思是已经找到了一个被@Autowired注解标记的构造函数,同时这个注解中的required属性为true。后来我测试了将其中一个注解中的required属性改成false,发现仍是报一样的错,最终将两个注解中的属性都改成false测试才经过,而且测试结果跟上面的同样,都是执行的无参构造。

要说清楚这一点,涉及到两个知识

  • Spring中的注入模型,下篇文章专门讲这个

  • Spring对构造函数的推断。这个到源码阶段我打算专门写一篇文章,如今咱们暂且记得:

默认的注入模型下,Spring若是同时找到了两个符合要求的构造函数,那么Spring会采用默认的无参构造进行实例化,若是这个时候没有无参构造,那么此时会报错java.lang.NoSuchMethodException。什么叫符合要求的构造函数呢?就是构造函数中的参数Spring能找到,参数被Spring所管理。

这里须要着重记得:一,默认注入模型;二,符合要求的构造函数

  1. 若是咱们同时采用构造注入加属性注入会怎么样呢?

    在没有进行测试前,咱们能够大胆猜想下,Spring虽然能在构造函数里完成属性注入,可是这属于实例化对象阶段作的事情,那么在后面真正进行属性注入的时候,确定会将其覆盖掉。如今咱们来验证咱们的结论

    @Component
    public class Service {
       private LuBanService luBanService;  
       public Service(LuBanService luBanService) {
           System.out.println("注入luBanService by constructor with arg");
           this.luBanService = luBanService;
           System.out.println("service create by constructor with arg");
       }
       public void test(){
           System.out.println(luBanService);
       }
       @Autowired
       public void setLuBanService(LuBanService luBanService) {
           System.out.println("注入luBanService by setter");
           this.luBanService = null;
       }
    }

    运行结果:


java   注入luBanService by constructor with arg  // 实例化时进行了一次注入   service create by constructor with arg   // 完成了实例化   注入luBanService by setter    // 属性注入时将实例化时注入的属性进行了覆盖   null


区别:
在这里插入图片描述

根据上图中官网所说,咱们能够得出以下结论:

  1. 构造函数注入跟setter方法注入能够混用

  2. 对于一些强制的依赖,咱们最好使用构造函数注入,对于一些可选依赖咱们能够采用setter方法注入

  3. Spring团队推荐使用构造函数的方式完成注入。可是对于一些参数过长的构造函数,Spring是不推荐的

方法注入:

咱们不彻底按照官网顺序进行学习,先看这一小节,对应官网上的位置以下图:

在这里插入图片描述
为何须要方法注入

首先咱们思考一个问题,在有了依赖注入的状况下,为何还须要方法注入这种方式呢?换而言之,方法注入解决了什么问题?

咱们来看下面这种场景:

@Component
public class MyService {

    @Autowired
    private LuBanService luBanService;

    public void test(int a){
        luBanService.addAndPrint(a);
    }

}

@Component
// 原型对象
@Scope("prototype")
public class LuBanService {
    int i;

    LuBanService() {
        System.out.println("luBan create ");
    }
    // 每次将当前对象的属性i+a而后打印
    public void addAndPrint(int a) {
        i+=a;
        System.out.println(i);
    }
}

public class Main02 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
        MyService service = (MyService) ac.getBean("myService");
        service.test(1);
        service.test(2);
        service.test(3);
    }
}

在上面的代码中,咱们有两个Bean,MyService为单例的Bean,LuBanService为原型的Bean。咱们的本意多是但愿每次都能获取到不一样的LuBanService,预期的结果应该打印出:


1,2,3


实际输出:


1
3
6


这个结果说明咱们每次调用到的LuBanService是同一个对象。固然,这也很好理解,由于在依赖注入阶段咱们就完成了LuBanService的注入,以后咱们在调用测试方法时,不会再去进行注入,因此咱们一直使用的是同一个对象。

咱们能够这么说,原型对象在这种状况下,失去了原型的意义,由于每次都使用的是同一个对象。那么如何解决这个问题呢?只要我每次在使用这个Bean的时候都去从新获取就能够了,那么这个时候咱们能够经过方法注入来解决。

经过注入上下文(applicationContext对象)

又分为如下两种方式:

  • 实现org.springframework.context.ApplicationContextAware接口

@Component
public class MyService implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public void test(int a) {
        LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));
        luBanService.addAndPrint(a);
    }

    @Override
    public void setApplicationContext(@Nullable ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
  • 直接注入上下文

@Component
public class MyService{
    @Autowired
    private ApplicationContext applicationContext;

    public void test(int a) {
        LuBanService luBanService = ((LuBanService) applicationContext.getBean("luBanService"));
        luBanService.addAndPrint(a);
    }
}
经过@LookUp的方式(也分为注解跟XML两种方式,这里只演示注解的)
@Component
public class MyService{
    public void test(int a) {
        LuBanService luBanService = lookUp();
        luBanService.addAndPrint(a);
    }
    // 
    @Lookup
    public LuBanService lookUp(){
        return null;
    }
}
方法注入 之  replace-method

方法注入还有一种方式,即经过replace-method这种形式,没有找到对应的注解,因此这里咱们也就用XML的方式测试一下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myService" class="com.dmz.official.service.MyService">
        <replaced-method replacer="replacer" name="test"/>
    </bean>

    <bean id="replacer" class="com.dmz.official.service.MyReplacer"/>
</beans>
public class MyReplacer implements MethodReplacer {
    @Override
   public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("替代"+obj+"中的方法,方法名称:"+method.getName());
        System.out.println("执行新方法中的逻辑");
        return null;
    }
}

public class MyService{
    public void test(int a) {
        System.out.println(a);
    }
}

public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext cc =
            new ClassPathXmlApplicationContext("application.xml");
        MyService myService = ((MyService) cc.getBean("myService"));
        myService.test(1);
    }
}

执行结果:

替代com.dmz.official.service.MyService$$EnhancerBySpringCGLIB$$61c14242@63e31ee中的方法,方法名称:test
执行新方法中的逻辑

这里须要注意一点:

我在测试replace-method这种方法注入的方式时,受动态代理的影响,一直想将执行咱们被替代的方法。用代码体现以下:

public class MyReplacer implements MethodReplacer {

    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
//        System.out.println("替代"+obj+"中的方法,方法名称:"+method.getName());
//        System.out.println("执行新方法中的逻辑");
        method.invoke(obj,args);
        return null;
    }
}

可是,这段代码是没法执行的,会报栈内存溢出。由于obj是咱们的代理对象,method.invoke(obj,args)执行时会进入方法调用的死循环。最终我也没有找到一种合适的方式来执行被替代的方法。目前看来这可能也是Spring的设计,因此咱们使用replace-method的场景应该是想彻底替代某种方法的执行逻辑,而不是像AOP那样更多的用于在方法的执行先后等时机完成某些逻辑。

依赖注入跟方法注入的总结:
  • 咱们首先要明确一点,什么是依赖(Dependencies)?来看官网中的一段话:

在这里插入图片描述

能够说,一个对象的依赖就是它自身的属性,Spring中的依赖注入就是属性注入

  • 咱们知道一个对象由两部分组成:属性+行为(方法),能够说Spring经过属性注入+方法注入的方式掌控的整个bean。

  • 属性注入跟方法注入都是Spring提供给咱们用来处理Bean之间协做关系的手段

  • 属性注入有两种方式:构造函数,Setter方法。

  • 方法注入(LookUp Method跟Replace Method)须要依赖动态代理完成

  • 方法注入对属性注入进行了必定程度上的补充,由于属性注入的状况下,原型对象可能会失去原型的意义,见:为何须要方法注入

画图以下:


点击在看不迷路,我带你们上高速


本文分享自微信公众号 - 程序员DMZ(programerDmz)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索