专栏首页WebJ2EESpring入门:The IoC Container,实践篇(上)

Spring入门:The IoC Container,实践篇(上)

图文无关

Spring 框架的 IoC 容器(Inversion of Control,Ioc)是 Spring 框架中最基础、最重要的部分。Ioc 也被称为依赖注入(Dependency Injection,DI),是一种将组件依赖项的创建和管理外部化的技术。

图:Spring 架构图

1. Spring IoC 容器简介与 Beans

It is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes or a mechanism such as the Service Locator pattern. —— What is IoC ?

  • IoC 包括“依赖注入”与“依赖查找”;
  • org.springframework.beansorg.springframework.context 是 Spring Ioc 容器的基础包;
  • BeanFactory 是 Spring IoC 容器最核心的接口,提供了 IoC 的基础配置机制。
  • ApplicationContext 是建立在 BeanFactory 之上,提供了更多面向应用的功能(例如:国际化、框架事件体系、AOP集成) 。
  • 凡是被 Spring IoC 容器管理的对象,即为 Beans。Spring IoC 容器负责这些 Bean 的构造、组装、生命周期管理。

图:BeanFactory 局部结构

2. 容器概述

  • org.springframework.context.ApplicationContext 接口即代表 Spring IoC 容器。
  • ClassPathXmlApplicationContext、FileSystemApplicationContext、AnnotationConfigApplicationContext 是几个常见的 ApplicationContext 实现类。
  • Spring IoC 容器通过“配置元数据(configuration metadata)”获得如何初始化、配置、组装对象的信息。
  • “配置元数据”可以用 XML(经典、传统)、Java注解(Spring 2.5)、Java代码(Spring 3.0)、Groovy代码表示。

图:The Spring IoC container

最最最基础的示例:

package webj2ee;
public class HelloWorld { // Bean
    public void sayHello(){
        System.out.println("Hello World!");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd ">
    <bean id="helloWorld" class="webj2ee.HelloWorld"/>
</beans>
package webj2ee;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        HelloWorld helloWorld = context.getBean("helloWorld", HelloWorld.class);
        helloWorld.sayHello();
    }
}

3. Bean 概述

Spring IoC 容器管理着那些构成你应用的对象(Bean),这些 Bean 如何初始化、组装则是通过你提供给 IoC 容器的“配置元数据(XML、注解、Java、Groovy)”控制。不管是哪种形式的“配置元数据”,在 IoC 容器内部,都会转换为 BeanDefinition。

图:BeanDefinition 基本结构

3.1. 命名 Beans

  • 只可以指定一个id;
  • 可以指定多个别名;

示例:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="hello" name="hello1, hello2, hello3" class="webj2ee.Hello"/>
    <alias name="hello" alias="hello4"/>
</beans>
package webj2ee;

import org.apache.commons.lang3.StringUtils;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        Object o = context.getBean("hello");
        Object o1 = context.getBean("hello1");
        Object o2 = context.getBean("hello2");
        Object o3 = context.getBean("hello3");
        Object o4 = context.getBean("hello4");

        System.out.println(o == o1);
        System.out.println(o1 == o2);
        System.out.println(o2 == o3);
        System.out.println(o3 == o4);

        String[] aliases = context.getAliases("hello");
        System.out.println(StringUtils.join(aliases, ", "));
    }
}
  • 命名规范:建议参考 java beans 命名规范;(通常采用驼峰命名方式,但首字母小写(例如:loginController、accountDao)。例外情况是,如果前两个字母都是大写,则保持原始命名不变(例如:URLConnection))
/**
 * Utility method to take a string and convert it to normal Java variable
 * name capitalization.  This normally means converting the first
 * character from upper case to lower case, but in the (unusual) special
 * case when there is more than one character and both the first and
 * second characters are upper case, we leave it alone.
 * <p>
 * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays
 * as "URL".
 *
 * @param  name The string to be decapitalized.
 * @return  The decapitalized version of the string.
 */
public static String decapitalize(String name) {
    if (name == null || name.length() == 0) {
        return name;
    }
    if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                    Character.isUpperCase(name.charAt(0))){
        return name;
    }
    char chars[] = name.toCharArray();
    chars[0] = Character.toLowerCase(chars[0]);
    return new String(chars);
}

