文章目录
一、IOC的概念和原理
1.什么是IOC
IOC,翻译过来就是控制反转,就是把对象创建和对象之间的调用过程都交给Spring进行管理。 目的是为了降低耦合。
2.IOC的底层原理
技术支持:xml解析、工厂模式(设计模式的一种)、反射。
(1)工厂模式
假设有两个类,UserService类和UserDao类,我们想要在UserService中调用一个UserDao的方法,传统做法如下。这样的弊端就是,类之间的耦合度高。
class UserService{
execute(){
UserDao userDao=new UserDao();
userDao.add();
}
class UserDao{
add(){
}
}
如果采用工厂模式,如下,耦合度有所降低,但工厂类和UserDao类依旧存在耦合。
class UserService{
execute(){
UserDao userDao=UserFactory.getDao();
userDao.add();
}
class UserDao{
add(){
}
}
class UserFactory{
//用这个类统一创建对象
public static UserDao getDao(){
return new UserDao();
}
}
(2)IOC的解耦过程
为了进一步将耦合度降到最低限度,我们在利用了xml解析、工厂模式、反射的原理基础上,引入了IOC技术。
步骤如下:
- 在xml文件中配置创建的对象
<bean id="dao" class="com.wang.UserDao"></bean>
- 已有Service类和Dao类,现在要创建工厂类
class UserFactory{
public static UserDao getDao(){
String classValue=class属性值;//通过xml解析获取class属性值,这一步还需要展开
Class class=Class.forName(classValue);//通过反射创建对象
return (UserDao)class.newInstance();//完成了反射创建对象
}
}
(3)IOC接口
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。
Spring中提供IOC容器两种实现方式:
- BeanFactory接口:IOC容器最基本的方式(Spring自带的),一般只内部使用,不提供我们这些开发人员使用。特点是:加载配置文件时不会创建对象,而是获取对象、使用对象时才创建对象。
BeanFactory context=new ClassPathXmlApplicationContext("bean1.xml");
- ApplicationContext接口:是BeanFactory的子接口,功能更强大,是推荐使用的。特点是:加载配置文件时(启动服务器时)就创建对象。
ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml");
- BeanFactory接口下面还有一个ConfigurableApplicationContext子接口,有一些扩展的功能,可以了解一下。
(4)ApplicationContext接口的实现类
- FileSystemXmlApplicationContext类:参数为xml文件的绝对路径
- ClassPathXmlApplicationContext类:参数为xml文件的项目相对路径
二、IOC操作Bean管理(基于XML)
1.什么是Bean管理
- 两个操作:
Spring创建对象
Spring注入属性
- 两种实现方式:
基于xml配置文件
基于注解方式
2.基于xml方式操作Bean管理
(1)创建对象
<bean id="user" class="com.wang.User"> </bean>
- 在spring配置文件中使用bean标签,在标签中添加对应属性,就可以实现对象创建。
- bean标签中常用属性:id属性(唯一标识)、class属性(类全路径)。
- 创建对象时,默认也是执行无参数构造方法。
(2)注入属性
DI(依赖注入),说白了就是注入属性的一种实现。可通过set方法注入、类的有参数的构造器注入。
- 通过Set方法实现:
第一步:在src下新建Book类,并写好相应的代码,如下:
package com.wang;
public class Book {
private String bname;
private String bauthor;
public void setBname(String bname) {
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
public void printMessage(){
System.out.println("《"+bname+"》 "+bauthor);
}
}
第二步:在xml配置文件中配置对象,配置属性注入,如下:
//在beans标签内写bean子标签
<!--配置book类-->
<bean id="book" class="com.wang.Book">
<!--使用property标签完成属性注入
name属性是类属性名,
value是要注入的属性值-->
<property name="bname" value="喵王八"></property>
<property name="bauthor" value="狗兔子"></property>
</bean>
第三步:我们在TestSpring5类中来测试一下,来看看效果是什么,TestSpring5类中测试方法代码及运行结果如下:
@Test
public void testBook1(){
//加载spring配置文件
ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml");
//获取对象
Book book = context.getBean("book",Book.class);
System.out.println(book);
book.printMessage();
}
- 通过有参构造方法实现:
第一步:在src下面创建Order类、
package com.wang;
public class Order {
private String oname;
private String address;
public Order(String oname,String address){
this.address=address;
this.oname=oname;
}
public void orderTest(){
System.out.println("从"+address+"买"+oname);
}
}
第二步:在xml配置文件中配置对象,配置属性注入,如下:
<!--配置Order类-->
<bean id="order" class="com.wang.Order">
<!--如果bean内部没有任何子标签,将通过无参构造器创建对象,但我们现在要通过有参构造器创建-->
<!--通过constractor-arg子标签完成属性注入-->
<constructor-arg name="oname" value="兔兔"></constructor-arg>
<constructor-arg name="address" value="China"></constructor-arg>
</bean>
第三步:在TestSpring5类中来测试一下,方法代码及运行效果如下:
@Test
public void testOrder1(){
//加载spring配置文件
ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml");
//获取对象
Order order=context.getBean("order",Order.class);
System.out.println(order);
order.orderTest();
}
- 通过p名称空间来简化基于set方法注入属性的操作:
和基于set方法唯一的区别就是xml配置更简化了,首先要在xml的beans头标签中添加属性:
xmlns:p="http://www.springframework.org/schema/p"
然后Book类的配置为:
<bean id="book" class="com.wang.Book" p:bname="喵王八" p:bauthor="狗兔子"></bean>
3.注入属性-其他类型属性
(1)null值
在注入属性的时候,这么写:
<!--null值-->
<property name="bauthor">
<null></null>
</property>
测试结果如下:
(2)属性值中包含了特殊符号
- 方法一:实体引用。
- 方法二:CDATE
<property name="address">
<value><![CDATE[<<南京>>]]></value>
</property>
4.注入属性-外部bean
创建Service类和Dao类,在Service里面调用Dao里面的方法
- 在src下 新建两个包service和dao包,然后在service包下新建UserService类,在dao包下新建UserDao接口和UserDaoImpl实现类,代码如下:
public interface UserDao {
void update();
}
public class UserDaoImpl implements UserDao {
@Override
public void update() {
System.out.println("dao update...");
}
}
public class UserService {
private UserDao userdao;
public void setUserdao(UserDao userdao) {
this.userdao = userdao;
}
public void add(){
System.out.println("service add...");
userdao.update();
}
}
通过在xml中进行配置
- 在xml文件中基于set方法注入属性,然后就可以在UserService中调用UserDao的方法了。此处我重新建立了一个xml文件,命名为bean2.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">
<!--配置UserService类-->
<bean id="userService" class="com.wang.service.UserService">
<!--注入属性,外部依赖要使用ref属性-->
<property name="userdao" ref="userDaoImpl"> </property>
</bean>
<!--配置UserDaoImpl类-->
<bean id="userDaoImpl" class="com.wang.dao.UserDaoImpl">
</bean>
</beans>
测试及运行结果:
@Test
public void testUserService(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml");
UserService userService=context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
5.注入属性-内部bean
一对多关系:部门对员工。
- 新建部门类Dept类和员工类Employee类
package com.wang;
//部门类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
@Override
public String toString() {
return "Dept{" +
"dname='" + dname + '\'' +
'}';
}
}
//员工类
public class Employee {
private String ename;
private String gender;
//员工属于某一个部门
private Dept dept;
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public void add(){
System.out.println(dept.toString());
}
}
- 配置xml文件
<!--配置Employee类-->
<bean id="employee" class="com.wang.Employee">
<property name="ename" value="tracy"> </property>
<property name="gender" value="female"> </property>
<!--内部嵌套配置Dept类-->
<property name="dept">
<bean id="dept" class="com.wang.Dept">
<property name="dname" value="保安部"> </property>
</bean>
</property>
</bean>
- 单元测试
@Test
public void testEmployee(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean2.xml");
Employee employee=context.getBean("employee",Employee.class);
System.out.println(employee);
employee.add();
}
6.注入属性-级联赋值
在5的两个类的基础上,除了内部bean,可以有几种不同的xml配置方式:
- 方式一:在bean标签内部用ref属性
- 方式二:……略
7.注入属性-集合属性
(1)一般做法
为了更清楚地展示这部分内容,我们在src下面的com.wang包中新建一个collectionType包。
- 在collectionType包下创建Student类,如下:
package com.wang.collectionType;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Student {
//数组类型
private String[] course;
//List集合类型
private List<String> list;
//Map集合类型
private Map<String,String> map;
//Set集合类型
private Set<String> set;
public void setCourse(String[] course) {
this.course = course;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public Student setSet(Set<String> set) {
this.set = set;
return this;
}
public void testCollection(){
System.out.println(Arrays.toString(course));
System.out.println(list);
System.out.println(map);
System.out.println(set);
}
}
- 在src下新建bean3.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 id="student" class="com.wang.collectionType.Student">
<!--数组类型属性-->
<property name="course">
<array>
<value>java课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list类型属性-->
<property name="list">
<list>
<value>小团团</value>
<value>小坨坨</value>
</list>
</property>
<!--map类型属性-->
<property name="map">
<map>
<entry key="JAVA" value="java"> </entry>
<entry key="PHP" value="php"> </entry>
</map>
</property>
<!--set类型属性-->
<property name="set">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>
</beans>
- 测试代码及运行结果:
@Test
public void testStudent(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean3.xml");
Student student=context.getBean("student",Student.class);
System.out.println(student);
student.testCollection();
}
(2)当集合属性为自定义类
- 在Student类中添加属性和对应的set方法:
private List<Course> courses;
public void setCourses(List<Course> courses) {
this.courses = courses;
}
- 在CollectionType包下新建Course类:
package com.wang.collectionType;
public class Course {
private String cname;
public Course setCname(String cname) {
this.cname = cname;
return this;
}
}
- 配置xml文件,配置Course类的对象:
<!--创建多个course对象-->
<bean id="course1" class="com.wang.collectionType.Course">
<property name="cname" value="Spring5框架"> </property>
</bean>
<bean id="course2" class="com.wang.collectionType.Course">
<property name="cname" value="Redis教程"> </property>
</bean>
- 配置xml文件,配置Student类的新增属性:
<property name="courses">
<list>
<ref bean="course1"></ref>
<ref bean="course2"></ref>
</list>
</property>
- 测试:略
(3)把集合注入部分提取出来
- 在collectionType包下新建Books类,如下:
package com.wang.collectionType;
import java.util.List;
public class Books {
private List<String> bookList;
public void setBookList(List<String> bookList) {
this.bookList = bookList;
}
public void test(){
System.out.println(bookList);
}
}
- 配置xml文件:
首先在beans头标签中需要添加一些代码:
<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"//添加1
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util//添加2
http://www.springframework.org/schema/util/spring-util.xsd">//添加3
具体配置:
<!--提取list集合类型属性注入-->
<util:list id="bookList">
<value>三字经</value>
<value>养兔秘籍</value>
<value>红楼梦</value>
</util:list>
<!--使用上面的集合类型属性注入-->
<bean id="books" class="com.wang.collectionType.Books">
<property name="bookList" ref="bookList">
</property>
</bean>
- 测试,代码及运行效果:
@Test
public void testBooks(){
ApplicationContext context=new ClassPathXmlApplicationContext("bean3.xml");
Books books=context.getBean("books", Books.class);
System.out.println(books);
books.test();
}
8.工厂Bean
spring中有两种Bean,前面全都是普通Bean(在配置文件中配置的Bean类型就是返回类型),另外一种是工厂Bean(在配置文件中配置的Bean类型可以和返回类型不同)。实现过程为:
- 创建一个类,让这个类作为工厂Bean,实现接口FactoryBean。
- 实现接口中的方法,定义返回的Bean类型,可以和原本类的类型不同,也就是和配置的Bean类型不同。
9.Bean的作用域和生命周期
(1)作用域
在spring里面,可以设置bean实例是单实例(只创建一个对象,供多次使用)和多实例(每次使用都创建一个新对象)。默认情况下,bean是单实例对象。
如何设置单实例和多实例?在bean标签中使用属性scope,值为singleton表明为单实例,prototype为多实例。
除了单实例和多实例的区别singleton和prototy的区别:若设置为scope时,加载配置文件时就自动创建了对象;若设置为prototype时,在调用getBean方法时才创建多实例对象。
(2)生命周期
从对象创建到对象销毁的基本过程:
- ①通过构造器创建bean实例(无参构造器)。
- ②为bean的属性设置值和其他bean引用(调用set方法)。
- ③调用bean的初始化方法(需要进行配置):先在类中定义初始化方法,然后在xml文件中的对应bean标签中添加属性init-method=“方法名”。
- ④bean的获取和使用。
- ⑤当容器在关闭时,会调用bean的销毁方法(需要进行配置):首先在类中定义销毁时能起提示作用的方法,然后在xml文件中对应的bean标签中添加属性destory-method=“方法名”,最后在使用bean的java代码中调用ClassPathXmlApplicationContext类中的close方法(即
context.close()
),就可以销毁bean了。
另外,关于bean的后置处理器的部分此处不作说明。
10.自动装配
通过在xml配置文件的bean标签中使用属性autowire来实现,值为byName表明是根据属性名称自动装配(属性名称要和要注入的bean的id相同),属性值为byType表明是根据属性类型自动装配(根据属性的类型自动注入,其属性对应类型的bean不能注入多个)。
11.外部属性文件
以配置数据库连接池为例:
-
在src目录下创建外部属性文件,properties格式文件,写数据库信息。比如命名为jdbc.properties。
-
在xml配置文件中的beans头标签中引入context名称空间:
<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"
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/util
http://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context //增加的部分
http://www.springframework.org/schema/context/spring-context.xsd">//增加的部分
- 在spring的xml配置文件中使用标签引入外部配置文件:
<context:property-placeholder location="classpath:jdbc.properties" />//classpath表明文件在src路径下
- 在xml配置文件中配置连接池:
<property name="url" value="&{properties文件中对应属性key的名称}"><property>