三种使用模式

纯xml模式

web.xml中的配置:

  1. <!DOCTYPE web-app PUBLIC
  2. "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  3. "http://java.sun.com/dtd/web-app_2_3.dtd" >
  4. <web-app>
  5. <display-name>Archetype Created Web Application</display-name>
  6. <!--配置Spring IoC容器的配置文件-->
  7. <context-param>
  8. <param-name>contextConfigLocation</param-name>
  9. <param-value>classpath:applicationContext.xml</param-value>
  10. </context-param>
  11. <!--使用监听器启动Spring IoC容器-->
  12. <listener>
  13. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  14. </listener>
  15. </web-app>

配置文件名为applicationContext.xml。

Spring IoC实例化Bean的三种方式

使用无参构造器

applicationContext.xml:

<bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils" />

获取:

ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
ConnectionUtils connectionUtils = (ConnectionUtils) applicationContext.getBean("connectionUtils");

静态方法

<bean id="connectionUtils" class="com.lagou.edu.factory.CreateBeanFactory" factory-method="getInstanceStatic" />

CreateBeanFactory.java

package com.lagou.edu.factory;

import com.lagou.edu.utils.ConnectionUtils;

public class CreateBeanFactory {

    // 静态方法
    public static ConnectionUtils getInstanceStatic() {
        return new ConnectionUtils();
    }

    // 实例化方法
    public ConnectionUtils getInstance() {
        return new ConnectionUtils();
    }
}

实例化方法

<bean id="createBeanFactory" class="com.lagou.edu.factory.CreateBeanFactory" />
<bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance" />

Bean标签属性

  • id:唯一标识。
  • class:创建bean对象的全限定类名。
  • name:给bean提供一个或多个名称,空格分隔。
  • factory-bean:指定创建当前bean对象的工厂bean的唯一标识,会使class属性失效。
  • factory-method:指定创建当前bean对象的工厂方法。如果配合factory-bean属性使用,会使class属性失效;如果配合class属性使用,则该方法必须是static。
  • scope:指定bean对象的作用范围,默认singleton。
  • init-method:指定bean对象的初始化方法,此方法会在bean对象装配后调用,必须为无参方法。
  • destroy-method:指定bean对象的销毁方法,此方法会在bean对象销毁前执行,只有scope为singleton的时候才起作用。

Bean的作用范围及生命周期

image.png
用scope定义bean的作用范围:

  • singleton:单例,IoC容器中只有一个该类对象,默认。
  • prototype:原型(多例),每次使用该类的对象(getBean),都返回给你一个新的对象。

其他几种很少使用。

DI依赖注入的xml配置

依赖注入分类

注入方式:

  • 构造函数注入
  • set方法注入

注入的数据类型:

  • 基本类型和String
  • 其他Bean类型
  • 复杂类型(集合类型),如Array,List,Set,Map,Properties。

    使用构造参数注入

    涉及的标签是constructor-arg,属性有:

  • name:给构造函数中指定名称的参数赋值。

  • index:给构造函数中指定索引位置的参数赋值。
  • value:指定基本类型或者String类型的数据。
  • ref:指定其他Bean类型的数据。

参考写法:
如JdbcAccountDaoImpl有构造器:

    private ConnectionUtils connectionUtils;
    private String name;
    private int sex;
    private float money;

        public JdbcAccountDaoImpl(ConnectionUtils connectionUtils, String name, int sex, float money) {
        this.connectionUtils = connectionUtils;
        this.name = name;
        this.sex = sex;
        this.money = money;
    }

xml配置可以为:

<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl" init-method="init" destroy-method="destroy">
  <constructor-arg index="0" ref="connectionUtils"/>
  <constructor-arg index="1" value="zhangsan"/>
  <constructor-arg index="2" value="1"/>
  <constructor-arg index="3" value="100.5"/>
</bean>
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl" init-method="init" destroy-method="destroy">
  <constructor-arg name="connectionUtils" ref="connectionUtils"/>
  <constructor-arg name="name" value="zhangsan"/>
  <constructor-arg name="sex" value="1"/>
  <constructor-arg name="money" value="100.6"/>
</bean>

set方法注入

涉及的标签是property,属性有:

  • name:指定注入时调用的set方法名称,不包含set这三个字母,Druid连接池指定属性名称。
  • value:指定注入的数据,支持基本类型和String类型。
  • ref:指定注入的数据,支持其他bean类型。