代码:java.beans 的命名规范代码;

3.2. 实例化 Beans

  • 通过构造函数实例化Bean;
  • 通过静态工厂方法实例化Bean;
  • 通过实例工厂方法实例化Bean;

示例1:(构造函数)

package webj2ee;

public class HelloWorld {
    public void sayHello(){
        System.out.println("Hello World!");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="helloWorld" class="webj2ee.HelloWorld"/>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        HelloWorld o = context.getBean("helloWorld", HelloWorld.class);
        o.sayHello();
    }
}

示例2:(静态工厂方法实例化Bean)

package webj2ee;

public class HelloWorld {
    private static HelloWorld instance = new HelloWorld();
    private HelloWorld(){}

    public static HelloWorld getInstance(){
        return instance;
    }

    public void sayHello(){
        System.out.println("Hello World!");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="helloWorld" class="webj2ee.HelloWorld" factory-method="getInstance"/>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        HelloWorld o = context.getBean("helloWorld", HelloWorld.class);
        o.sayHello();
    }
}

示例3:(实例工厂方法实例化Bean)

package webj2ee;

public class HelloWorld {
    private static HelloWorld instance = new HelloWorld();

    private HelloWorld(){
    }

    public HelloWorld getInstance(){
        return instance;
    }

    public void sayHello(){
        System.out.println("Hello World!");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

    <bean id="helloWorldGetter" class="webj2ee.HelloWorld"/>
    <bean id="helloWorld" factory-bean="helloWorldGetter" factory-method="getInstance"/>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Demo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        HelloWorld o = context.getBean("helloWorld", HelloWorld.class);
        o.sayHello();
    }
}

4. 依赖关系管理

4.1. 依赖注入

  • 依赖注入包含“构造器注入”、“属性注入”;
  • 面向接口编程 + 依赖注入 => 程序更容易测试;
  • 构造器注入,不允许循环依赖,常用于实现对不可变对象的依赖;
  • 通过 <constructor-arg> 描述构造器注入参数;
  • 通过 <property> 描述属性注入参数;
  • <constructor-arg>,不能通过编写顺序标记参数 ;
  • <constructor-arg>,可通过“类型(type)”标记参数;
  • <constructor-arg>,可通过“索引(index)”标记参数;
  • <constructor-arg>,可通过“名称(name)”标记参数;(需配合 @ConstructorProperties 注解)
package webj2ee;

public class ExampleBean {
    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

    @Override
    public String toString() {
        return "ExampleBean{" +
                "years=" + years +
                ", ultimateAnswer='" + ultimateAnswer + '\'' +
                '}';
    }
}

示例1:通过 type 标记构造函数参数的顺序

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean">
        <constructor-arg type="int" value="7500000"/>
        <constructor-arg type="java.lang.String" value="42"/>
    </bean>
</beans>

示例2:通过 index 标记构造函数参数的顺序

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean">
        <constructor-arg index="0" value="7500000"/>
        <constructor-arg index="1" value="42"/>
    </bean>
</beans>

示例3:通过 name 标记构造函数参数的顺序

package webj2ee;

import java.beans.ConstructorProperties;

public class ExampleBean {
    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

