文章目录
- 1. IOC容器的概念
- 1.1 什么是IOC容器
- 1.2 IOC容器底层原理
- 2. IOC容器常用接口
- 2.1 BeanFactory 接口
- 2.2 ApplicationContext 接口
- 2.3 ApplicationContext 接口实现类
- 2.3.1 ClassPathXmlApplicationContext 实现类
- 2.3.2 FileSystemXmlApplicationContext 实现类
- 3. IOC容器操作Bean管理
- 3.1 什么是Bean管理
- 3.2 Bean管理-XML方式
- 3.2.1 基于XML方式创建Bean对象
- 3.2.2 基于XML方式注入属性【String类型】
- 3.2.2.1 使用Set 方法进行依赖注入
- 3.2.2.2 使用有参构造 进行依赖注入
- 3.2.3 基于XML方式注入属性【其他非集合类型】
- 3.2.3.1 注入空值及特殊符号
- 3.2.3.2 注入外部bean
- 3.2.3.3 注入内部bean
- 3.2.3.3 注入级联属性
- 3.2.4 基于XML方式注入属性【集合类型】
- 3.2.4.1 集合属性的注入方法
- 3.2.4.2 抽取公共集合属性
- 3.2.5 工厂Bean【FactoryBean】
- 3.2.6 Bean的作用域
- 3.2.7 Bean的生命周期
- 3.2.8 基于XML方式实现自动装配
- 3.2.9 基于XML方式引入外部属性文件
- 3.3 Bean管理-注解方式
- 3.3.1 基于注解方式创建Bean对象
- 3.3.2 开启组件扫描的细节配置
- 3.3.3 基于注解方式注入属性
- 3.3.3.1 @Autowired注解的使用
- 3.3.3.2 @Qualifer注解的使用
- 3.3.3.3 @Resource注解的使用
- 3.3.3.4 @Value注解的使用
- 3.3.4 完全注解开发
1. IOC容器的概念
控制反转(Inversion of Control,缩写为 IOC),是面向对象编程的一种设计原则,可以用来减低计算机代码之间的耦合度。其中,最常见的方式叫做依赖注入(Dependency Injection,简称为 DI),还有一种方式叫做 “依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。——《百度百科》
1.1 什么是IOC容器
(1)控制反转,把对象的创建和对象之间的调用过程全部交给 Spring 进行管理
(2)使用 IOC 的目的:为了降低耦合度
1.2 IOC容器底层原理
(1)xml 解析
(2)工厂模式
(3)反射机制
举例说明:
我们原始在 UserService 类中调用 UserDao 层用的是如下的方式:
原始方式虽然在实现效果上能正常使用,但是存在一个致命的问题:耦合度太高。此时 UserService 类和 UserDao 的关联程度过于紧密,例如:UserDao 此时被移动到另一个包内(路径发送了变化),那么这个时候 UserService 需要 import UserDao 的代码也要跟着变化。
针对上面耦合度高的问题,现在有一种通俗的解决方案:工厂模式。工厂模式的出现,就是为了解耦合。
工厂模式:
通过工厂模式,显著地降低了 UserService 和 UserDao 两个类之间的耦合度。但是这里仍然存在一个问题: UserService 和 UserFactory、UserDao 和 UserFactory 之间依旧存在较高的耦合度,而我们最终的目标是:让耦合度降低到最低程度。
而现在,我们可以结合 xml 解析(Dom4j.jar)、反射技术来结合工厂模式进一步降低耦合度。这也就是 IOC 的基本思路:
通过这种方式,如果以后 UserDao 的路径发生了变化,则直接可以通过修改 xml 中的 class 属性既可以完成全局的 UserDao 路径配置,进一步降低了耦合度。
2. IOC容器常用接口
(1)IOC 思想基于 IOC 容器完成,IOC 容器的底层(本质)就是对象工厂。
(2)使用 IOC 容器需要对 IOC容器进行实例化,Spring 提供了IOC 两种实例化方式:
2.1 BeanFactory 接口
BeanFactory 是 IOC 容器中最基本的实现方式,是Spring 内部使用的一个接口,不提供开发人员进行使用。
注意:加载配置文件的时候,BeanFactory不会主动创建 xml 中配置好的对象,而是在其他对象要求Spring 提供一个配置好的对象时才去创建(懒加载),也就是说在 BeanFactory context = new ClassPathXmlApplication("bean.xml");
时BeanFactory 不会创建对象。只有在主动调用 User user = context.getBean("user", User.class);
时才会创建对象。
2.2 ApplicationContext 接口
ApplicationContext 接口 是 BeanFactory 接口的一个子接口,提供了更多更强大的功能,面向开发人员使用,我们一般做工程时使用这个接口而不是 BeanFactory 接口。
注意:加载配置文件的时候就会将编程人员在 xml 中配置的对象进行创建,也就是说,当在执行完 ApplicationContext context = new ClassPathXmlApplication("bean.xml");
时,ApplicationContext 已经创建完 xml 中配置好的对象。
在实际 Web 开发中,我们最好将创建对象这种耗时耗资源的事情放在项目启动时进行,而不是什么时候用什么时候创建,因此一般我们在操作中使用 ApplicationContext 接口。
2.3 ApplicationContext 接口实现类
ApplicationContext 主要有 ClassPathXmlApplicationContext
和 FileSystemXmlApplicationContext
这两个实现类。ConfigurableApplicationContext
子接口包含了一些相关的扩展功能,暂时先不介绍,留个印象。
2.3.1 ClassPathXmlApplicationContext 实现类
在使用时,其读取 xml 文件时是路径是基于src下的内路径。
2.3.2 FileSystemXmlApplicationContext 实现类
在使用时,其读取 xml 文件时是基于系统的盘符路径,需要写出文件的全路径。
3. IOC容器操作Bean管理
3.1 什么是Bean管理
IOC 的 Bean管理主要包含两个操作:Spring 创建对象 和 Spring 注入属性。
Bean 管理操作有两种实现方式:
(1)基于xml配置文件方式实现
(2)基于注解方式实现
3.2 Bean管理-XML方式
3.2.1 基于XML方式创建Bean对象
(1)在Spring 配置文件中,使用 <bean>标签,标签中添加对应属性。
(2)<bean>标签中有很多属性,下面介绍常用的几种属性:
* id 属性:给对象取一个唯一标识、别名
* class 属性:标明创建对象类的全路径
(3)创建对象时,默认使用类的无参构造方法
<bean id="user" class="com.zju.spring5.User"></bean>
3.2.2 基于XML方式注入属性【String类型】
DI是IOC中的一种具体实现,它就表示依赖注入或者注入属性,注入属性需要在创建对象的基础之上进行完成。
原始方法注入属性方式一:使用 setXxx() 方法注入:
public class Book {
private String name;
// set 方法注入
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
Book book = new Book();
book.setName("abc");
}
}
原始方法注入属性方式二:使用有参构造方法注入:
public class Book {
private String name;
// 有参构造方式注入
public Book(String name) {
this.name = name;
}
public static void main(String[] args) {
Book book = new Book("abc");
}
}
3.2.2.1 使用Set 方法进行依赖注入
(1)创建类,定义属性和对应属性的 set 方法
public class Book {
private String name;
private String author;
// set 方法注入
public void setName(String name) {
this.name = name;
}
public void setAuthor(String author) {
this.author = author;
}
}
(2)在Spring 配置文件中配置对象创建,再配置属性注入
Spring配置文件:bean1.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置Book 对象创建 -->
<bean id="book" class="com.zju.spring5.Book">
<!-- 使用property 完成属性注入 -->
<property name="name" value="易筋经"></property>
<property name="author" value="达摩老祖"></property>
</bean>
</beans>
(3)使用getBean()获得对象
@Test
public void testBook() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Book book = context.getBean("book", Book.class);
// 上一步执行完成后,就已经完成了属性的注入(赋值)
}
p名称空间注入:set方式的配置简化:不使用property 标签,使用属性进行set方法赋值(了解)
第一步:需要在<beans>标签中新增一个 xmlns:p
属性,属性值为:"http://www.springframework.org/schema/p"
。
第二步:在注册<bean>标签中添加属性 p:类的属性名="属性值"
。
<?xml version="1.0" encoding="UTF-8"?>
<!-- 在beans标签中添加了一个xmlns:p属性 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置Book 对象创建 -->
<bean id="book" class="com.zju.spring5.Book" p:name="易筋经" p:author="菩提老祖">
</bean>
</beans>
该方式和property 标签方式的使用效果一致,看自己喜好选择一种配置方式即可。
3.2.2.2 使用有参构造 进行依赖注入
(1)创建类,定义属性和该类的有参构造方法
public class Orders {
private String name;
private String address;
// 有参构造
public Orders(String name, String address) {
this.name = name;
this.address = address;
}
}
(2)在Spring 配置文件中配置对象创建,再配置属性注入
Spring配置文件:bean1.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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置Orders对象创建 -->
<bean id="orders" class="com.zju.spring5.Orders">
<constructor-arg name="name" value="电脑"></constructor-arg>
<constructor-arg name="address" value="china"></constructor-arg>
</bean>
</beans>
或者,除了使用name属性指定有参构造属性值,还可以使用index属性:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置Orders对象创建 -->
<bean id="orders" class="com.zju.spring5.Orders">
<!-- 0表示第一个参数,1表示第二个参数 -->
<constructor-arg index="0" value="电脑"></constructor-arg>
<constructor-arg index="1" value="china"></constructor-arg>
</bean>
</beans>
(3)使用getBean()获得对象
@Test
public void testOrders() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Orders orders = context.getBean("orders", Orders.class);
// 上一步执行完成后,就已经完成了属性的注入(赋值)
}
3.2.3 基于XML方式注入属性【其他非集合类型】
3.2.3.1 注入空值及特殊符号
可以使用<null>标签注入空值:
<bean id="book" class="com.zju.spring5.Book">
<!-- 使用property 完成属性注入 -->
<property name="name" value="易筋经"></property>
<property name="author" value="达摩老祖"></property>
<!-- 使用null 标签注入空值 -->
<property name="address">
<null />
</property>
</bean>
可以使用<![CDATA[ ]]> 标签进行 xml 字符转义【常用】:
<bean id="book" class="com.zju.spring5.Book">
<!-- 使用property 完成属性注入 -->
<property name="name" value="易筋经"></property>
<property name="author" value="达摩老祖"></property>
<!-- 使用CDATA 标签注入特殊符号 -->
<property name="address">
<value>
<![CDATA[
<<南京>>
]]>
</value>
</property>
</bean>
3.2.3.2 注入外部bean
在 web 开发中,service 层调用 dao 层,就是一种调用外部 bean,现在演示一下用 spring ioc 如何操作:
首先创建 service 类 和 dao 类
一个实现接口的 dao 类【主要看演示如果有接口了,class属性如何配置】
dao 的接口:
public interface UserDao {
public void update();
}
dao 的实现类:
public class UserDaoImpl implements UserDao {
@Override
public void update() {
System.out.println("dao update....");
}
}
在 service 中调用 dao 中的方法【传统方式】
service 类就写得简单一点,不继承接口了:
public class UserService {
public void add() {
System.out.println("service add...");
// 创建UserDao 对象
UserDao dao = new UserDaoImpl();
dao.update();
}
}
通过Spring 的 IOC容器实现【Spring方式】
首先在 service 类中创建 UserDao 类型的属性,生成 set 方法(使用set 注入)
public class UserService {
// 创建UserDao 类型属性,生成set 方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("service add...");
// 直接使用,spring会帮我们生成userDao
userDao.update();
}
}
xml 配置文件中(把beans省略了,看得简单点)
<!-- 创建Service 和 Dao 对象 -->
<bean id="userService" class="com.zju.spring5.UserService">
<!-- 注入userDao对象
name:类中的属性名称
ref:这里因为是引用外部bean,不再使用value,ref属性值为引用bean的id值
-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<!-- 注意:class必须填写 接口的实现类的全类名 -->
<bean id="userDaoImpl" class="com.zju.spring5.UserDaoImpl"></bean>
这里模拟web 层调用service
@test
public void testAdd() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
// 此时不仅生成了service,还生成了service中的dao
userService.add();
}
3.2.3.3 注入内部bean
数据库中,表与表之间一对多、一对一和多对多等关系,我们现在学习的内部bean就跟这个表与表的关系相关。
现在通过一个一对多的关系来说明内部bean 和级联赋值:部门-员工关系:
一个部门有多个员工,而一个员工属于一个部门:部门是一,员工是多。我们现在要在实体类中表示一对多的关系:
首先创建部门类:
// 部门类
public class Dept {
private String name;
public void setName(String name) {
this.name = name;
}
}
然后再创建员工类,里面包含其所属部门:
// 员工类
public class Emp {
private String name;
private String gender;
// 员工属于某一个部门
private Dept dept;
public void setName(String name) {
this.name = name;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
在Spring 的配置文件中进行配置:
<bean id="emp" class="com.zju.spring5.bean.Emp">
<!-- 先设置两个普通属性 -->
<property name="name" value="lucy"></property>
<property name="gender" value="女"></property>
<!-- 对象类型的属性 -->
<property name="dept">
<!-- 下面这种写法也可以直接使用外部bean,通过ref引入 -->
<bean id="dept" class="com.zju.spring5.bean.Dept">
<property name="name" vaule="保安部"><property>
</bean>
</property>
</bean>
这个就不做测试了,相信大家都能看懂。
3.2.3.3 注入级联属性
(1)实现注入级联属性的第一种方式:与注入外部bean一致
<bean id="emp" class="com.zju.spring5.bean.Emp">
<!-- 先设置两个普通属性 -->
<property name="name" value="lucy"></property>
<property name="gender" value="女"></property>
<!-- 对象类型的属性 -->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.zju.spring5.bean.Dept">
<property name="name" value="财务部"></property>
</bean>
(2)实现注入级联属性的第二种方式:通过get方法设置值:
xml 中配置情况:
<bean id="emp" class="com.zju.spring5.bean.Emp">
<!-- 先设置两个普通属性 -->
<property name="name" value="lucy"></property>
<property name="gender" value="女"></property>
<!-- 对象类型的属性 -->
<property name="dept" ref="dept"></property>
<!-- 这里要求Dept 中生成了getName方法
本质是通过getName 方式获得name 属性然后赋值
这里会覆盖原来的"财务部"属性值
-->
<property name="dept.name" value="技术部"></property>
</bean>
<bean id="dept" class="com.zju.spring5.bean.Dept">
<property name="name" value="财务部"></property>
</bean>
Dept 代码中需要加入 getName 方法
public class Dept {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
3.2.4 基于XML方式注入属性【集合类型】
3.2.4.1 集合属性的注入方法
首先,创建两个类
public class Stu {
// 数组类型属性
private String[] courses;
// list类型属性
private List<String> list;
// Map类型属性
private Map<String, String> maps;
// Set类型属性
private Set<String> set;
// 再来一个Course类型的集合属性
private List<Course> courseList;
// 生成每个属性的set方法
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
public void setSets(Set<String> sets) {
this.sets = sets;
}
}
依次在xml中配置属性,都很简单,看过了就会了:
<bean id="stu" class="com.zju.spring5.collectiontype.Stu">
<property name="courses">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<property name="list">
<list>
<value>张三</value>
<value>小三</value>
</list>
</property>
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
<property name="courseList">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
</bean>
<bean id="course1" class="com.zju.spring5.collectiontype.Course">
<property name="cname" value="Spring5框架"></property>
</bean>
<bean id="course2" class="com.zju.spring5.collectiontype.Course">
<property name="cname" value="MyBatis框架"></property>
</bean>
3.2.4.2 抽取公共集合属性
抽取公共集合属性就是说,如果有一个集合会被很多类都用到,那么就可以将该集合部分抽取出来作为公共部分。
第一步:创建一个Book类
public class Book {
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}
}
第二步:需要在<beans>标签中新增一个 xmlns:util
属性,属性值为:"http://www.springframework.org/schema/util"
,然后将原有的 xsi:schemaLocation
属性新增一条 "http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
<?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: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/util http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 提取list集合类型作为公共属性
如果是Map集合类型就使用<util:map>,以此类推
-->
<util:list id="bookList">
<value>易筋经</value>
<value>九阴真经</value>
<value>九阳神功</value>
</util:list>
<!-- 公共list集合使用,这里只用一个类演示,如果有多个类也可以共同使用公共部分 -->
<bean id="book" class="com.zju.spring5.collectiontype.Book">
<property name="list" ref="bookList"></property>
</bean>
</beans>
3.2.5 工厂Bean【FactoryBean】
Spring 有两种类型的bean,一种是普通bean,也就是之前我们一直在讨论的bean,另外一种是工厂bean(又称FactoryBean)。
普通Bean:在Spring的 xml 配置文件中定义的 bean 标签中的 class 类型就是最后 content.getBean() 的返回类型;
工厂Bean:在Spring的 xml 配置文件中定义的 bean 标签的 class 类型可以和返回类型不一样。
如何使用工厂Bean?
第一步:创建类,让这个类作为工厂Bean,让其实现 FactoryBean 接口。
第二步:在实现接口的方法里面,定义返回Bean 的对象类型。
public class MyBean implements FactoryBean<Course> {
@Override
public Course getObject() throws Exception {
/**
* 这里应该使用反射机制创建对象,这里为了简便直接new 了一个对象出来
*/
Course course = new Course();
course.setName("Java大数据");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
// 判断是否是单例
@Override
public boolean isSingleton() {
return false;
}
}
第三步:在Spring 配置文件中注册工厂Bean
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myBean" class="com.zju.spring5.factorybean.MyBean"></bean>
</beans>
第四步:使用工厂Bean,展现工厂Bean的定义类型和返回类型不一致
@Test
public void testCollection3() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Course course = context.getBean("myBean", Course.class);
}
3.2.6 Bean的作用域
在Spring 的Bean管理中,可以设置创建Bean 实例是单实例还是多实例。
在Spring 中,在默认情况下(不做任何额外设置),Bean是一个单实例对象。
单实例模式和多实例模式的区别:
设置scope的值为 singleton 的时候,Spring 在加载 Spring 配置文件的时候就会创建好单实例bean 对象。
设置scope的值为 prototype 的时候,不是在加载 Spring 配置文件的时候创建对象,而是在调用content.getBean() 方法的时候才创建。每次创建的都是全新的对象。
单实例举例:
我们在 xml 中定义 bean 时不做额外的设置
<bean id="book" class="com.zju.spring5.collectiontype.Book">
<property name="name" value="假如给我三天光明"></property>
<property name="author" value="安妮海瑟薇"></property>
</bean>
查看两次生成的bean 的hashcode,两者完全一致,证明这个Bean 是一个单实例对象
@Test
public void testCollection3() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Book book1 = context.getBean("book", Book.class);
System.out.println(book1.hashCode());
// 两者的hashcode 完全一致,是同一个实例
Book book2 = context.getBean("book", Book.class);
System.out.println(book2.hashCode());
}
多实例举例:
Spring 的配置文件 bean 标签里面有属性(scope)用于设置单实例和多实例。
scope属性值:
singleton
(默认值),表示是单实例对象prototype
表示是多实例对象request
创建对象之后,将该Bean 放入request 域中(极少使用)session
创建对象之后,将该Bean 放入session 域中(极少使用)
<!-- 设置多实例模式 -->
<bean id="book" class="com.zju.spring5.collectiontype.Book" scope="prototype">
<property name="name" value="假如给我三天光明"></property>
<property name="author" value="安妮海瑟薇"></property>
</bean>
此时,两个实例的hashcode 不再一致,说明现在的Bean 是一个多实例对象
@Test
public void testCollection3() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Book book1 = context.getBean("book", Book.class);
System.out.println(book1.hashCode());
// 两者的hashcode 不一致,属于多实例对象
Book book2 = context.getBean("book", Book.class);
System.out.println(book2.hashCode());
}
3.2.7 Bean的生命周期
Bean 的生命周期指的是Bean 对象从创建到销毁的几个过程:
(1)通过构造器创建Bean 实例(默认无参构造)
(2)为 Bean 的属性值和对其他Bean 的引用(调用set 方法)
(3)调用 Bean 的初始化方法(需要进行配置初始化方法)
(4)Bean 对象获取到,可以进行使用
(5)当容器被关闭时,调用Bean的销毁方法(需要进行配置销毁方法)
下面演示Bean 的生命周期:
首先创建 java 类:
package com.zju.spring5.bean;
public class Orders {
private String name;
// 演示无参构造方法
public Orders() { System.out.println("1. 无参构造创建Bean 实例..."); }
// 演示setXxx 方法
public void setName(String name) {
System.out.println("2. 调用setXxx 方法设置属性的值...");
this.name = name;
}
// 创建执行初始化的方法【需要配置】
public void initMethod() { System.out.println("3. 执行初始化的方法..."); }
// 创建销毁的方法【需要配置】
public void destroyMethod() { System.out.println("5. 执行销毁的方法..."); }
}
然后配置 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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
通过init-method 属性可以设置初始化方法
通过destroy-method 属性可以设置销毁方法
-->
<bean id="orders" class="com.zju.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="手机"></property>
</bean>
</beans>
然后编写 测试代码:
@Test
public void testBean() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("4. 获取到创建的Bean 对象,可以进行使用...");
// 必须手动让该Bean 销毁【强转是因为ApplicationContext接口中没有close方法】
((ClassPathXmlApplicationContext) context).close();
}
最后显示代码执行结果:
你以为这就完啦?其实,Spring 还有一个Bean 的后置处理器,如果你不配置Bean 的后置处理器,则确实只有五个步骤,但是如果你配置了Bean 的后置处理器,则配置处理器会给所有在xml中配置的Bean 执行初始化方法前后各加一个方法,整个生命周期将会达到7个过程:
(1)通过构造器创建Bean 实例(默认无参构造)
(2)为 Bean 的属性值和对其他Bean 的引用(调用set 方法)
(3)把 Bean 的实例传递给 Bean 的后置处理器的方法
(4)调用 Bean 的初始化方法(需要进行配置初始化方法)
(5)把 Bean 的实例传递给 Bean 的后置处理器的方法
(6)Bean 对象获取到,可以进行使用
(7)当容器被关闭时,调用Bean的销毁方法(需要进行配置销毁方法)
下面来演示添加Bean的后置处理器的效果:
创建类,实现接口 BeanPostProcessor,创建后置处理器,然后需要实现两个方法:postProcessBeforeInitialization
和 postProcessAfterInitialization
,分别对应上述过程的(3)和(5):
public class MyBeanPost implements BeanPostProcessor {
// 完成setXxx 赋值后,将Bean 传递给该方法,然后再进行Bean 的初始化操作
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("3. 在初始化之前执行【Bean的后置处理器】...");
return bean;
}
// 完成Bean 的初始化操作后,再将Bean 传递给该方法,然后Bean 即可使用
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("5. 在初始化之后执行【Bean的后置处理器】...");
return bean;
}
}
然后将该后置处理器 配置到 xml 文件中,注意:该后置处理器会给所有在 xml 中注册的Bean 的初始化方法前后都加上一个方法!
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
通过init-method 属性可以设置初始化方法
通过destroy-method 属性可以设置销毁方法
-->
<bean id="orders" class="com.zju.spring5.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="name" value="手机"></property>
</bean>
<!-- 配置后置处理器,作用于所有Bean -->
<bean id="myBeanPost" class="com.zju.spring5.bean.MyBeanPost"></bean>
</beans>
再次测试:
@Test
public void testBean() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("6. 获取到创建的Bean 对象,可以进行使用...");
// 必须手动让该Bean 销毁【强转是因为ApplicationContext接口中没有close方法】
((ClassPathXmlApplicationContext) context).close();
}
获得输出顺序为下图,这就是最完整的Bean 的生命周期:
3.2.8 基于XML方式实现自动装配
首先要明白一点,我们以前配置 <bean> 标签的时候使用的 <property> 标签就是一种手动装配。
什么是自动装配(Autowired)?
根据指定的装配规则如属性名称或者属性类型,Spring自动将匹配的属性值进行注入,可以简化我们手动装配的写法,不用再写<property>标签。
注意:在实际操作中,很少在 xml 中配置 autowire 属性进行自动装配,一般都是通过注解方式进行自动装配,注解方式我们以后介绍。
我们还是使用 员工-部门的那个案例进行说明:
传统的注入方式【手动装配】:采用外部bean 注入,给员工类中注入部门bean
首先定义一个员工类和部门类:
package com.zju.spring5.autowire;
public class Emp {
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"dept=" + dept +
'}';
}
public void test() { System.out.println(dept); }
}
package com.zju.spring5.autowire;
public class Dept {
@Override
public String toString() {
return "Dept{}";
}
}
然后在 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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 传统外部bean 注入 -->
<bean id="emp" class="com.zju.spring5.autowire.Emp">
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.zju.spring5.autowire.Dept"></bean>
</beans>
然后,我们可以使用自动装配,来进行简化配置:
在bean 标签中配置属性 autowire
,可以实现自动装配:
autowire比较常用的属性值是
byName
属性:如果属性值是 byName,则 Dept 类的 id 必须和 emp 的 dept 属性名相一致。
byType
属性:根据属性类型进行自动注入,注意:自动装配的外部bean 同一个类型不能定义多个。
分别根据属性的名称和属性的类型进行注入
首先演示使用 ByName 属性值:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
实现自动装配
bean 标签属性 autowire,配置自动装配
autowire比较常用的属性值是
byName属性:如果使用了byName:则Dept类的id必须和emp的dept属性名相一致
byType属性:根据属性类型进行自动注入
分别根据属性的名称和属性的类型进行注入
-->
<bean id="emp" class="com.zju.spring5.autowire.Emp" autowire="byName">
</bean>
<bean id="dept" class="com.zju.spring5.autowire.Dept"></bean>
</beans>
然后演示使用 ByType 属性值:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
实现自动装配
bean 标签属性 autowire,配置自动装配
autowire比较常用的属性值是
byName属性:如果使用了byName:则Dept类的id必须和emp的dept属性名相一致
byType属性:根据属性类型进行自动注入
分别根据属性的名称和属性的类型进行注入
-->
<bean id="emp" class="com.zju.spring5.autowire.Emp" autowire="byType">
</bean>
<bean id="dept" class="com.zju.spring5.autowire.Dept"></bean>
</beans>
3.2.9 基于XML方式引入外部属性文件
应用场景:之前的做法中,我们配置一个Bean 需要先创建一个JavaBean 类,然后在 xml 文件中进行<bean>标签的配置。但如果一个类中的属性很多,那么就会有很多的<property>标签,这么写并不是很方便。所以我们可以将一些属性放到一个其他类型文件中,如 properties文件中,然后将其他类型文件引入到 Spring 的 xml 配置文件中去读取。
比如我们操作数据库,数据库的驱动、地址、用户名、密码这些值放入一个properties文件中,然后再在 xml 文件中引入完成注入,这就叫引入外部属性文件。
首先,我们先用老方法直接在 xml 文件中配置一个德鲁伊连接池:
先引入druid 需要引用的 jar 包 druid-1.1.9.jar
。
然后配置相关属性:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
直接配置Druid 连接池对象
-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/book"></property>
<property name="username" value="root"></property>
<property name="password" value="000420"></property>
</bean>
</beans>
其实,我们可以将数据库驱动、url、username这些属性都放置到一个外部properties文件中,然后通过 xml 引入即可。
新建一个 jdbc.properties
外部配置文件:
一般而言,我们不直接写driverClass
属性名称,因为这样写经常会发生冲突,这里我们用prop.xxx
来避免发生冲突。
prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/book
prop.username=root
prop.password=000420
然后我们将外部 properties 属性文件引入 xml 文件中,然后进行读取:
注意: 这里需要引入 xmlns:context
名称空间 "http://www.springframework.org/schema/context"
,然后继续修改beans 标签中schemaLocation
的属性值,添加一条"http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
属性。
引入外部配置文件的语法是:<context:property-placeholder location="路径名">
,这里的classpath
指的是 src
路径下。
属性值引入的语法就是: ${properties文件中'等号'的左侧属性名}
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 引入外部文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${prop.driverClassName}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.username}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
</beans>
3.3 Bean管理-注解方式
什么是注解?
注解是代码中特殊的标记,格式为:@注解名称(属性名称=属性值, 属性名称=属性值, ...)
,注解可以作用在类上面、方法上面和属性上面。
使用注解的目的是什么?
是为了让我们的Spring xml 配置更加简洁、更加优雅。
3.3.1 基于注解方式创建Bean对象
基于Bean 管理进行对象创建,Spring 提供了如下四个注解:
@Component
:可以创建所有的Bean 对象@Service
:一般用在业务逻辑层、Service层上@Controller
:一般用在Web 层上@Repository
:一般用在Dao 层、持久层上
注意:上面的四个注解,功能是完全一样的,都可以用来创建Bean 实例。每个注解可以用在不同的层上,实际中把@Component 用在Service 层,把@Service 注解用在Dao 层都是可以的,它并没有强制要求哪个注解必须用在哪个层上,只是我们习惯把对应的注解放在对应的层中,便于程序员更加清晰当前组件所扮演的角色。
第一步:引入依赖 spring-aop-5.2.6.RELEASE.jar
第二步:开启组件扫描。因为我们要在相关的类上面加上注解,必须告诉Spring 容器现在要在哪些包里面的类上面加上注解,要指定扫描的位置,然后Spring 才能去扫描这些类进行创建。
需要在 beans 标签中引入一个名称空间 xmlns:context
值为"http://www.springframework.org/schema/context"
,然后继续修改beans 标签中schemaLocation
的属性值,添加一条"http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
属性。
如果说我们需要扫描多个包:
第一种方式,可以在base-package
属性中写上多个包,然后用,
将不同包隔开,如下:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.zju.spring5.dao, com.zju.spring5.service"></context:component-scan>
</beans>
第二种方式,是将dao 包和 service 包这两个包的上层目录放入base-package
属性中:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.zju.spring5"></context:component-scan>
</beans>
第三步:创建类,在类上面添加创建对象的注解
现在,不需要再在 xml 文件中配置 bean 标签了,直接在类上面加上注解:
注意:注解括号中的value
就是等价于 bean 标签中的id
属性
@Service(value = "userService") // 等价于 <bean id="userService" class="类的全路径">
public class UserService {
public void add() {
System.out.println("Service add...");
}
}
注解中的(value="id值")
可以省略不写,默认值是类名称,首字母小写。
// 此代码和上面代码等价
@Service // 等价于 <bean id="userService" class="类的全路径">
public class UserService {
public void add() {
System.out.println("Service add...");
}
}
然后直接进行测试,代码可以正常运行:
public class TestSpring5Demo {
@Test
public void testService() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
3.3.2 开启组件扫描的细节配置
我们之前在配置组件扫描时,都是基于一个包去扫描,那么如何配置才能决定哪些类才能被扫描,哪些不能被扫描呢?
事实上,当我们配置上 <context:component-scan base-package="com.zju"></context:component-scan>
时,Spring 中会有一个默认的 filter
这个 filter 会找到 com.zju
下的所有类,进行扫描。
但是,如果我们加上了 use-default-filter="false"
这个属性,表示现在不使用Spring 默认的 filter,而使用自己配置的 filter。
我们可以使用 <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
来指定扫描在 base-package
路径下,有 @Controller
注解标注的类。
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--
开启组件扫描
base-package 表示扫描 xxx 路径下的类
首先通过use-default-filter="false" 取消使用默认的过滤器
然后通过context:include-filter 来表示仅扫描 xxxx
type="annotation" 表示针对的是注解
expression="org.springframework.stereotype.Controller" 表示针对的是Controller注解的类
联合起来,就是扫描 base-package 下,仅扫描带有@Controller 注解的类
-->
<context:component-scan base-package="com.zju" use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
同时,我们可以再来看这样一组案例,使用的是context:exclude-filter
表达除了 xxx 都扫描:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--
开启组件扫描
base-package 表示扫描 xxx 路径下的类
这里没有使用 use-default-filter="false",说明仍然使用默认过滤器
然后通过context:exclude-filter 表示除了 xxx 都扫描
type="annotation" 表示针对的是注解
expression="org.springframework.stereotype.Controller" 表示针对的是Controller注解的类
联合起来,就是扫描 base-package 下,除了带有 @Controller注解的类都扫描
-->
<context:component-scan base-package="com.zju">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
</beans>
这两个案例看完了,基本上就懂了关于组件扫描的细节。
3.3.3 基于注解方式注入属性
首先,我们需要介绍一些Spring 给属性注入提供了哪些注解:
@Autowired
:注入对象类型属性,根据属性类型,进行自动注入。@Qualifier
:注入对象类型属性,解决@Autowired注解的歧义性,根据属性名称,进行自动注入。@Resource
:注入对象类型属性,可以根据属性类型注入,也可以根据属性名称注入。@Value
:注入普通类型属性。
3.3.3.1 @Autowired注解的使用
我们演示一个在Service 类中自动注入 Dao 类的案例:
首先,把Service 和 Dao 的对象进行创建,在Service 和 Dao 的类上面添加创建对象的注解。
注意:如果有接口,只将注解加在实现类上。
Service 类:
package com.zju.spring5.service;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void add() {
System.out.println("Service add...");
}
}
DaoImpl 实现类,注意注解不加在UserDao 接口上,仅加在实现类上:
package com.zju.spring5.dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("service add...");
}
}
然后需要将 Dao 注入到 Service 类中,因此要在Service 类中定义 Dao 类型的属性,此时不再需要set 方法。
@Autowired 表示根据类型进行自动注入。
package com.zju.spring5.service;
import com.zju.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 定义Dao 类型属性
@Autowired
private UserDao userDao;
public void add() {
System.out.println("Service add...");
userDao.add();
}
}
3.3.3.2 @Qualifer注解的使用
同样的,我们也可以使用根据属性名进行自动装配的 @Qualifier 注解,因为一个接口可能会有多个实现类,先通过@Autowire 进行类型匹配,如果匹配到多个实现类的话,再使用 @Qualifier 注解进行名称指定。
注意:@Qualifier
注解,必须结合 @Autowired
注解配合使用:
这里为了方便演示,我们给 @Repository 注解指定了一个id
值【别忘了不指定的话默认是类名,首字母小写】:
package com.zju.spring5.dao;
import org.springframework.stereotype.Repository;
@Repository(value = "myUserDaoImpl")
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("service add...");
}
}
然后展示 @Qualifer 的使用,value 的值就是创建类的 id
值。
package com.zju.spring5.service;
import com.zju.spring5.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 定义Dao 类型属性
@Autowired
@Qualifier(value="myUserDaoImpl")
private UserDao userDao;
public void add() {
System.out.println("Service add...");
userDao.add();
}
}
3.3.3.3 @Resource注解的使用
@Resource 注解既可以根据类型注入、又可以根据名称注入。
如果 @Resource 注解如果不添加任何参数,则功能和 @Autowire一致,根据类型进行自动注入:
@Service
public class UserService {
// 定义Dao 类型属性
@Resource
private UserDao userDao;
public void add() {
System.out.println("Service add...");
userDao.add();
}
}
如果 @Resource注解添加了 name=名称
,则根据名称注入:
@Service
public class UserService {
// 定义Dao 类型属性
@Resource(name = "myUserDaoImpl")
private UserDao userDao;
public void add() {
System.out.println("Service add...");
userDao.add();
}
}
需要注意的是:@Resource 注解是 javax.annotation
包下的,不是Spring 内置的注解,因此Spring 官方更建议我们使用 @Autowire 和 @Qualifer 两个注解。
3.3.3.4 @Value注解的使用
@Value 注解针对的主要类型是String 等普通类型的属性的自动注入,相当于替代了原来的 property 标签。
@Service
public class UserService {
// 定义String 类型属性
@Value(value = "abc")
private String name;
// 定义Dao 类型属性
@Resource
private UserDao userDao;
public void add() {
System.out.println("Service add...");
userDao.add();
}
}
3.3.4 完全注解开发
在Spring 中可以完全脱离 xml 配置,仅仅使用注解开发。也就是说,我们连 xml 中配置的开启组件扫描也可以使用注解完成。
首先,我们必须创建一个配置类,用它来替代传统的Spring xml 配置文件,我们需要在该类上方加一个 @Configuration
注解,该注解可以将一个类标记为配置类给Spring 识别,替代 xml。
然后,我们在原来的 xml 配置文件中的<context:component-scan>
标签,可以使用 @ComponentScan(basePackages={字符串数组})
,来设置扫描的包的路径。
package com.zju.spring5.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"com.zju"})
public class SpringConfig {
}
但是既然现在不使用配置文件 xml 了,那么如果我们要做测试的话,就不能用原来的 new ClassPathXmlApplicationContext("xml路径")
了,我们需要使用 new AnnotationConfigApplicationContext(配置类的Class);
来获得 context。
public class TestSpring5Demo {
@Test
public void testService() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService service = context.getBean("userService", UserService.class);
service.add();
}
}
这就是所谓的 “完全注解开发”,但是需要说明的是,这种纯注解开发在实际操作中一般用到 SpringBoot 框架,而 SpringBoot 本质上就是 Spring。
这就是IOC 所有的内容。