3.depends-on

(1) depends-on用来表示一个bean的实例化依靠另一个bean的先实例化,如果在一个bean A上定义了depends-on bean B就表示:bean A实例化前先实例化bean B。

<!-- 例一 -->
<beans ...>
    <!-- 对于下面这俩个bean,谁先谁后创建不能确定 -->
    <bean id="exampleA" class="cn.example.spring.boke.ExampleA"></bean>
    <bean id="exampleB" class="cn.example.spring.boke.ExampleB"></bean>
</beans>

<!-- 例二 -->
<beans ...>
    <!-- exampleB depends-on exampleA,因此exampleA会先于exampleB被创建 -->
    <bean id="exampleA" class="cn.example.spring.boke.ExampleA"></bean>
    <bean id="exampleB" class="cn.example.spring.boke.ExampleB" depends-on="exampleA"></bean>
</beans>

4.懒加载

(1) 默认情况下,Spring会在容器启动时初始化所有的singleton bean,因为在这种情况下,某些配置错误会尽早发现,但如果我们不想提前初始化bean,就可以指定bean的lazy-init属性,来延迟初始化bean,这样这个bean会在第一次请求时被初始化而非在容器启动时被初始化

<beans ...>
    <!-- exampleA会在第一次请求时被初始化 -->
    <bean id="exampleA" class="cn.example.spring.boke.ExampleA" lazy-init="true"></bean>
    <!-- exampleB会在容器启动时被初始化 -->
    <bean id="exampleB" class="cn.example.spring.boke.ExampleB"></bean>
</beans>

(2) 注意:当延迟初始化的bean是非延迟初始化的bean的依赖项时,延迟初始化就失效了,它也会在容器启动时被初始化,因为容器在启动时必须为这个非延迟初始化的bean注入依赖项

(3) 使用beans标签上的default-lazy-init属性批量设置延迟初始化的bean

<beans default-lazy-init="true"> 
  <!-- 相当于给容器内的每一个bean都提供了lazy-init=true属性 --> 
</beans>

4.自动装配

(1) 对于存在依赖关系的多个bean,我们可以通过ref属性来手动注入依赖项,同时,如果不想手动,可通过设置bean标签的autowire属性来开启Spring的自动装配功能,它会按照所给的策略来自动的注入依赖项,相较于手动装配,其具有自动更新配置,简化代码等优点,如下

<!-- 例一:手动装配 -->
<beans ...>
    <bean id="exampleA" class="cn.example.spring.boke.ExampleA"></bean>

    <bean id="exampleB" class="cn.example.spring.boke.ExampleB">
        <!-- 使用ref来配置依赖项 -->
        <property name="exampleA" ref="exampleA"></property>
    </bean>
</beans>

<!-- 例二:自动装配 -->
<beans ...>
    <bean id="exampleA" class="cn.example.spring.boke.ExampleA"></bean>
    
    <!-- autowire属性设置为byType,告诉容器来按照类型进行依赖项的注入 -->
    <bean id="exampleB" class="cn.example.spring.boke.ExampleB" autowire="byType"></bean>
</beans>

(2) 自动装配策略,即autowire属性的取值:

策略 描述
no 默认选项,即不进行自动装配,bean之间的依赖关系通过指定ref属性来手动装配
byName 根据属性名称进行自动装配,Spring会寻找与属性名同名的bean来进行装配,比如上面的例子中,exampleB对象有一个名为exampleA的成员变量,则Spring会在容器中寻找id为exampleA的这个bean进行注入
byType 根据属性类型进行自动装配,如果容器中恰好存在一个同类型的bean,则会进行自动装配,如果存在多个,则会抛出异常,如果没有该类型的bean,则什么也不会发生,属性不会被注入
constructor 类似于byType,不过针对的是构造函数参数进行的自动装配,如果容器中不存在类型跟构造函数参数的类型一样的bean时,将引发异常

此外,可通过byType或constructor策略,来对数组或集合进行自动装配,注入与类型相匹配的所有bean,如下

public class ExampleA {}