    @Override
    public String toString() {
        return "ExampleBean{" +
                "years=" + years +
                ", ultimateAnswer='" + ultimateAnswer + '\'' +
                '}';
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean">
        <constructor-arg name="years" value="7500000"/>
        <constructor-arg name="ultimateAnswer" value="42"/>
    </bean>
</beans>

示例4:静态工厂方法——构造器注入

package webj2ee;

public class ExampleBean {
    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    private ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

    private static ExampleBean getInstance(int years, String ultimateAnswer) {
        return new ExampleBean(years, ultimateAnswer);
    }

    @Override
    public String toString() {
        return "ExampleBean{" +
                "years=" + years +
                ", ultimateAnswer='" + ultimateAnswer + '\'' +
                '}';
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean" factory-method="getInstance">
        <constructor-arg name="years" value="7500000"/>
        <constructor-arg name="ultimateAnswer" value="42"/>
    </bean>
</beans>

示例5:构造器注入——循环依赖

package webj2ee;
public class Abean {
    public Abean(Bbean b){ }
}
package webj2ee;
public class Bbean {
    public  Bbean(Abean a){ }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="abean" class="webj2ee.Abean">
        <constructor-arg ref="bbean"/>
    </bean>
    <bean id="bbean" class="webj2ee.Bbean">
        <constructor-arg ref="abean"/>
    </bean>
</beans>

4.2. 依赖注入细节

  • 构造器参数,可通过 <constructor-arg/> 指定;
  • 属性参数,可通过 <property/> 指定;
  • 构造器参数,可通过 c-namespace 简化 <constructor-arg/> 复杂度;
  • 属性参数,可通过 p-namespace 简化 <property/> 复杂度;
  • 构造器参数、属性参数,都可指定多种类型变量值;

示例1:通过 <property> 指定基本数据类型;

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/mydb" />
        <property name="username" value="root" />
        <property name="password" value="oracledba" />
        <property name="initialSize" value="1" />
        <property name="minIdle" value="1" />
        <property name="maxActive" value="20" />
        <property name="maxWait" value="60000" />
    </bean>
</beans>

示例2:通过 p-namespace 简化 <property> 语法;

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close"
            p:url="jdbc:mysql://localhost:3306/mydb"
            p:username="root"
            p:password="oracledba"
            p:initialSize="1"
            p:minIdle="1"
            p:maxActive="20"
            p:maxWait="6000">
    </bean>
</beans>

示例3:引用 bean(java.util.Properties);

package webj2ee;

import java.util.Properties;

public class ExampleBean {
    private Properties props;

    public ExampleBean(Properties props){
        this.props = props;
    }

    @Override
    public String toString() {
        return "ExampleBean{" +
                "props=" + props +
                '}';
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean">
        <constructor-arg>
           <!-- 通过 <props> 构造 java.util.Properties 对象 -->
            <props>
                <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop>
                <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop>
            </props>
        </constructor-arg>
    </bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <!-- 通过 utils.properties 构造 java.util.Properties 对象 -->
    <util:properties id="props">
        <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop>
        <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop>
    </util:properties>
    <!-- 完整版 -->
    <bean id="exampleBean" class="webj2ee.ExampleBean">
        <constructor-arg>
            <ref bean="props"/>
        </constructor-arg>
    </bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <util:properties id="props">
        <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop>
        <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop>
    </util:properties>
    <!-- 简化版 -->
    <bean id="exampleBean" class="webj2ee.ExampleBean">
        <constructor-arg ref="props"/>
    </bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <util:properties id="props">
        <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop>
        <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop>
    </util:properties>
    <!-- 再简化 -->
    <bean id="exampleBean" class="webj2ee.ExampleBean"
        c:props-ref="props">
    </bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean">
        <constructor-arg>
            <!-- InnerBean 形式注入 java.util.Properties 参数 -->
            <util:properties>
                <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop>
                <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop>
            </util:properties>
        </constructor-arg>
    </bean>
</beans>

示例4:引用父容器 bean(java.util.Properties);

parent.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <util:properties id="props">
        <prop key="jdbc.driver.className">com.mysql.jdbc.Driver</prop>
        <prop key="jdbc.url">jdbc:mysql://localhost:3306/mydb</prop>
    </util:properties>
</beans>

child.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean">
        <constructor-arg>
            <ref parent="props"/>
        </constructor-arg>
    </bean>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        // parent context
        ClassPathXmlApplicationContext parent = new ClassPathXmlApplicationContext("parent.xml");

        // child context
        ClassPathXmlApplicationContext child = new ClassPathXmlApplicationContext(new String[]{"child.xml"}, parent);

        ExampleBean exampleBean = child.getBean("exampleBean", ExampleBean.class);
        System.out.println(exampleBean);
    }
}

4.3. depends-on、lazy-init

  • 某个 Bean 并没通过构造器注入、属性注入显式描述与其他 Bean 的依赖关系,但又想保证自己的初始化必须建立在某些 Bean 已经初始化完成的基础上,就需要利用 depends-on 特性。

示例:

package webj2ee;
public class DependOnBeanA {
    public DependOnBeanA() {
        System.out.println("DependOnBeanA Init Success!");
    }
}
package webj2ee;
public class DependOnBeanB {
    public DependOnBeanB(){
        System.out.println("DependOnBeanB Init Success!");
    }
}
package webj2ee;
public class ExampleBean {
    public ExampleBean(){
        System.out.println("ExampleBean Init Success!");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean" depends-on="dependOnBeanA,dependOnBeanB"></bean>
    <bean id="dependOnBeanA" class="webj2ee.DependOnBeanA"></bean>
    <bean id="dependOnBeanB" class="webj2ee.DependOnBeanB"></bean>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        // parent context
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class);
        System.out.println(exampleBean);
    }
}
  • Spring默认情况下,所有的单例 Bean,在装配的时候,就实例化。如果想把Bean的实例化过程延后到第一次 getBean 的时候,就需要 lazy-init 特性;
package webj2ee;

public class NotLazyBean {
    public NotLazyBean(){
        System.out.println("NotLazyBean Init Success!");
    }
}
package webj2ee;

public class LazyBean {
    public LazyBean(){
        System.out.println("LazyBean Init Success!");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="lazyBean" class="webj2ee.LazyBean" lazy-init="true"></bean>
    <bean id="notLazyBean" class="webj2ee.NotLazyBean"></bean>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        // parent context
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        System.out.println("After Container Init!");

        System.out.println("Before get notLazyBean!");
        NotLazyBean notLazyBean = context.getBean("notLazyBean", NotLazyBean.class);
        System.out.println("After get notLazyBean!");

        System.out.println("Before get lazyBean!");
        LazyBean lazyBean = context.getBean("lazyBean", LazyBean.class);
        System.out.println("After get lazyBean!");

    }
}

4.4. Method Injection(Lookup Method)

  • 当一个 Singleton Bean A依赖一个 Prototype Bean B,每次对 Bean A 方法的调用,都期望得到(操作)一个新的 Bean B,这时候 Method Injection(Lookup Method)就会派上用场。

反例:Singleton 依赖 Prototype,无法每次都得到新的 Prototype Bean;

package webj2ee;
public class PrototypeBean {
}
package webj2ee;

public class SingletonBean {
    PrototypeBean prototypeBean;
    public void setPrototypeBean(PrototypeBean prototypeBean) {
        this.prototypeBean = prototypeBean;
    }

    public PrototypeBean getNewPrototypeBean(){
        return prototypeBean;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="singletonBean" class="webj2ee.SingletonBean"
          p:prototypeBean-ref="prototypeBean"/>
    <bean id="prototypeBean" class="webj2ee.PrototypeBean" scope="prototype"></bean>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        SingletonBean singletonBean = context.getBean("singletonBean", SingletonBean.class);
        System.out.println(singletonBean.getNewPrototypeBean() == singletonBean.getNewPrototypeBean());
    }
}

正例1:通过 ApplicationContextAware 实现;

package webj2ee;

public class PrototypeBean {
}
package webj2ee;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class SingletonBean implements ApplicationContextAware {
    ApplicationContext applicationContext;

    public PrototypeBean getNewPrototypeBean(){
        return applicationContext.getBean("prototypeBean", PrototypeBean.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="singletonBean" class="webj2ee.SingletonBean"/>
    <bean id="prototypeBean" class="webj2ee.PrototypeBean" scope="prototype"></bean>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        SingletonBean singletonBean = context.getBean("singletonBean", SingletonBean.class);
        System.out.println(singletonBean.getNewPrototypeBean() == singletonBean.getNewPrototypeBean());
    }
}

正例2:通过 <lookup-method> 实现(CGLib)

package webj2ee;

public class PrototypeBean {
}
package webj2ee;

public abstract class SingletonBean {
    public abstract PrototypeBean getNewPrototypeBean();
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="singletonBean" class="webj2ee.SingletonBean">
        <lookup-method name="getNewPrototypeBean" bean="prototypeBean" ></lookup-method>
    </bean>
    <bean id="prototypeBean" class="webj2ee.PrototypeBean" scope="prototype"></bean>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        SingletonBean singletonBean = context.getBean("singletonBean", SingletonBean.class);
        System.out.println(singletonBean.getNewPrototypeBean() == singletonBean.getNewPrototypeBean());
    }
}

5. Bean 作用域

singleton:

  • 默认作用域;
  • per-container and per-bean;

prototype:

  • 每次 getBean(),都是一个新的 Bean 实例;
  • 通常 DAO(data access object)不是 prototype 类型;
  • Spring 容器负责实例化、配置、组装 prototype 类型 Bean,但不负责销毁;
  • 可考虑通过自定义容器级生命周期管理 Bean Post-Processor 销毁 prototype 类型 Bean。
  • 若 singleton 类型 Bean 有对 prototype 类型 Bean 的依赖,可通过 Method Injection 方法,每次都构造一个全新的 prototype 类型 Bean。

request、session、application:

  • 需要结合AOP说明,暂时跳过;

6. Bean 生命周期控制、xxAware 接口

Initialization Callbacks:
  • org.springframework.beans.factory.InitializingBean,容器对Bean完成必要的属性填充后,即触发此接口中的【void afterPropertiesSet() throws Exception】方法。
  • XML 中定义 Bean 时,init-method,用于实现同样效果;
  • JSR-250 规范中的 @PostConstruct,用于实现同样效果;
  • 如果3者混用,回调顺序:@PostConstruct > afterPropertiesSet > init-method
Destruction Callbacks:
  • org.springframework.beans.factory.DisposableBean,容器要销毁的时候,即触发此接口中的【void destory() throws Exception】方法
  • XML 中定义 Bean 时,destory-method,用于实现同样效果;
  • JSR-250规范中的 @PreDestory,用于实现同样效果;
  • 如果3者混用,回调顺序:@PreDescoty> destory > destory-method

示例:

package webj2ee;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class ExampleBean implements InitializingBean, DisposableBean {

    //////////////Initialization Callbacks//////////////
    @PostConstruct
    private void postConstruct(){
        System.out.println("@PostConstruct");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet");
    }

    private void initMethod(){
        System.out.println("init-method");
    }

    //////////////Destruction Callbacks//////////////

    @PreDestroy
    private void preDestory(){
        System.out.println("@PreDestory");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("destory");
    }

    private void destoryMethod(){
        System.out.println("destory-method");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <context:annotation-config/>
    <bean id="exampleBean" class="webj2ee.ExampleBean" 
          init-method="initMethod" 
          destroy-method="destoryMethod"></bean>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class);
        System.out.println(">>>>");
        context.close();
    }
}
xxAware 接口:
  • 实际是在告诉 Spring 容器,你想得到某些特殊依赖关系;
  • 常见的 xxAware 接口有:ApplicationContextAware(Method Injection的一种事项方式)、BeanFactoryAware、BeanNameAware、ServletConfigAware、ServletContextAware;

示例:

package webj2ee;

import org.springframework.beans.factory.BeanNameAware;

public class ExampleBean implements BeanNameAware {

    @Override
    public void setBeanName(String name) {
        System.out.println("setBeanName triggered with: "+name);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean"></bean>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class);
    }
}

7. 扩展 Spring 容器

Customizing Beans by Using a BeanPostProcessor:

  • org.springframework.beans.factory.config.BeanPostProcessor
  • BeanPostProcessor 的只会影响它所在 Spring 容器中的 Bean,不会影响到所在容器的父、子、兄弟容器中的 Bean。
  • BeanPostProessor 操作的是 Bean 的实例,即 Spring 容器实例化 Bean 后,BeanPostProcessor 即介入。
  • 包含两个callback:一个在 Bean 所有初始化方法前调用、另一个在 Bean 所有初始化方法后调用;
  • 如果存在多个 BeanPostProcessor,可通过 @Order 指明执行顺序。
  • Spring AOP 的一些实现,就是通过 BeanPostProcessor 给 Bean 包装代理类的。

示例:

package webj2ee;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class ExampleBean implements InitializingBean, DisposableBean {

    //////////////Initialization Callbacks//////////////
    @PostConstruct
    private void postConstruct(){
        System.out.println("@PostConstruct");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet");
    }

    private void initMethod(){
        System.out.println("init-method");
    }

    //////////////Destruction Callbacks//////////////

    @PreDestroy
    private void preDestory(){
        System.out.println("@PreDestory");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("destory");
    }

    private void destoryMethod(){
        System.out.println("destory-method");
    }
}
package webj2ee;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean '" + beanName + "' before-created : " + bean.toString());
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean '" + beanName + "' after-created : " + bean.toString());
        return bean;
    }
}
package webj2ee;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.Order;

@Order(2)
public class AnotherBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("AnotherBeanPostProcessor-Bean '" + beanName + "' before-created : " + bean.toString());
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("AnotherBeanPostProcessor-Bean '" + beanName + "' after-created : " + bean.toString());
        return bean;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <context:annotation-config/>
    <bean id="exampleBean" class="webj2ee.ExampleBean"
          init-method="initMethod"
          destroy-method="destoryMethod"></bean>

