基础

Spring框架的IOC有三种实现,分别是:

  • 纯xml方式
  • 半xml半注解方式
  • 纯注解方式

对于xml来说就是beans.xml和BeanFactory了,beans.xml中定义了需要实例化对象的全限定类名以及类之间的依赖关系;至于BeanFactory则是通过反射技术来实例化对象以及维护对象之间的依赖关系。
对于不同的方式来说,使用时通过不同的加载类去启动,下面使用不同的方式来演示和应用。

这里需要知道的是Spring IOC的实现方式有三种,以及beans.xml与BeanFactory的作用分别是什么?其实xml就是定义,工厂就是实例化与维护。

BeanFactory与ApplicationContext的区别

BeanFactory是Spring IoC容器的顶层接口,它定义了Ioc容器的一些基本规范;ApplicationContext是BeanFactory的一个子接口,拥有BeanFactory的所有功能。

BeanFactory是Spring IoC的基础容器,ApplicationContext是容器中的高级接口,拥有比BeanFactory更多的功能。

Spring IOC xml方式

applicationContext.xml的定义,也就是beans的定义,里面存放了类的全限定类名以及属性依赖的配置。
两种模式启动:

Java SE模式

SE模式只需要 ApplicationContext 加载beans的配置文件就可以,如下:

  1. ApplicationContext seApplicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
  2. // 根据配置文件的绝对路径来获取bean容器,不推荐
  3. ApplicationContext seApplicationContext1 = new FileSystemXmlApplicationContext("配置文件的绝对路径");
  4. // 以上两种方式推荐第一种
  5. // 获取了ApplicationContext之后就可以使用 getBean 的多态方法来获取容器中的对象

需要注意的是 ApplicationContext.xml内容的编写,这里只展示约束,不展示具体的内容:

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

Web模式

web模式的配置是在web.xml中存放的,简单点,web启动是通过 ContextLoaderListener 来进行启动的,所以我们需要配置该监听器,并且配置该监听器需要的配置文件就可以。这个在web容器启动就会加载监听器。

  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. <!--ContextLoaderListener 基于 contextClass 和 contextConfigLocation 来创建一个web应用,这里配置的就是 contextConfigLocation 配置文件-->
  7. <context-param>
  8. <param-name>contextConfigLocation</param-name>
  9. <param-value>classpath:applicationContext.xml</param-value>
  10. </context-param>
  11. <!--监听器启动 IoC容器-->
  12. <listener>
  13. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  14. </listener>
  15. </web-app>

xml属性以及创建方式

xml下的三种创建方式

第一种:使用无参构造,也就是我们常用的方式

  1. <bean id="user" class="com.wangzhi.domain.User"/>
  2. <bean id="beanFactory" class="com.wangzhi.util.BeanFactory"/>

第二种:使用类的静态方法

  1. <bean id="userController" class="com.wangzhi.util.BeanFactory" factory-method="createUserController"/>

第三种:使用类的实例方法

  1. <bean id="beanFactory" class="com.wangzhi.util.BeanFactory"/>
  2. <bean id="userService" factory-bean="beanFactory" factory-method="createUserService"/>

这里展示使用的 BeanFactory 类

  1. public class BeanFactory {
  2. public static UserController createUserController() {
  3. return new UserController();
  4. }
  5. public UserService createUserService() {
  6. return new UserServiceImpl();
  7. }
  8. }

bean的相关属性