如JdbcAccountDaoImpl有一些setter:

    private ConnectionUtils connectionUtils;
    private String name;
    private int sex;
    private float money;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

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

    public void setSex(int sex) {
        this.sex = sex;
    }

    public void setMoney(float money) {
        this.money = money;
    }

xml配置可以为:

<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl" init-method="init" destroy-method="destroy">
    <property name="ConnectionUtils" ref="connectionUtils"/>
  <property name="name" value="zhangsan"/>
  <property name="sex" value="1"/>
  <property name="money" value="100.3"/>
</bean>

复杂数据类型的注入

集合分为两类:

  • List结构(数组结构)。List结构的集合数据注入时,arraylistset三个标签通用,value标签内部可以直接写值,也可以用bean标签配置一个对象,或者用ref标签引用一个已经配置的bean的唯一标识。
  • Map结构(键值对)。Map结构的集合数据注入时,map标签使用entry子标签实现数据注入,entry标签可以使用keyvalue属性指定存入map中的数据。使用value-ref属性指定已经配置好的bean的引用,同时entry标签中也可以使用ref标签,但是不能使用bean标签。而property标签中不能使用ref或者bean标签引用对象。

如JdbcAccountDaoImpl有一些setter:

    private String[] myArray;
    private Map<String,String> myMap;
    private Set<String> mySet;
    private Properties myProperties;

    public void setMyArray(String[] myArray) {
        this.myArray = myArray;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyProperties(Properties myProperties) {
        this.myProperties = myProperties;
    }

xml配置可以为:

<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcAccountDaoImpl" init-method="init" destroy-method="destroy">
  <property name="myArray">
    <array>
      <value>array1</value>
      <value>array2</value>
      <value>array3</value>
    </array>
  </property>

  <property name="myMap">
    <map>
      <entry key="key1" value="value1"/>
      <entry key="key2" value="value2"/>
    </map>
  </property>

  <property name="mySet">
    <set>
      <value>set1</value>
      <value>set2</value>
    </set>
  </property>

  <property name="myProperties">
    <props>
      <prop key="prop1">value1</prop>
      <prop key="prop2">value2</prop>
    </props>
  </property>
</bean>

xml与注解相结合模式

第三方jar中的bean定义在xml,如Druid数据库连接池。

    <context:property-placeholder location="classpath:jdbc.properties"/>

        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

创建jdbc.properties文件:

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/bank?&amp;useSSL=false&amp;serverTimezone=UTC
jdbc.username=root
jdbc.password=zhaoyiqi

自己开发的bean定义使用注解。

xml中标签与注解的对应(IoC)

xml形式 注解形式
标签 @Component(“accountDao”),注解加在类上。
bean的id属性内容直接配置在注解后面。
如果不配置,默认定义个这个bean的id为类的类名首字母小写。
另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository,分别用于控制层类、服务层类、dao层类的bean定义。
这四个注解的用法完全一样,只是为了更清晰的区分而已。
标签的scope属性 @Scope(“prototype”)
标签的init-method属性 @PostConstruct
标签的destroy-method属性 @PreDestroy

DI依赖注入的注解实现方式

开启注解扫描,base-package指定扫描的包路径:

<context:component-scan base-package="com.lagou.edu"/>

@Autowired

需要导入org.springframework.beans.factory.annotation.Autowired
自动装配,按类型注入。
如果按照类型无法唯一锁定对象,可以结合@Qualifier指定具体的id。

public class TransferServiceImpl {
    @Autowired
    @Qualifier(name="jdbcAccountDaoImpl")
    private AccountDao accountDao;
}

@Resource

默认按照名称注入,也可以按照类型注入。
在jdk11中已经移除,如果还想使用,需要引入jar包javax.annotation-api

纯注解模式

web.xml改为:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <!--告诉ContextloaderListener知道我们使用注解的方式启动ioc容器-->
  <context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
  </context-param>
  <!--配置启动类的全限定类名-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.lagou.edu.SpringConfig</param-value>
  </context-param>
  <!--使用监听器启动Spring的IOC容器-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

配置类为com.lagou.edu.SpringConfig。
对应注解:

  • @Configuration:表明当前类是一个配置类。
  • @ComponentScan:替代context:component-scan。
  • @PropertySource:引入外部属性配置文件。
  • @Import:引入其他配置类。
  • @Value:对变量赋值,可以直接赋值,也可以使用${}读取资源配置文件中的信息。
  • @Bean:将方法返回对象加入Spring IoC容器。

一个demo:

package com.lagou.edu;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;

import javax.sql.DataSource;

@Configuration
@ComponentScan({"com.lagou.edu"})
@PropertySource({"classpath:jdbc.properties"})
public class SpringConfig {

    @Value("${jdbc.driver}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean("dataSource")
    public DataSource createDataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }
}

高级特性

Lazy-init延迟加载

涉及bean标签的lazy-init属性,默认值为false,可以手动把默认值改为true:

<beans default-lazy-init="true">
    <!-- no beans will be eagerly pre-instantiated... -->
</beans>

为false的时候,spring启动时,对应的bean立刻进行实例化。
为true的时候,bean不会在ApplicationContext启动时提前被实例化,而是第一次向容器通过getBean索取bean时,才进行实例化。
注意,如果一个bean的scope属性值为prototype,即使设置了lazy-init=”false”,容器启动时也不会实例化bean,在getBean的时候才实例化。
也可以使用注解@Lazy
使用场景:

  1. 开启延迟加载,可以一定程度上提高容器启动和运转性能。
  2. 对于不常使用的bean设置延迟加载,不必一开始就占用资源。

FactoryBean

(BeanFactory是容器的顶级接口,定义了容器的一些基础行为,负责生产和管理Bean的一个工厂,一般使用它下面的子接口类型,如ApplicationContext。)
Spring中的Bean有两种:普通Bean和工厂Bean(FactoryBean).
FactoryBean可以生成某一个类型的Bean实例并返回,即我们可以借助FactoryBean自定义Bean的创建过程。
Bean创建的三种方式中,静态方法和实例化方法与FactoryBean的作用类似。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

public interface FactoryBean<T> {
    @Nullable
    // 返回FactoryBean创建的Bean实例
    T getObject() throws Exception;

    @Nullable
    // 返回FactoryBean创建的Bean类型
    Class<?> getObjectType();

    // 如果isSingleton返回true,则getObject()返回的实例会放到Spring IoC容器的单例缓存池中
    default boolean isSingleton() {
        return true;
    }
}

一个demo:

package com.lagou.edu.factory;

import com.lagou.edu.pojo.Company;
import org.springframework.beans.factory.FactoryBean;

/**
 * @author 应癫
 * */
public class CompanyFactoryBean implements FactoryBean<Company> {
    private String companyInfo; // 公司名称,地址,规模

    public void setCompanyInfo(String companyInfo) {
        this.companyInfo = companyInfo;
    }

    @Override
    public Company getObject() throws Exception {
        Company company = new Company();
        String[] strings = companyInfo.split(",");
        company.setName(strings[0]);
        company.setAddress(strings[1]);
        company.setScale(Integer.parseInt(strings[2]));
        return company;
    }

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

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

后置处理器

Spring提供了两种后处理bean的扩展接口:

  • BeanPostProcessor。在Bean对象实例化之后使用。
  • BeanFactoryPostProcessor。在BeanFactory初始化之后使用。

对象不一定是spring bean,而spring bean一定是个对象。

SpringBean生命周期

  1. 根据配置情况调用Bean构造方法或工厂方法实例化Bean;
  2. 利用依赖注入完成Bean中所有属性值的配置注入;
  3. 如果Bean实现了BeanNameAware接口,则Spring调用Bean的setBeanName方法传入当前Bean的id值;
  4. 如果Bean实现了BeanFactoryAware接口,则Spring调用setBeanFactory方法传入当前工厂实例的引用;
  5. 如果Bean实现了ApplicationContextAware接口,则Spring调用setApplicationContext方法传入当前ApplicationContext实例的引用;
  6. 如果BeanPostProcessor和Bean关联,则Spring将调用该接口的预初始化方法postProcessBeforeInitialization对Bean进行加工操作,Spring AOP就是用它实现的;
  7. 如果Bean实现了InitializingBean接口,则Spring将调用afterPropertiesSet方法;
  8. 如果在配置文件中通过init-method属性指定了初始化方法,则调用;
  9. 如果BeanPostFactory和Bean关联,则Spring将调用该接口的初始化方法postProcessAfterInitialization,此时,Bean已经可以被应用系统使用了;
  10. 如果在bean标签中指定了该Bean的作用范围scope=”singleton”,则将该Bean放入Spring IoC容器的缓存池中,将触发Spring对该Bean的生命周期管理;如果scope=”prototype”,则将该Bean交给调用者;
  11. 如果Bean实现了DisposableBean接口,则Spring会调用destroy方法将Spring中的Bean销毁;如果在配置文件中通过destroy-method属性指定了Bean的销毁方法,则Spring将调用该方法对Bean进行销毁。

BeanPostProcessor

可以针对某个具体的Bean。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

接口中的两个方法分别在Bean的初始化方法前和初始化方法后执行。这个初始化方法就是由init-method属性所指定的方法。
定义一个类实现BeanPostProcessor,默认会对整个Spring容器中所有的bean进行处理。
若要针对具体的某个bean,可以通过方法参数判断:

  • Object bean是每个bean的实例。
  • String beanName是每个bean的name或者id属性的值。

可以通过第二个参数来判断要处理的具体的bean。
一个demo:

package com.lagou.edu.pojo;

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

/**
 * @author 应癫
 *
 * 拦截实例化之后的对象(实例化了并且属性注入了)
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if("lazyResult".equalsIgnoreCase(beanName)) {
            System.out.println("MyBeanPostProcessor  before方法拦截处理lazyResult");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if("lazyResult".equalsIgnoreCase(beanName)) {
            System.out.println("MyBeanPostProcessor  after方法拦截处理lazyResult");
        }
        return bean;
    }
}

BeanFactoryPostProcessor

可以针对整个Bean的工厂进行处理。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;

@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}

接口中方法的参数中定义了一些方法:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.beans.factory.config;

import java.util.Iterator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.lang.Nullable;

public interface ConfigurableListableBeanFactory extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory {
    void ignoreDependencyType(Class<?> var1);

    void ignoreDependencyInterface(Class<?> var1);

    void registerResolvableDependency(Class<?> var1, @Nullable Object var2);

    boolean isAutowireCandidate(String var1, DependencyDescriptor var2) throws NoSuchBeanDefinitionException;

    BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException;

    Iterator<String> getBeanNamesIterator();

    void clearMetadataCache();

    void freezeConfiguration();

    boolean isConfigurationFrozen();

    void preInstantiateSingletons() throws BeansException;
}

我们可以根据getBeanDefinition方法找到我们定义的bean的BeanDefinition对象,然后就可以对定义的属性进行修改。

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.beans.factory.config;

import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.core.AttributeAccessor;
import org.springframework.lang.Nullable;

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";
    int ROLE_APPLICATION = 0;
    int ROLE_SUPPORT = 1;
    int ROLE_INFRASTRUCTURE = 2;

    void setParentName(@Nullable String var1);

    @Nullable
    String getParentName();

    void setBeanClassName(@Nullable String var1);

    @Nullable
    String getBeanClassName();

    void setScope(@Nullable String var1);

    @Nullable
    String getScope();

    void setLazyInit(boolean var1);

    boolean isLazyInit();

    void setDependsOn(@Nullable String... var1);

    @Nullable
    String[] getDependsOn();

    void setAutowireCandidate(boolean var1);

    boolean isAutowireCandidate();

    void setPrimary(boolean var1);

    boolean isPrimary();

    void setFactoryBeanName(@Nullable String var1);

    @Nullable
    String getFactoryBeanName();

    void setFactoryMethodName(@Nullable String var1);

    @Nullable
    String getFactoryMethodName();

    ConstructorArgumentValues getConstructorArgumentValues();

    default boolean hasConstructorArgumentValues() {
        return !this.getConstructorArgumentValues().isEmpty();
    }

    MutablePropertyValues getPropertyValues();

    default boolean hasPropertyValues() {
        return !this.getPropertyValues().isEmpty();
    }

    void setInitMethodName(@Nullable String var1);

    @Nullable
    String getInitMethodName();

    void setDestroyMethodName(@Nullable String var1);

    @Nullable
    String getDestroyMethodName();

    void setRole(int var1);

    int getRole();

    void setDescription(@Nullable String var1);

    @Nullable
    String getDescription();

    boolean isSingleton();

    boolean isPrototype();

    boolean isAbstract();

    @Nullable
    String getResourceDescription();

    @Nullable
    BeanDefinition getOriginatingBeanDefinition();
}

方法名字类似bean标签的属性。
如,setBeanClassName就对应bean标签中的class属性。
因此拿到BeanDefinition对象就可以手动修改bean标签中所定义的属性值。
(我们在XML中定义的bean标签,会被Spring解析为一个JavaBean,这个JavaBean就是BeanDefinition。)
注意,调用BeanFactoryPostProcessor的方法时,bean还没有实例化,此时bean刚被解析成BeanDefinition对象。