//ExampleB中存在一个类型为ExampleA的集合,希望通过这个集合来接收容器内所有类型为ExampleA的bean
public class ExampleB {
    private List<ExampleA> exampleAList;

    public void setExampleAList(List<ExampleA> exampleAList) {
        this.exampleAList = exampleAList;
    }
}

<!-- xml配置文件 -->
<beans ...>
    <!-- 定义了两个类型为ExampleA的bean -->
    <bean id="exampleA1" class="cn.example.spring.boke.ExampleA"></bean>
    <bean id="exampleA2" class="cn.example.spring.boke.ExampleA"></bean>

    <!-- autowire为byType,此时Spring会向exampleAList中注入上面定义的两个bean -->
    <bean id="exampleB" class="cn.example.spring.boke.ExampleB" autowire="byType"></bean>
</beans>

(3) 自动装配的缺陷:

  • 在property或constructor-arg标签中手动指定的bean会覆盖掉自动装配的bean,此外,自动装配不同装配一些简单类型,如int,boolean,String等

  • 自动装配不如手动显式指定精确

  • 如果容器中存在同一类型的多个bean时,由于容器有时不知道该装配哪一个,从而会引发异常(解决方案:1.放弃自动装配改为手动装配 2.将不期望装配的bean的autowire-candidate属性设为false,容器在寻找依赖项时会忽略掉这个bean 3.将期望装配的bean的primary属性设为true,容器会首先装配这个bean)

(4) 在自动装配过程中排除bean:通过bean标签中的autowire-candidate属性(该属性只会影响byType策略,对于byName无效),来设置当前bean在自动装配过程中是否作为候选bean,默认值为true,当设置为false时,表示当前bean不作为候选bean,即容器在寻找依赖项时会忽略掉这个bean,例子如下

public class ExampleA {}

//ExampleB依赖ExampleA
public class ExampleB {
    private ExampleA exampleA;

    public void setExampleA(ExampleA exampleA) {
        this.exampleA = exampleA;
    }
}

<!-- xml配置文件 -->
<beans ...>

    <!-- 如果这里不将exampleA1的autowire-candidate属性设置为false,那么容器中就会有两个类型为ExampleA的bean,在自动装配时,容器会抛出NoUniqueBeanDefinitionException -->
    <!-- exampleA1的autowire-candidate为false,那么容器在寻找依赖项时就会忽略这个bean,因此会将exampleA2进行注入 -->
    <bean id="exampleA1" class="cn.example.spring.boke.ExampleA" autowire-candidate="false"></bean>
    <bean id="exampleA2" class="cn.example.spring.boke.ExampleA"></bean>

    <bean id="exampleB" class="cn.example.spring.boke.ExampleB" autowire="byType"></bean>
</beans>

5.方法注入(Method Injection)

(1) 方法注入的作用场景:在存在不同生命周期的bean的情况下,假设一个singleton bean A依赖了一个non-singleton bean B,因为singleton bean只会被容器创建一次,因此在设置bean A的依赖项时,bean B也只会被创建一次,故而容器无法在bean A每次获取bean B时为bean A注入一个全新的bean B对象,此时这个bean B就失去了‘non-singleton’的意义,如下所示

public class ExampleA {
    public void execute() {
        System.out.println(this);
    }
}

public class ExampleB {
    private ExampleA exampleA;

    //我们希望ExampleB在每次执行doSomething方法时,容器都会提供给我们一个全新的ExampleA bean
    public void doSomething() {
        exampleA.execute();
    }

    public void setExampleA(ExampleA exampleA) {
        this.exampleA = exampleA;
    }
}

<!-- xml配置文件 -->
<beans ...>
    <!-- singleton bean ExampleB依赖了non-singleton bean ExampleA,因为我们希望每次使用的ExampleA都是一个新的bean,因此我们将ExampleA的scope定义为prototype -->
    <bean id="exampleA" class="cn.example.spring.boke.ExampleA" scope="prototype"></bean>
    <bean id="exampleB" class="cn.example.spring.boke.ExampleB" scope="singleton">
        <property name="exampleA" ref="exampleA"></property>
    </bean>
</beans>