scope属性:bean的作用域,也算是生命周期吧。常用的值有两个:singleton和prototype;前者是默认值,表示单例模式,也就是一个容器一个对象,生命周期跟随容器的生命周期;后者表示多例模式,每获取一次,则创建一个对象,生命周期有Java内存机制来管理,容器只负责创建,不负责销毁。
id属性:对象的唯一标识
name属性:给对象起别名
class属性:对象的全限定类型
factory-bean属性:容器已经管理的对象,如上面创建bean的第三种方式
factory-method属性:创建对象执行的方法,如上面创建bean的第二种和第三种方式
init-method:初始化bean时执行的方法
destroy-method:销毁bean时执行的方法
depends-on属性:强制依赖,指定另一个bean,表示初始化该bean之前强制初始化其它bean
代码演示:

  1. // 先看一下xml的内容
  2. <beans>
  3. <bean id="user" class="com.wangzhi.domain.User" scope="prototype" init-method="init" destroy-method="destroy"/>
  4. <bean id="beanFactory" class="com.wangzhi.util.BeanFactory" init-method="init" destroy-method="destroy"/>
  5. <bean id="userController" class="com.wangzhi.util.BeanFactory" factory-method="createUserController"/>
  6. <bean id="userService" factory-bean="beanFactory" factory-method="createUserService"/>
  7. </beans>
  1. // 看一下测试类的内容
  2. @Test
  3. public void testStart() {
  4. ClassPathXmlApplicationContext seApplicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
  5. User user = (User) seApplicationContext.getBean("user");
  6. System.out.println( "第一次获取user:" + user);
  7. User user1 = (User) seApplicationContext.getBean("user");
  8. System.out.println("第二次获取user: " + user1);
  9. System.out.println("比较两次的user: " + (user == user1));
  10. BeanFactory beanFactory = (BeanFactory) seApplicationContext.getBean("beanFactory");
  11. System.out.println( "第一次获取beanFactory:" + beanFactory);
  12. BeanFactory beanFactory1 = (BeanFactory) seApplicationContext.getBean("beanFactory");
  13. System.out.println("第二次获取beanFactory: " + beanFactory1);
  14. System.out.println("比较两次的beanFactory: " + (beanFactory1 == beanFactory));
  15. seApplicationContext.close();
  16. }
  17. // 最后请看输出结果
  18. 初始化BeanFactory方法
  19. 初始化user方法
  20. 第一次获取usercom.wangzhi.domain.User@2b91004a
  21. 初始化user方法
  22. 第二次获取user: com.wangzhi.domain.User@20ccf40b
  23. 比较两次的user: false
  24. 第一次获取beanFactorycom.wangzhi.util.BeanFactory@2fb3536e
  25. 第二次获取beanFactory: com.wangzhi.util.BeanFactory@2fb3536e
  26. 比较两次的beanFactory: true
  27. 销毁BeanFactory方法

关于结果的说明:

  1. 为什么会先输出beanFactory的初始化方法,因为我们xml的配置中,需要创建userService对象,用到了beanFactory
  2. 为什么我们在销毁容器的时候,没有执行User的destroy方法,因为我们的user对象的作用域或者生命周期设置的是 prototype,是每次获取都创建一个对象,并且该对象不会被容器管理,由Java管理,也就是这种模式下,容器只负责创建,不负责销毁。

依赖注入的回顾

这里主要回顾使用xml进行依赖注入,各种简单类型、复杂类型等;以及注入的方式有两种:set注入和构造器注入。直接进行代码展示

  1. <beans>
  2. <bean id="user" class="com.wangzhi.domain.User" scope="prototype" init-method="init" destroy-method="destroy">
  3. <property name="name" value="王智"/>
  4. <property name="sex" value="男"/>
  5. <property name="age" value="27"/>
  6. <property name="myArr">
  7. <array>
  8. <value>1</value>
  9. <value>2</value>
  10. <value>3</value>
  11. </array>
  12. </property>
  13. <property name="myList">
  14. <list>
  15. <value>4</value>
  16. <value>5</value>
  17. <value>6</value>
  18. </list>
  19. </property>
  20. <property name="myMap">
  21. <map>
  22. <entry key="wangzhi" value="男"/>
  23. <entry key="qianer" value="女"/>
  24. <entry key="huanhuan" value="女"/>
  25. </map>
  26. </property>
  27. <property name="myProperties">
  28. <props>
  29. <prop key="pro1">pro1Value</prop>
  30. <prop key="pro2">pro2Value</prop>
  31. <prop key="pro3">pro3Value</prop>
  32. </props>
  33. </property>
  34. <property name="userService" ref="userService"/>
  35. </bean>
  36. <bean id="user1" class="com.wangzhi.domain.User">
  37. <constructor-arg name="age" value="27"></constructor-arg>
  38. <constructor-arg name="name" value="王智"></constructor-arg>
  39. <constructor-arg name="sex" value="男"></constructor-arg>
  40. </bean>
  41. <bean id="beanFactory" class="com.wangzhi.util.BeanFactory" init-method="init" destroy-method="destroy"/>
  42. <bean id="userService" factory-bean="beanFactory" factory-method="createUserService"/>
  43. </beans>

代码测试:

  1. @Test
  2. public void testDI() {
  3. ClassPathXmlApplicationContext seApplicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
  4. User user1 = (User)seApplicationContext.getBean("user1");
  5. System.out.println(user1);
  6. User user = (User)seApplicationContext.getBean("user");
  7. System.out.println(user);
  8. }
  9. // 执行结果
  10. 初始化BeanFactory方法
  11. User{name='王智', age=27, sex='男', myArr=null, myList=null, myMap=null, myProperties=null, userService=null}
  12. 初始化user方法
  13. User{name='王智', age=27, sex='男', myArr=[1, 2, 3], myList=[4, 5, 6], myMap={wangzhi=男, qianer=女, huanhuan=女}, myProperties={pro1=pro1Value, pro2=pro2Value, pro3=pro3Value}, userService=com.wangzhi.service.impl.UserServiceImpl@68c9133c}

