Spring IOC 的具体应用
1. BeanFactory与ApplicationContext区别
BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接⼝⽐BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等
2. IOC 容器的启动
Java SE环境下启动IoC容器
ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)
FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件
AnnotationConfigApplicationContext:纯注解模式下启动Spring容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
TransferService transferService = (TransferService)applicationContext.getBean("transferService");
Web环境下启动IoC容器
Tomcat 启动会执行xml 文件 执行监听器 启动Spring的ioc容器 加载applicationContextxml文件
-
从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> <!--配置Spring ioc容器的配置⽂件--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <!-- 使用监听器配置Spring的ioc容器--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> </web-app>
调用
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
-
纯注解模式下从配置类启动容器
<!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> <paramvalue> org.springframework.web.context.support.AnnotationConfigWebAppli cationContext</param-value> </context-param> <!--配置启动类的全限定类名--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.lagou.edu.SpringConfig</param-value> </context-param> <!--使⽤监听器启动Spring的IOC容器--> <listener> <listenerclass> org.springframework.web.context.ContextLoaderListener</listenerclass> </listener> </web-app>
3. xml实例化bean的三种方式
-
调用无参构造器(常用推荐)
在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。
<bean id="userService" class="com.lagou.service.impl.TransferServiceImpl"> </bean>
-
使⽤静态⽅法创建
我们自己使用工厂模式创建出一个static 方法 里面 new 一个对象, 此时并不会创建该class类的对象而是我们指定的
该静态方法的返回的对象放入ioc的容器当中。
//静态方法创建对象 public static User getAdmin(){ return new User(1,"admin"); }
<!--使⽤静态⽅法创建对象的配置⽅式--> <bean id="userAdmin" class="com.lagou.factory.BeanFactory" factory-method="getAdmin"></bean>
-
使⽤实例化⽅法创建
此种⽅式和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。
<!--使⽤实例⽅法创建对象的配置⽅式--> <bean id="beanFactory" class="com.lagou.factory.instancemethod.BeanFactory"></bean> <bean id="transferService" factory-bean="beanFactory" factorymethod=" getTransferService"></bean>
4. bean的作用范围和生命周期
-
作用范围
在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围。作⽤范围官⽅提供的说明如下图:
在上图中提供的这些选项中,我们实际开发中⽤到最多的作⽤范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。配置⽅式参考下⾯的代码:
<!--配置service对象--> <bean id="transferService" class="com.lagou.service.impl.TransferServiceImpl" scope="singleton"> </bean>
-
生命周期
单例模式:singleton
对象出⽣:当创建容器时,对象就被创建了。
对象活着:只要容器在,对象⼀直活着。
对象死亡:当销毁容器时,对象就被销毁了。
⼀句话总结:单例模式的bean对象⽣命周期与容器相同。
多例模式:prototype
对象出⽣:当使⽤对象时,创建新的对象实例。
对象活着:只要对象在使⽤中,就⼀直活着。
对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。
⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。
5. Bean标签属性
在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的⼀个对象。换句话说,如果⼀个对象想让spring管理,在XML的配置中都需要使⽤此标签配置,Bean标签的属性如下:
id属性: ⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。
class属性:⽤于指定创建Bean对象的全限定类名。
name属性:⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。
factory-bean属性:⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。
factory-method属性:⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。
scope属性:⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。
init-method属性:⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。
destory-method属性:⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。
6. DI 依赖注⼊的xml配置
1. 按照注⼊的⽅式分类
构造函数注⼊:顾名思义,就是利⽤带参构造函数实现对类成员的数据赋值。
set⽅法注⼊:它是通过类成员的set⽅法实现数据的注⼊。(使⽤最多的)
2. 按照注⼊的数据类型分类
基本类型和String
注⼊的数据类型是基本类型或者是字符串类型的数据。
其他Bean类型
注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。那么针对当前Bean来说,就是其他Bean了。
复杂类型(集合类型)
注⼊的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。
3. 依赖注⼊的配置具体实现
构造函数注⼊
就是利⽤构造函数实现对类成员的赋值。它的使⽤要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring框架会报错。该注入方式无法解决Spring的循环依赖问题
/**
* @author lane
* @date 2021年03月26日 上午10:23
*/
public class User {
private int id;
private String username;
private Account account;
public User(int id, String username, Account account) {
this.id = id;
this.username = username;
this.account = account;
}
}
<!-- 构造方法注入-->
<bean id="user" class="com.lagou.edu.pojo.User" >
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg name="username" value="lane"></constructor-arg>
<constructor-arg name="account" ref="account"></constructor-arg>
</bean>
在使⽤构造函数注⼊时,涉及的标签是constructor-arg ,该标签有如下属性:
name:⽤于给构造函数中指定名称的参数赋值。
index:⽤于给构造函数中指定索引位置的参数赋值。
value:⽤于指定基本类型或者String类型的数据。
ref:⽤于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。
set⽅法注⼊
利⽤字段的set⽅法实现赋值的注⼊⽅式。此种⽅式在实际开发中是使⽤最多的注⼊⽅式。
在使⽤set⽅法注⼊时,需要使⽤property 标签,该标签属性如下:
name:指定注⼊时调⽤的set⽅法名称。(注:不包含set这三个字⺟,druid连接池指定属性名称)
value:指定注⼊的数据。它⽀持基本类型和String类型。
ref:指定注⼊的数据。它⽀持其他bean类型。写的是其他bean的唯⼀标识。
复杂数据类型注⼊ ⾸先,解释⼀下复杂类型数据,它指的是集合类型数据。集合分为两类,⼀类是List结构(数组结构),⼀类是Map接⼝(键值对) 。
/**
* @author lane
* @date 2021年03月26日 上午10:23
*/
public class User {
private int id;
private String username;
private Account account;
private String[] myArray;
private List<String> myList ;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProperties;
public void setMyArray(String[] myArray) {
this.myArray = myArray;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProperties(Properties myProperties) {
this.myProperties = myProperties;
}
public void setId(int id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setAccount(Account account) {
this.account = account;
}
<!--set方法注入-->
<bean id="user3" class="com.lagou.edu.pojo.User">
<!--set方法注入简单类型-->
<property name="id" value="1"></property>
<property name="username" value="lane"></property>
<property name="account" ref="account"></property>
<!--set方法注入复杂类型-->
<property name="myArray">
<array>
<value>good</value>
<value>good</value>
<value>study</value>
</array>
</property>
<property name="myList">
<list>
<value>day</value>
<value>day</value>
<value>up</value>
</list>
</property>
<property name="mySet">
<set>
<value>start</value>
<value>study</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="i" value="study"></entry>
<entry key="you" value="study"></entry>
</map>
</property>
<property name="myProperties">
<props>
<prop key="we">study</prop>
</props>
</property>
</bean>
User{id=1, username='lane', account=Account{cardNo='6227', name='建设银行', money=10000}, myArray=[good, good, study], myList=[day, day, up], mySet=[start, study], myMap={i=study, you=study}, myProperties={we=study}}
注意
在List结构的集合数据注⼊时, array , list , set 这三个标签通⽤,另外注值的value 标签内部可以直接写值,也可以使⽤bean 标签配置⼀个对象,或者⽤ref 标签引⽤⼀个已经配合的bean的唯⼀标识。
在Map结构的集合数据注⼊时, map 标签使⽤entry ⼦标签实现数据注⼊, entry 标签可以使⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。同时entry 标签中也可以使⽤ref 标签,但是不能使⽤bean 标签。⽽property 标签中不能使⽤ref 或者bean 标签引⽤对象
7. xml与注解相结合模式
1. 具体情况
1)实际企业开发中,纯xml模式使⽤已经很少了
2)引⼊注解功能,不需要引⼊额外的jar
3)xml+注解结合xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始
4)一般原则 第三⽅jar中的bean定义在xml,⽐如德鲁伊数据库连接池。⾃⼰开发的bean定义使⽤注解模式。
2. xml中标签与注解的对应(IoC)
**标签@Component(“accountDao”)**注解加在类上bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类
的类名⾸字⺟⼩写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已
标签的scope属性@Scope(“prototype”),默认单例,注解加在类上
标签的initmethod属性@PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法
标签的destorymethod属性@PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法
3. DI 依赖注⼊的注解实现⽅式
@Autowired按照类型注⼊(推荐使⽤)
@Autowired为Spring提供的注解,需要导⼊包org.springframework.beans.factory.annotation.Autowired。
@Autowired采取的策略为按照类型注⼊。spring容器中找到类型为Account的类,然后将其注⼊进来。这样会产⽣⼀个问题,当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着@Qualifier使⽤。@Qualifier告诉Spring具体去装配哪个对象。
public class User {
private int id;
private String username;
@Autowired
@Qualifier("account")
private Account account;
}
@Resource 默认按照 ByName ⾃动注⼊
@Resource 注解由 J2EE 提供,需要导⼊包 javax.annotation.Resource。
如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。
如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。
如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,都会抛出异常。
如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配;
@Resource 在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包javax.annotation-api.不建议使用。
public class TransferService {
@Resource
private AccountDao accountDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}
4.开启注解包扫描
引入spring 的xmlns:context
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd" >
配置扫描包路径
<!-- 开启注解扫描,指定扫描的包路径-->
<context:component-scan base-package="com.lagou.edu"></context:component-scan>
<!-- 引入外部资源文件,一般数据库连接信息放在jdbc.properties文件里-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
8. 纯注解模式
改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动。
1. 转移xml文件内容到Spring配置类里面,删除xml
@Configuration 注解,表名当前类是⼀个配置类
@ComponentScan 注解,替代 context:component-scan
@PropertySource,引⼊外部属性配置⽂件
@Import 引⼊其他配置类
@Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
@Bean 将⽅法返回对象加⼊ SpringIOC 容器
/**
* 纯注解模式需要配置一个Spring的配置类,这样applicationContext.xml就可以删除了
* @author lane
* @date 2021年03月26日 下午2:43
*/
@Configuration //指定该类是一个Spring 的配置类
@ComponentScan({"com.lagou.edu"}) //配置ioc的注解扫描 参数为数组
@PropertySource({"classpath:jdbc.properties"}) //引入外部的properties文件
//@Import() //可以把其它配置类加载到这个里面,只需要启动这一个就可以了
public class SpringConfig {
// 通过jdbc.properties 赋值
@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放入ioc容器中
@Bean
public DataSource creatAnnotationDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
2. 启动spring ioc 容器
Java SE模式下
ApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(SpringConfig.class);
User user2 = (User) applicationContext2.getBean("user");
Java 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知道我们使⽤注解的⽅式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<paramvalue>
org.springframework.web.context.support.AnnotationConfigWebAppli
cationContext</param-value>
</context-param>
<!--指定配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.lagou.edu.SpringConfig</param-value>
</context-param>
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listenerclass>
org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
</web-app>