    <bean class="webj2ee.InstantiationTracingBeanPostProcessor"/>
    <bean class="webj2ee.AnotherBeanPostProcessor"/>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class);
        context.close();
    }
}

Customizing Configuration Metadata with a BeanFactoryPostProcessor:

  • org.springframework.beans.factory.config.BeanFactoryPostProcessor
  • BeanFactoryPostProcessor 操作的是 Bean 的配置元数据,所以它在 Spring 容器实例化 Bean 之前生效。
  • 如果有多个 BeanFactoryPostProcessor,可以通过 @Order 控制顺序。
  • 生效范围只是它所在的 Spring 容器。
  • PropertyOverrideConfigurer、PropertyPlaceholderConfigurer 就是 Spring 中自带的 BeanFactoryPostProcessor。

示例1:(PropertyPlaceholderConfigurer )

  • 用于实现配置项的外部化。
  • 元素 <context:property-placeholder/> 即代表 PropertyPlaceholderConfigurer 的一个子类。
  • PropertyPlaceholderConfigurer 不仅会查找自身的配置,而且会在 Java System 属性中查找(systemPropertiesMode 可以细粒度会控制搜索方式(nerver、fallback(default)、override)) 。
  • 使用 ${xyz} 形式表示(与 JSP EL、Ant EL、Log4j EL 表达式贴合)(注意与 EL 表达式 #{xyz} 作区分)
package webj2ee;
public interface LanePromptDao {
    LanePrompt getLanePromot(String promptId);
    void saveLanePrompt(String promptId, LanePrompt lanePrompt);
}
package webj2ee;

public class SessionLanePromptDao implements LanePromptDao {
    public SessionLanePromptDao() {
        System.out.println("SessionLanePromptDao");
    }

    @Override
    public LanePrompt getLanePromot(String promptId) {
        return null;
    }

    @Override
    public void saveLanePrompt(String promptId, LanePrompt lanePrompt) {

    }
}
package webj2ee;

public class RedisLanePromptDao implements LanePromptDao {
    public RedisLanePromptDao() {
        System.out.println("RedisLanePromptDao");
    }

    @Override
    public LanePrompt getLanePromot(String promptId) {
        return null;
    }

    @Override
    public void saveLanePrompt(String promptId, LanePrompt lanePrompt) {

    }
}
package webj2ee;

public class LanePrompt {
    private LanePromptDao dao;
    public void setDao(LanePromptDao dao) {
        this.dao = dao;
    }

    // ... business logic
}
// application.properties
dw.sef.SAVE_PROMPT_IN_REDIS=true
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <context:property-placeholder location="classpath:application.properties"/>
    <bean id="lanePromptDao"
          class="#{'${dw.sef.SAVE_PROMPT_IN_REDIS}'=='true' ?
                        'webj2ee.RedisLanePromptDao':
                        'webj2ee.SessionLanePromptDao'}"></bean>
    <bean id="lanePrompt" class="webj2ee.LanePrompt"
          p:dao-ref="lanePromptDao"/>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        LanePrompt lanePrompt = context.getBean("lanePrompt", LanePrompt.class);
        context.close();
    }
}