为什么没有销毁方法,原因就是我这次的测试并没有关闭容器,scope默认是singleton,生命周期是更随容器的生命周期,所以没有关闭容器对向不会销毁。

xml与注解结合

结合的方式话就要区分哪些使用xml来配置,哪些使用注解来加载。针对于这个问题,第三方的jar包配置通过xml来进行配置;我们自定义的类使用注解来进行加载。
这里以jdbc数据源为例,除了数据源之外,其余的全部转化为注解的方式。

注解

对于注解有三种:

  • @Required:作用在setter方法上,已经弃用,不推荐使用
  • @Autowired:作用于成员变量声明上、setter方法上以及构造函数上,注入方式为byType
  • @Resource:Java提供的注解,在jdk11中已经被移除,不推荐使用,注入方式为byName

Autowired通常在成员变量上使用时,与Qualifier(“value”)一起使用,防止出现多个type相同的bean。

注解与xml的对应

  • 标签:对应了注解@Component,还有三个衍生注解:@Controller、@Service、@Repository,其实注解没有什么不同,只是为了区分不同的层而已。
  • scope属性:对应@Scope(“singleton”),作用在类上
  • init-method属性:对应了@PostConstruct,作用在方法上,初始化回调,在jdk11中删除
  • destroy-method属性:对应了@PreDestroy,作用于方法上,销毁回调,在11中删除

注解的使用需要利用context标签来进行注解包扫描:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. https://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. https://www.springframework.org/schema/context/spring-context.xsd">
  9. <context:component-scan base-package="com.wangzhi"/>
  10. </beans>

纯注解方式

也就是将第三方的jar也配置成注解的形式,也就是添加配置类,常用的注解如下:

  • @Configuration:表示当前类是一个注解类
  • @PropertySource:引入外部资源文件,值为数组
  • @ComponentScan:用来声明注解扫描的包路径,值为数组
  • @Import:可以引入其它配置文件,这样可以再web.xml的配置中添加一个配置类就可以了

这里我们演示将DataSource使用注解的形式加入到Spring IoC容器中管理:

  1. @Configuration
  2. @ComponentScan({"com.wangzhi"})
  3. @PropertySource({"classpath:jdbc.properties"})
  4. public class SpringConfig {
  5. private DruidDataSource druidDataSource;
  6. @Value("${jdbc.driver}")
  7. private String driverClass;
  8. @Value(("${jdbc.url}"))
  9. private String url;
  10. @Value("${jdbc.user}")
  11. private String username;
  12. @Value("${jdbc.password}")
  13. private String password;
  14. @Bean
  15. public DataSource createDataSource() {
  16. druidDataSource = new DruidDataSource();
  17. druidDataSource.setDriverClassName(driverClass);
  18. druidDataSource.setUsername(username);
  19. druidDataSource.setPassword(password);
  20. druidDataSource.setUrl(url);
  21. return druidDataSource;
  22. }
  23. }

如上就全部使用注解的形式了,那se的时候获取Spring IoC容器的方式就变为 ApplicationContext seApplicationContext = ``new ``AnnotationConfigApplicationContext``(``SpringConfig.``class``)``**;** SpringConfig就是我们书写的配置类.

如果使用web容器启动呢?比如说tomcat,那就需要在web.xml配置监听器以及contextClass和contextConfigLocation,contextClass表名是纯注解开发,contextConfigLocation表示配置类的路径。

  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. <!--ContextLoaderListener 基于 contextClass 和 contextConfigLocation 来创建一个web应用,这里配置的就是 contextConfigLocation 配置文件-->
  7. <!--<context-param>
  8. <param-name>contextConfigLocation</param-name>
  9. <param-value>classpath:applicationContext.xml</param-value>
  10. </context-param>-->
  11. <!--表名是使用纯注解的开发方式-->
  12. <context-param>
  13. <param-name>contextClass</param-name>
  14. <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
  15. </context-param>
  16. <context-param>
  17. <param-name>contextConfigLocation</param-name>
  18. <param-value>com.wangzhi.config.SpringConfig</param-value>
  19. </context-param>
  20. <!--监听器启动 IoC容器-->
  21. <listener>
  22. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  23. </listener>
  24. </web-app>

