基础
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的配置文件就可以,如下:
ApplicationContext seApplicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 根据配置文件的绝对路径来获取bean容器,不推荐
ApplicationContext seApplicationContext1 = new FileSystemXmlApplicationContext("配置文件的绝对路径");
// 以上两种方式推荐第一种
// 获取了ApplicationContext之后就可以使用 getBean 的多态方法来获取容器中的对象
需要注意的是 ApplicationContext.xml内容的编写,这里只展示约束,不展示具体的内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
Web模式
web模式的配置是在web.xml中存放的,简单点,web启动是通过 ContextLoaderListener 来进行启动的,所以我们需要配置该监听器,并且配置该监听器需要的配置文件就可以。这个在web容器启动就会加载监听器。
<!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 基于 contextClass 和 contextConfigLocation 来创建一个web应用,这里配置的就是 contextConfigLocation 配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--监听器启动 IoC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
xml属性以及创建方式
xml下的三种创建方式
第一种:使用无参构造,也就是我们常用的方式
<bean id="user" class="com.wangzhi.domain.User"/>
<bean id="beanFactory" class="com.wangzhi.util.BeanFactory"/>
第二种:使用类的静态方法
<bean id="userController" class="com.wangzhi.util.BeanFactory" factory-method="createUserController"/>
第三种:使用类的实例方法
<bean id="beanFactory" class="com.wangzhi.util.BeanFactory"/>
<bean id="userService" factory-bean="beanFactory" factory-method="createUserService"/>
这里展示使用的 BeanFactory 类
public class BeanFactory {
public static UserController createUserController() {
return new UserController();
}
public UserService createUserService() {
return new UserServiceImpl();
}
}
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
代码演示:
// 先看一下xml的内容
<beans>
<bean id="user" class="com.wangzhi.domain.User" scope="prototype" init-method="init" destroy-method="destroy"/>
<bean id="beanFactory" class="com.wangzhi.util.BeanFactory" init-method="init" destroy-method="destroy"/>
<bean id="userController" class="com.wangzhi.util.BeanFactory" factory-method="createUserController"/>
<bean id="userService" factory-bean="beanFactory" factory-method="createUserService"/>
</beans>
// 看一下测试类的内容
@Test
public void testStart() {
ClassPathXmlApplicationContext seApplicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
User user = (User) seApplicationContext.getBean("user");
System.out.println( "第一次获取user:" + user);
User user1 = (User) seApplicationContext.getBean("user");
System.out.println("第二次获取user: " + user1);
System.out.println("比较两次的user: " + (user == user1));
BeanFactory beanFactory = (BeanFactory) seApplicationContext.getBean("beanFactory");
System.out.println( "第一次获取beanFactory:" + beanFactory);
BeanFactory beanFactory1 = (BeanFactory) seApplicationContext.getBean("beanFactory");
System.out.println("第二次获取beanFactory: " + beanFactory1);
System.out.println("比较两次的beanFactory: " + (beanFactory1 == beanFactory));
seApplicationContext.close();
}
// 最后请看输出结果
初始化BeanFactory方法
初始化user方法
第一次获取user:com.wangzhi.domain.User@2b91004a
初始化user方法
第二次获取user: com.wangzhi.domain.User@20ccf40b
比较两次的user: false
第一次获取beanFactory:com.wangzhi.util.BeanFactory@2fb3536e
第二次获取beanFactory: com.wangzhi.util.BeanFactory@2fb3536e
比较两次的beanFactory: true
销毁BeanFactory方法
关于结果的说明:
- 为什么会先输出beanFactory的初始化方法,因为我们xml的配置中,需要创建userService对象,用到了beanFactory
- 为什么我们在销毁容器的时候,没有执行User的destroy方法,因为我们的user对象的作用域或者生命周期设置的是 prototype,是每次获取都创建一个对象,并且该对象不会被容器管理,由Java管理,也就是这种模式下,容器只负责创建,不负责销毁。
依赖注入的回顾
这里主要回顾使用xml进行依赖注入,各种简单类型、复杂类型等;以及注入的方式有两种:set注入和构造器注入。直接进行代码展示
<beans>
<bean id="user" class="com.wangzhi.domain.User" scope="prototype" init-method="init" destroy-method="destroy">
<property name="name" value="王智"/>
<property name="sex" value="男"/>
<property name="age" value="27"/>
<property name="myArr">
<array>
<value>1</value>
<value>2</value>
<value>3</value>
</array>
</property>
<property name="myList">
<list>
<value>4</value>
<value>5</value>
<value>6</value>
</list>
</property>
<property name="myMap">
<map>
<entry key="wangzhi" value="男"/>
<entry key="qianer" value="女"/>
<entry key="huanhuan" value="女"/>
</map>
</property>
<property name="myProperties">
<props>
<prop key="pro1">pro1Value</prop>
<prop key="pro2">pro2Value</prop>
<prop key="pro3">pro3Value</prop>
</props>
</property>
<property name="userService" ref="userService"/>
</bean>
<bean id="user1" class="com.wangzhi.domain.User">
<constructor-arg name="age" value="27"></constructor-arg>
<constructor-arg name="name" value="王智"></constructor-arg>
<constructor-arg name="sex" value="男"></constructor-arg>
</bean>
<bean id="beanFactory" class="com.wangzhi.util.BeanFactory" init-method="init" destroy-method="destroy"/>
<bean id="userService" factory-bean="beanFactory" factory-method="createUserService"/>
</beans>
代码测试:
@Test
public void testDI() {
ClassPathXmlApplicationContext seApplicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
User user1 = (User)seApplicationContext.getBean("user1");
System.out.println(user1);
User user = (User)seApplicationContext.getBean("user");
System.out.println(user);
}
// 执行结果
初始化BeanFactory方法
User{name='王智', age=27, sex='男', myArr=null, myList=null, myMap=null, myProperties=null, userService=null}
初始化user方法
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标签来进行注解包扫描:
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.wangzhi"/>
</beans>
纯注解方式
也就是将第三方的jar也配置成注解的形式,也就是添加配置类,常用的注解如下:
- @Configuration:表示当前类是一个注解类
- @PropertySource:引入外部资源文件,值为数组
- @ComponentScan:用来声明注解扫描的包路径,值为数组
- @Import:可以引入其它配置文件,这样可以再web.xml的配置中添加一个配置类就可以了
这里我们演示将DataSource使用注解的形式加入到Spring IoC容器中管理:
@Configuration
@ComponentScan({"com.wangzhi"})
@PropertySource({"classpath:jdbc.properties"})
public class SpringConfig {
private DruidDataSource druidDataSource;
@Value("${jdbc.driver}")
private String driverClass;
@Value(("${jdbc.url}"))
private String url;
@Value("${jdbc.user}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource createDataSource() {
druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClass);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
druidDataSource.setUrl(url);
return druidDataSource;
}
}
如上就全部使用注解的形式了,那se的时候获取Spring IoC容器的方式就变为 ApplicationContext seApplicationContext = ``new ``AnnotationConfigApplicationContext``(``SpringConfig.``class``)``**;**
SpringConfig就是我们书写的配置类.
如果使用web容器启动呢?比如说tomcat,那就需要在web.xml配置监听器以及contextClass和contextConfigLocation,contextClass表名是纯注解开发,contextConfigLocation表示配置类的路径。
<!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 基于 contextClass 和 contextConfigLocation 来创建一个web应用,这里配置的就是 contextConfigLocation 配置文件-->
<!--<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>-->
<!--表名是使用纯注解的开发方式-->
<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.wangzhi.config.SpringConfig</param-value>
</context-param>
<!--监听器启动 IoC容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</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包中,重点就是生成复杂的对象,这里写个简单的例子:
public interface FactoryBean<T> {
/**
* The name of an attribute that can be
* {@link org.springframework.core.AttributeAccessor#setAttribute set} on a
* {@link org.springframework.beans.factory.config.BeanDefinition} so that
* factory beans can signal their object type when it can't be deduced from
* the factory bean class.
* @since 5.2
*/
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
// 用来获取我们生成的bean对象放入容器
@Nullable
T getObject() throws Exception;
// 获取生成的bean类型
@Nullable
Class<?> getObjectType();
// 是否为单例
default boolean isSingleton() {
return true;
}
}
下面为测试,假设我们的Company是一个复杂的类:
/**
* 假设当前对象是复杂对象
*/
public class Company {
private String name;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
// 定义Company工厂
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[] split = companyInfo.split(",");
company.setName(split[0]);
company.setAddress(split[1]);
return company;
}
@Override
public Class<?> getObjectType() {
return Company.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
在xml中定义Bean
<bean id="company" class="com.wangzhi.util.CompanyFactoryBean">
<property name="companyInfo" value="师悦,未央区"/>
</bean>
测试类:
@Test
public void testFactoryBean() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Object company = applicationContext.getBean("company");
System.out.println(company);
Object contextBean = applicationContext.getBean("&company");
System.out.println(contextBean);
}
// 输出的结果
Company{name='师悦', address='未央区'}
com.wangzhi.util.CompanyFactoryBean@14028087
可以看到我们的Bean的class虽然是CompanyFactoryBean,但是使用容器获取的结果是Company对象,说明FactoryBean就是帮助我们生成对象的,如果一定要获取CompanyFactoryBean,需要在获取bean的时候,id前面加上&
后置处理器
Spring提供了两种后处理bean的扩展接口,分别为 BeanPostProcessor 和 BeanFactoryPostProcessor,区别在于 前者是在Bean实例化之后进行后置处理来做一些事情;后者是在 BeanFactory 初始化之后进行后置处理一些事情。BeanPostProcessor 可以拦截所有Bean,也可以拦截单个bean。
这个重点要说的是Bean的生命周期