示例2:(PropertyOverrideConfigurer)

  • 跟 PropertyOverrideConfigurer 很类似,但作用是用于覆盖 Bean 中的属性;
  • 格式:beanName.property=value,支持嵌套:tom.fred.bob.sammy=123
  • 如果有多个 PropertyOverrideConfigurer,最后一个生效(覆盖策略)。
package webj2ee;

public class ExampleBean {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "ExampleBean{" +
                "name='" + name + '\'' +
                '}';
    }
}
## override.properties
## beanName.property=value
exampleBean.name=xiaogang
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <context:property-override location="classpath:override.properties"/>
    <bean id="exampleBean" class="webj2ee.ExampleBean"
          p:name="xiaohong"/>
</beans>
package webj2ee;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("context.xml");
        ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class);
        System.out.println(exampleBean);
        context.close();
    }
}

Customizing Instantiation Logic with a FactoryBean:

  • FactoryBean 是一个 Bean,只不过是用于生成其他 Bean 的工厂 Bean。
  • 常用于封装过于复杂的初始化逻辑;
  • Spring 中自带超过 50 个 FactoryBean 的实现。
  • 可通过 getBean("&xxx"),获取 FactoryBean,而不是它产生的 Bean 本身。

图:Spring 自带的 FactoryBean 实现类