高级特性

lazy_init

延迟加载的属性,作用的位置

  • 标签头内,表名当前xml中的所有bean
  • 表明当前bean的加载属性,优先级高于beans
  • 使用注解的时候放在类上 @Lazy,也就是lazy-init=”true”

意思就是是否在容器初始化的时候加载bean,否则就是在使用的时候加载bean,默认值是false,也就是容器初始化的时候加载。
使用场景其实就是针对于我们不常用的bean,可以放在使用的时候再加载,这样可以减少资源的利用。

重点注意:lazy_init只对scope为singleton的bean起作用,因为scope为prototype的bean不受spring容器管理,spring容器只负责创建,不负责管理。

FactoryBean

这里要说BeanFactory和FactoryBean,BeanFactory是Spring容器的顶层接口,定义了Spring容器的基本行为,我们一般使用它的子实现;FactoryBean也是一个顶层接口,与BeanFactory没有任何关系,它的作用是帮助我们生成复杂的对象,然后放入容器中。
一般用于Spring集成第三方jar包中,重点就是生成复杂的对象,这里写个简单的例子:

  1. public interface FactoryBean<T> {
  2. /**
  3. * The name of an attribute that can be
  4. * {@link org.springframework.core.AttributeAccessor#setAttribute set} on a
  5. * {@link org.springframework.beans.factory.config.BeanDefinition} so that
  6. * factory beans can signal their object type when it can't be deduced from
  7. * the factory bean class.
  8. * @since 5.2
  9. */
  10. String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
  11. // 用来获取我们生成的bean对象放入容器
  12. @Nullable
  13. T getObject() throws Exception;
  14. // 获取生成的bean类型
  15. @Nullable
  16. Class<?> getObjectType();
  17. // 是否为单例
  18. default boolean isSingleton() {
  19. return true;
  20. }
  21. }

下面为测试,假设我们的Company是一个复杂的类:

  1. /**
  2. * 假设当前对象是复杂对象
  3. */
  4. public class Company {
  5. private String name;
  6. private String address;
  7. public String getName() {
  8. return name;
  9. }
  10. public void setName(String name) {
  11. this.name = name;
  12. }
  13. public String getAddress() {
  14. return address;
  15. }
  16. public void setAddress(String address) {
  17. this.address = address;
  18. }
  19. @Override
  20. public String toString() {
  21. return "Company{" +
  22. "name='" + name + '\'' +
  23. ", address='" + address + '\'' +
  24. '}';
  25. }
  26. }
  27. // 定义Company工厂
  28. public class CompanyFactoryBean implements FactoryBean<Company> {
  29. private String companyInfo;
  30. public void setCompanyInfo(String companyInfo) {
  31. this.companyInfo = companyInfo;
  32. }
  33. @Override
  34. public Company getObject() throws Exception {
  35. Company company = new Company();
  36. String[] split = companyInfo.split(",");
  37. company.setName(split[0]);
  38. company.setAddress(split[1]);
  39. return company;
  40. }
  41. @Override
  42. public Class<?> getObjectType() {
  43. return Company.class;
  44. }
  45. @Override
  46. public boolean isSingleton() {
  47. return true;
  48. }
  49. }

在xml中定义Bean

  1. <bean id="company" class="com.wangzhi.util.CompanyFactoryBean">
  2. <property name="companyInfo" value="师悦,未央区"/>
  3. </bean>

测试类:

  1. @Test
  2. public void testFactoryBean() {
  3. ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
  4. Object company = applicationContext.getBean("company");
  5. System.out.println(company);
  6. Object contextBean = applicationContext.getBean("&company");
  7. System.out.println(contextBean);
  8. }
  9. // 输出的结果
  10. Company{name='师悦', address='未央区'}
  11. com.wangzhi.util.CompanyFactoryBean@14028087

可以看到我们的Bean的class虽然是CompanyFactoryBean,但是使用容器获取的结果是Company对象,说明FactoryBean就是帮助我们生成对象的,如果一定要获取CompanyFactoryBean,需要在获取bean的时候,id前面加上&

后置处理器

Spring提供了两种后处理bean的扩展接口,分别为 BeanPostProcessor 和 BeanFactoryPostProcessor,区别在于 前者是在Bean实例化之后进行后置处理来做一些事情;后者是在 BeanFactory 初始化之后进行后置处理一些事情。BeanPostProcessor 可以拦截所有Bean,也可以拦截单个bean。
这个重点要说的是Bean的生命周期