前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring入门:The IoC Container,实践篇(上)

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

作者头像
WEBJ2EE
发布2019-09-17 15:57:07
7210
发布2019-09-17 15:57:07
举报
文章被收录于专栏:WebJ2EEWebJ2EE

图文无关

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

最最最基础的示例:

代码语言:javascript
复制
package webj2ee;
public class HelloWorld { // Bean
    public void sayHello(){
        System.out.println("Hello World!");
    }
}
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
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;
  • 可以指定多个别名;

示例:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="hello" name="hello1, hello2, hello3" class="webj2ee.Hello"/>
    <alias name="hello" alias="hello4"/>
</beans>
代码语言:javascript
复制
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))
代码语言:javascript
复制
/**
 * 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:(构造函数)

代码语言:javascript
复制
package webj2ee;

public class HelloWorld {
    public void sayHello(){
        System.out.println("Hello World!");
    }
}
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="helloWorld" class="webj2ee.HelloWorld"/>
</beans>
代码语言:javascript
复制
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)

代码语言:javascript
复制
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!");
    }
}
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="helloWorld" class="webj2ee.HelloWorld" factory-method="getInstance"/>
</beans>
代码语言:javascript
复制
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)

代码语言:javascript
复制
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!");
    }
}
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

    <bean id="helloWorldGetter" class="webj2ee.HelloWorld"/>
    <bean id="helloWorld" factory-bean="helloWorldGetter" factory-method="getInstance"/>
</beans>
代码语言:javascript
复制
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 注解)
代码语言:javascript
复制
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 标记构造函数参数的顺序

代码语言:javascript
复制
<?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 标记构造函数参数的顺序

代码语言:javascript
复制
<?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 标记构造函数参数的顺序

代码语言:javascript
复制
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 + '\'' +
                '}';
    }
}
代码语言:javascript
复制
<?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:静态工厂方法——构造器注入

代码语言:javascript
复制
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 + '\'' +
                '}';
    }
}
代码语言:javascript
复制
<?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:构造器注入——循环依赖

代码语言:javascript
复制
package webj2ee;
public class Abean {
    public Abean(Bbean b){ }
}
代码语言:javascript
复制
package webj2ee;
public class Bbean {
    public  Bbean(Abean a){ }
}
代码语言:javascript
复制
<?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> 指定基本数据类型;

代码语言:javascript
复制
<?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> 语法;

代码语言:javascript
复制
<?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);

代码语言:javascript
复制
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 +
                '}';
    }
}
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
<?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:

代码语言:javascript
复制
<?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:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean">
        <constructor-arg>
            <ref parent="props"/>
        </constructor-arg>
    </bean>
</beans>
代码语言:javascript
复制
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 特性。

示例:

代码语言:javascript
复制
package webj2ee;
public class DependOnBeanA {
    public DependOnBeanA() {
        System.out.println("DependOnBeanA Init Success!");
    }
}
代码语言:javascript
复制
package webj2ee;
public class DependOnBeanB {
    public DependOnBeanB(){
        System.out.println("DependOnBeanB Init Success!");
    }
}
代码语言:javascript
复制
package webj2ee;
public class ExampleBean {
    public ExampleBean(){
        System.out.println("ExampleBean Init Success!");
    }
}
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
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 特性;
代码语言:javascript
复制
package webj2ee;

public class NotLazyBean {
    public NotLazyBean(){
        System.out.println("NotLazyBean Init Success!");
    }
}
代码语言:javascript
复制
package webj2ee;

public class LazyBean {
    public LazyBean(){
        System.out.println("LazyBean Init Success!");
    }
}
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
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;

代码语言:javascript
复制
package webj2ee;
public class PrototypeBean {
}
代码语言:javascript
复制
package webj2ee;

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

    public PrototypeBean getNewPrototypeBean(){
        return prototypeBean;
    }
}
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
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 实现;

代码语言:javascript
复制
package webj2ee;

public class PrototypeBean {
}
代码语言:javascript
复制
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;
    }
}
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="singletonBean" class="webj2ee.SingletonBean"/>
    <bean id="prototypeBean" class="webj2ee.PrototypeBean" scope="prototype"></bean>
</beans>
代码语言:javascript
复制
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)

代码语言:javascript
复制
package webj2ee;

public class PrototypeBean {
}
代码语言:javascript
复制
package webj2ee;

public abstract class SingletonBean {
    public abstract PrototypeBean getNewPrototypeBean();
}
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
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

示例:

代码语言:javascript
复制
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");
    }
}
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
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;

示例:

代码语言:javascript
复制
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);
    }
}
代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <bean id="exampleBean" class="webj2ee.ExampleBean"></bean>
</beans>
代码语言:javascript
复制
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 包装代理类的。

示例:

代码语言:javascript
复制
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");
    }
}
代码语言:javascript
复制
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;
    }
}
代码语言:javascript
复制
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;
    }
}
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
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} 作区分)
代码语言:javascript
复制
package webj2ee;
public interface LanePromptDao {
    LanePrompt getLanePromot(String promptId);
    void saveLanePrompt(String promptId, LanePrompt lanePrompt);
}
代码语言:javascript
复制
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) {

    }
}
代码语言:javascript
复制
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) {

    }
}
代码语言:javascript
复制
package webj2ee;

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

    // ... business logic
}
代码语言:javascript
复制
// application.properties
dw.sef.SAVE_PROMPT_IN_REDIS=true
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
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,最后一个生效(覆盖策略)。
代码语言:javascript
复制
package webj2ee;

public class ExampleBean {
    private String name;

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

    @Override
    public String toString() {
        return "ExampleBean{" +
                "name='" + name + '\'' +
                '}';
    }
}
代码语言:javascript
复制
## override.properties
## beanName.property=value
exampleBean.name=xiaogang
代码语言:javascript
复制
<?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>
代码语言:javascript
复制
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 实现类

代码语言:javascript
复制
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版)》

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Initialization Callbacks:
  • Destruction Callbacks:
  • xxAware 接口:
  • Customizing Beans by Using a BeanPostProcessor:
  • Customizing Instantiation Logic with a FactoryBean:
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档