package org.springframework.web.context.support;

import javax.servlet.ServletContext;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.lang.Nullable;
import org.springframework.web.context.ServletContextAware;

public class ServletContextParameterFactoryBean implements FactoryBean<String>, ServletContextAware {

  @Nullable
  private String initParamName;

  @Nullable
  private String paramValue;

  /**
   * Set the name of the ServletContext init parameter to expose.
   */
  public void setInitParamName(String initParamName) {
    this.initParamName = initParamName;
  }

  @Override
  public void setServletContext(ServletContext servletContext) {
    if (this.initParamName == null) {
      throw new IllegalArgumentException("initParamName is required");
    }
    this.paramValue = servletContext.getInitParameter(this.initParamName);
    if (this.paramValue == null) {
      throw new IllegalStateException("No ServletContext init parameter '" + this.initParamName + "' found");
    }
  }

  @Override
  @Nullable
  public String getObject() {
    return this.paramValue;
  }

  @Override
  public Class<String> getObjectType() {
    return String.class;
  }

  @Override
  public boolean isSingleton() {
    return true;
  }
}

太长了....先写到这....下一篇继续....

参考:

Spring Framework Runtime: https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/overview.html The IoC Container: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#spring-core Inversion of Control Containers and the Dependency Injection pattern: https://martinfowler.com/articles/injection.html 《Java Web 高级编程技术》 《Spring 入门经典》 《精通 Spring 4.x 企业应用开发实战》 《Spring5 高级编程》 《Spring实战(第3版)》