//测试,打印的结果显示地址相同,说明ExampleA始终是同一个bean,没有起到prototype的效果
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("boke/from.xml");
((ExampleB)ctx.getBean("exampleB")).doSomething();
((ExampleB)ctx.getBean("exampleB")).doSomething();

针对这种情况,一般有两种解决方案,如下

  • 通过实现ApplicationContextAware接口获取到ApplicationContext,这样每次在需要bean B时,通过调用其getBean()方法来手动获取一个新的bean B对象
public class ExampleB implements ApplicationContextAware {

    private ApplicationContext applicationContext;
    
    //ExampleB在每次执行doSomething方法时,主动去向容器中请求bean
    public void doSomething() {
        ExampleA exampleA = this.applicationContext.getBean(ExampleA.class);
        exampleA.execute();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

//测试,打印的结果显示地址不同,说明通过这种方式我们每次都可以获取一个新的ExampleA bean
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("boke/from.xml");
((ExampleB)ctx.getBean("exampleB")).doSomething();
((ExampleB)ctx.getBean("exampleB")).doSomething();
  • 不过上面这种方式增加了代码与Spring框架的耦合度,故不推荐使用,此时,就出现了第二种方法:lookup方法注入,这种方式会通过CGLIB字节码库动态生成某个bean的子类并覆盖其中特定的方法,来实现注入依赖项的功能
//因为Spring会生成该类的子类来重写其中的方法,因此类与方法均不能被final修饰
public abstract class ExampleB  {
    
    public void doSomething() {
        getExampleA().execute();
    }

    //声明一个方法,来让Spring重写,之后Spring会通过这个方法来为我们提供bean
    protected abstract ExampleA getExampleA();

    //需要进行lookup方法注入的方法签名格式:<public|protected> [abstract] <return-type> theMethodName(no-arguments);
    //由上面这个签名格式可以看出,abstract是可选的,可以是普通方法也可以是抽象方法
}

<!-- xml配置文件 -->
<beans ...>
    <bean id="exampleA" class="cn.example.spring.boke.ExampleA" scope="prototype"></bean>

    <bean id="exampleB" class="cn.example.spring.boke.ExampleB" scope="singleton">
        <!-- 注意这里使用look-method标签来指定会被Spring重写的方法,之后每次调用getExampleA方法,Spring都会为我们产生一个新的ExampleA bean -->
        <lookup-method name="getExampleA" bean="exampleA"></lookup-method>
    </bean>
</beans>

//测试,打印的地址值不相同
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("boke/from.xml");
((ExampleB)ctx.getBean("exampleB")).doSomething();
((ExampleB)ctx.getBean("exampleB")).doSomething();

上面的lookup-method标签,可通过@Lookup注解进行替换,具体示例可见官方文档,此处略

6.任意方法替换(Arbitrary Method Replacement)

(1) 作用:对于Spring bean中的某个方法,改变它的行为,不常用,例子如下

//我们有一个bean ExampleC,现在我想改写其中的方法getStr的行为,让它return "1999" + s; 而非 return "2022" + s;
public class ExampleC {
    public String getStr(String s) {
        return "2022" + s;
    }
}

//实现MethodReplacer接口,来实现被替换方法的新行为
public class Replacement implements MethodReplacer {
    /**
     *
     * @param o 要被替换的方法所在的bean的代理对象,这个例子中就是ExampleC的代理对象
     * @param method 要被替换的方法,这个例子中就是getStr方法
     * @param objects 要被替换的方法的参数列表
     * @return
     */
    @Override
    public Object reimplement(Object o, Method method, Object[] objects) throws Throwable {
        //新的行为
        String s = (String) objects[0];
        return "1999" + s;
    }
}

<!-- xml文件配置 -->
<beans ...>
    <bean id="replacement" class="cn.example.spring.boke.Replacement"></bean>

    <bean id="exampleC" class="cn.example.spring.boke.ExampleC">
        <replaced-method name="getStr" replacer="replacement">
            <!-- arg-type标签来指定被替换的方法的参数类型 -->
            <arg-type>java.lang.String</arg-type>
        </replaced-method>
    </bean>
</beans>