本文分享自微信公众号 - WebJ2EE(WebJ2EE)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Error creating bean,Failed to instantiate,NoClassDefFoundError

    common-base中的pom文件引入id-generator和id-generator-core的maven依赖

    chenchenchen
  • Composer系列之二

    启动:命令行输入composer 或 composer -v既能看到命令列表,一共35个。

    botkenni
  • 【小家java】java8新特性之---外部迭代和内部迭代(对比性能差异)

    最传统的方法是用Iterator,当然还以用for i、增强for循环等等。这一类方法叫做外部迭代,意为显式地进行迭代操作,即集合中的元素访问是由一个处于集合外...

    BAT的乌托邦
  • 【Rust日报】 2019-08-30 - Linux 未來可以使用 Rust 開發內核

    Josh Triplett (Linux主要開發者之一)在一次的演講提到了Rust的可能性,

    MikeLoveRust
  • LogBack的使用介绍

    Logback是由log4j创始人设计的另一个开源日志组件,官方网站: http://logback.qos.ch。它当前分为下面下个模块:

    周三不加班
  • 【小家java】剖析for、while、foreach、标签循环语句的控制( break,continue,return )

    java一共提供了3中循环语法:for循环(含增强for循环)、while循环、do…while循环。java8之后提供了基于stream的foreach循环,...

    BAT的乌托邦
  • js实现小数的算术运算方法

    <!DOCTYPE html> <html> <head> <title></title> </head> <body>

    botkenni
  • slf4j-api、slf4j-log4j12、log4j之间关系

    slf4j:Simple Logging Facade for Java,为java提供的简单日志Facade。Facade门面,更底层一点说就是接口。它允许用...

    chenchenchen
  • 开发Java这么久还不知深浅?

    实际开发场景中,你可能遇到过复制一个对象,而针对这个对象不应该影响被复制的对象,举个例子:

    用户1130025
  • 如何写出一个惊艳面试官的深拷贝

    最近经常看到很多 JavaScript手写代码的文章总结,里面提供了很多 JavaScriptApi的手写实现。

    ConardLi

扫码关注云+社区

领取腾讯云代金券