IoC容器
一、IoC底层原理
1、什么是IOC
(1)控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理
(2)使用IoC的目的:降低耦合度
(3)Spring入门案例就是IoC实现的
2、IoC底层原理
(1)xml解析、工厂模式、反射
(2)IoC底层原理
xml解析+反射+工厂模式
二、IoC接口(BeanFactory)
注:IoC本质上是一个容器,IoC思想基于IoC容器完成,IoC容器的底层是对象工厂。
1、Spring提供了IoC容器实现的两种方式,即两个接口。这两个接口的功能相似,都能实现加载配置文件,都能通过工厂的过程加载创建对象。
(1)BeanFactory
BeanFactory是IoC容器基本实现,是Spring内部的使用接口,不提供给开发人员使用
BeanFactory在加载配置文件的时候,不会创建对象,在获取/使用的时候才去创建对象。
(2)ApplicationContext
ApplicationContext是BeanFactory接口的子接口,比BeanFactory更加强大,提供了更多更强大的功能,一般由开发人员进行使用,在开发中一般都使用该接口。
ApplicationContext在加载配置文件的时候就会创建配置文件中的对象
*******ApplicationContext接口中主要的实现类
(I)FileSystemXmlApplicationContext:在配置文件中配置xml文件路径时需要将xml的全路径写出来(带盘符路径)。默认获取的是项目路径,默认文件路径是项目名下一级,与src同级,如果前边加了file:则说明后边的路径就要写全路径了,就是绝对路径,如:file:D:/workspace/applicationContext.xml
(II)ClassPathXmlApplicationContext:这个表示内路径,即默认文件路径是src下那一级
2、IoC操作:Bean管理
(1)什么是Bean管理
Bean管理具体指的是两个操作:Spring创建对象、Spring注入属性(即给类中的属性赋值,可以是基本数据类型,也可以是对象)
(2)Bean管理可以通过两种方式实现:基于Xml配置文件方式实现、基于注解方式实现
三、IoC操作Bean管理(基于xml)
1、基于xml方式创建对象
在Spring配置文件中加bean标签,标签中添加对应的属性,就可实现对象的创建。如:
<!--配置User类对象的创建-->
<bean id="user" class="com.university.spring5.User"></bean>
(1)bean中有两个重要的属性,接下来依次介绍
(a)id属性
id属性并不是指创建对象的名字,而是创建的对象的唯一标识,或者说是一个别名,通过这个标识/别名可以得到这个对象
(b)class属性
class属性是指创建的对象所属的类的全路径(包+类的路径)
(2)创建对象时,默认使用的是类中的无参构造方法(在类中,如有有参构造方法,但不显式声明无参构造,默认没有无参构造方法)。
(a)无有参构造方法,默认使用无参构造方法
配置文件如下:
<?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">
<!--配置User类对象的创建,这里会去调用无参构造器-->
<bean id="user" class="com.university.spring5.User"></bean>
</beans>
User类如下:
package com.university.spring5;
public class User {
public void add(){
System.out.println("add......");
}
}
测试类如下:
package com.university.spring5.testDemo;
import com.university.spring5.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
@Test
public void testF(){
//1、记载配置文件.注:若配置文件不在src下面,需要使用到FileSystemXmlApplicationContext,并且需要将配置文件的路径写全
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//2、获取配置创建的对象
User user=applicationContext.getBean("user",User.class);
user.add();
}
}
执行结果:
(b)显式声明有参构造方法,
配置文件如下:
<?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">
<!--配置User类对象的创建-->
<bean id="user" class="com.university.spring5.User"></bean>
</beans>
User类如下:
package com.university.spring5;
public class User {
private String userName="A";
public User(String userName) {
this.userName = userName;
}
public void add(){
System.out.println("add......");
}
}
测试类如下:
package com.university.spring5.testDemo;
import com.university.spring5.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
@Test
public void testF(){
//1、记载配置文件.注:若配置文件不在src下面,需要使用到FileSystemXmlApplicationContext,并且需要将配置文件的路径写全
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//2、获取配置创建的对象
User user=applicationContext.getBean("user",User.class);
user.add();
}
}
运行结果:
分析:如上代码分析,配置文件、测试类都是一致的,但是User类显式声明有参构造与没有声明有参构造得到的运行结果是不一致的,声明了有参构造之后程序运行失败,因为在Spring中在创建对象是默认调用的是无参构造器,但声明了有参构造器之后并没有声明无参构造,导致程序找不到无参构造器,所以导致程序出错。使用有参构造器创建对象的方法在下文有体现。
2、基于xml方式注入属性
在IoC中有一个概念:DI,什么是DI?DI是IoC的具体实现,依赖注入,即注入属性。注入属性的前提是对象的创建。
注入属性即给属性赋值,在原始的方式中可以有两种初始化属性的方式:使用set方法初始化属性、使用有参构造器初始化属性。在Spring中也支持这两种方式初始化属性。
(1)使用set方法注入属性
book类如下:
package com.university.spring5;
/**
* 使用set方法注入属性
*/
public class book {
//定义属性
private String bname;
private String bauther;
//声明对应属性的set方法
public void setBname(String bname) {
this.bname = bname;
}
public void setBauther(String bauther) {
this.bauther = bauther;
}
public void printTest(){
System.out.println("bName="+bname+" and bAuther="+bauther);
}
}
如原始使用set方法一致,都需先创建对象,再进行属性的初始化,如下:
<?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">
<!--1 使用bean标签配置book类对象的创建-->
<bean id="book" class="com.university.spring5.book">
<!--2 在bean标签中使用property标签作为bean的子标签完成属性的注入,其中name是指类中属性名,value是指向属性中注入的值,即给属性附的值,这里是使用相关属性的set方法去注入属性的值的,所以如果没有set方法的话会报错,注入值会失败-->
<property name="bname" value="java基础"></property>
<property name="bauther" value="java_bauther"></property>
</bean>
</beans>
测试类代码如下:
package com.university.spring5.testDemo;
import com.university.spring5.User;
import com.university.spring5.book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
@Test
public void testBook(){
//1、加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//2、得到book类的对象
book book = applicationContext.getBean("book",book.class);
//3、使用book类中的方法测试属性值是否注入成功
book.printTest();
}
}
运行结果:
如上图所示,属性值注入成功。
(2)使用有参构造器注入属性
Order类如下:
package com.university.spring5;
public class Order {
//1、定义属性
private String oName;
private String oAddress;
//2、声明有参构造器
public Order(String oName, String oAddress) {
this.oName = oName;
this.oAddress = oAddress;
}
public void printTest(){
System.out.println("我要去" + oAddress + "吃" + oName);
}
}
配置文件如下:
<?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">
<!--1 使用bean标签配置Order类对象的创建-->
<bean id="order" class="com.university.spring5.Order">
<!--2 在bean标签中使用constructor-arg标签作为bean的子标签完成属性的注入,其中name是指类中属性名,value是指向属性中注入的值,即给属性附的值,还有个index属性是指可以使用属性的下标指定属性,但我们常用name比较不容易出错-->
<constructor-arg name="oName" value="冰激凌"></constructor-arg><!--等同于<constructor-arg index="0" value="冰激凌"></constructor-arg>-->
<constructor-arg name="oAddress" value="小吃城"></constructor-arg><!--等同于<constructor-arg index="1" value="小吃城"></constructor-arg>-->
</bean>
</beans>
测试类如下:
package com.university.spring5.testDemo;
import com.university.spring5.Order;
import com.university.spring5.User;
import com.university.spring5.book;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
@Test
public void testOrder(){
//1、加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//2、得到Order类的对象
Order order = applicationContext.getBean("order", Order.class);
//3、使用Order类中的方法测试属性值是否注入成功
order.printTest();
}
}
运行结果:
(3)使用P名称空间注入,可以简化基于XML配置方式。在上述(1)使用set方法注入属性的方法中,都是通过标签一个一个属性依次注入的,如果属性过多的话,代码量就会很多,可读性就会降低,P名称空间注入可以解决这个问题。注:p名称空间注入只针对于使用set方法进行注入属性才有效。
第一步:添加p名称空间在配置文件中
第二步:进行属性注入,bean标签里面进行注入。如(1)使用set方法注入属性中的配置文件中使用property标签进行属性注入,如下:
<bean id="book" class="com.university.spring5.book">
<property name="bname" value="java基础"></property>
<property name="bauther" value="java_bauther"></property>
</bean>
使用p名称空间的配置文件如下:
<bean id="book1" class="com.university.spring5.book" p:bname="C语言基础" p:bauther="C语言基础_auther"></bean>
相比较使用property标签和使用p名称空间要简洁很多。
(4)使用xml配置文件方式注入其他类型的属性
(a)空值,即null值
在property标签中使用null子标签,如下:
<!--设置null值-->
<bean id="book2" class="com.university.spring5.book">
<!--设置bname为null值-->
<property name="bname">
<null></null>
</property>
<!--设置bauther为test-->
<property name="bauther" value="test"></property>
</bean>
测试方法代码如下:
@Test
public void testBook(){
//1、加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//2、得到book类的对象
book book = applicationContext.getBean("book2",book.class);
//3、使用book类中的方法测试属性值是否注入成功
book.printTest();
}
运行结果如下:
(b)注入的属性值中包含特殊字符,如:“<”,若我们直接将“<”作为正常的值传入property标签的value属性中,是会报错的,代码中会将属性值中的“<”与property前面的“<”的符号是一样的,“>”也是如此。如下演示:
配置文件如下代码:
<!--属性值中包含特殊符号,如“<”、“>”-->
<bean id="book3" class="com.university.spring5.book">
<property name="bname" value="<<Java>>"></property>
</bean>
测试代码:
@Test
public void testBook(){
//1、加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//2、得到book类的对象
book book = applicationContext.getBean("book3",book.class);
//3、使用book类中的方法测试属性值是否注入成功
book.printTest();
}
运行结果:
正确的配置文件如下:
第一种方式:将“<”“>”进行转义,如下:
<!--属性值中包含特殊符号,如“<”、“>”-->
<bean id="book3" class="com.university.spring5.book">
<!--将“<”转义为lt,将“>”转义为gt,注意lt和gt后面要加英文状态下的分号-->
<property name="bname" value="<Java>"></property>
</bean>
第二种方式:把带特殊符号的内容写到CDATA中,如下:
<!--属性值中包含特殊符号,如“<”、“>”-->
<bean id="book3" class="com.university.spring5.book">
<!--将“<”转义为lt,将“>”转义为gt,注意lt和gt后面要加英文状态下的分号-->
<property name="bname" value="<Java>"></property>
<!--将特殊符号内容放到CDATA中-->
<property name="bauther">
<value><![CDATA[<JAVA_auther>]]></value>
</property>
</bean>
(c)注入属性-外部bean
什么是外部bean?
即在一个bean里面调用了外面的另外一个bean,详细看配置文件。
注:这里通常可以使用set方法和有参构造进行注入,不同的是之前注入的是属性,但是现在注入的是dao中的类的对象,是一个对象类型。
(i)创建两个包以及相应的类service类和dao类
(ii)在service中调用dao里面的方法
dao中的接口代码:
package com.university.spring5.dao;
public interface UserDao {
public void update();
}
dao中实现类代码:
package com.university.spring5.dao;
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("successfully");
}
}
(iii)在配置文件中进行配置
<?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">
<!--1、创建service和dao类对象的创建-->
<bean id="userService" class="com.university.spring5.service.UserService">
<!--注入userDao对象
name:service类里面属性的名称
ref:创建userDao对象的bean标签的id值
在这里,userDao对于userService来说就是外部bean
-->
<property name="userDao" ref="userDaoImpl"></property>
</bean>
<bean id="userDaoImpl" class="com.university.spring5.dao.UserDaoImpl"></bean>
</beans>
测试方法代码:
@Test
public void serviceToDaoTest(){
//1、加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml");
//2、UserService
UserService userService = applicationContext.getBean("userService", UserService.class);
//3、使用userDao类中的方法测试是否注入成功
userService.add();
}
运行结果:
(d)注入属性-内部Bean和级联赋值
假设有部门类和员工类,一个部门里面可以有多个员工,一个员工只能属于一个部门,这是一对多的关系。
部门类:
package com.university.spring5.bean;
//部门类
public class Dept {
public String bName;
public String getbName() {
return bName;
}
public void setbName(String bName) {
this.bName = bName;
}
}
员工类
package com.university.spring5.bean;
//员工类
public class Emp {
public String eName;
public String gender;
public Dept dept;
public String geteName() {
return eName;
}
public void seteName(String eName) {
this.eName = eName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public void printEmp(){
System.out.print("eName=" + geteName() + ";gender=" + getGender() + ";Dept=" + this.dept.getbName());
}
}
配置文件
<?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里面可以嵌套另外一个bean-->
<bean id="emp" class="com.university.spring5.bean.Emp">
<!--设置两个普通属性-->
<property name="eName" value="amy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.university.spring5.bean.Dept">
<property name="bName" value="应用开发部"></property>
</bean>
</property>
</bean>
</beans>
测试类代码
@Test
public void empTest() {
// 1、加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean2.xml");
// 2、Emp
Emp emp = applicationContext.getBean("emp", Emp.class);
// 3、使用Emp中的方法测试是否注入成功
emp.printEmp();
}
运行结果
所谓级联赋值就是说在我给emp赋值的时候就给dept赋值了,以上是以内部bean的方式做到的,同样外部bean的方式也是可以做到的。
(e)注入数组类型、集合类型的属性i
新建一个类,里面包含了数组类型、集合类型的属性
package com.university.spring5;
import java.util.*;
public class Stu {
String[] course;
List<String> listTest = new ArrayList<>();
Map<String, String> mapTest;
public void setCourse(String[] course) {
this.course = course;
}
public void setListTest(List<String> listTest) {
this.listTest = listTest;
}
public void setMapTest(Map<String, String> mapTest) {
this.mapTest = mapTest;
}
public void printNews(){
System.out.print(Arrays.toString(course) + " " + listTest + " " + mapTest);
}
}
配置文件
<?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="stu" class="com.university.spring5.Stu">
<property name="course">
<array>
<value>Java编程基础</value>
<value>离散数学</value>
<value>高等数学</value>
</array>
</property>
<property name="listTest">
<list>
<value>成绩</value>
<value>学分</value>
<value>竞赛得分</value>
</list>
</property>
<property name="mapTest">
<map>
<entry key="大一" value="适应环境"></entry>
<entry key="大二" value="好好学习"></entry>
</map>
</property>
</bean>
</beans>
单元测试方式
@Test
public void stuTest() {
// 1、加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean3.xml");
// 2、Stu
Stu stu = applicationContext.getBean("stu", Stu.class);
// 3、使用Stu中打印方法测试看是否注入成功
stu.printNews();
}
运行结果
(f)注入数组类型、集合类型的属性ii
在i中,我们设置的值都是String类型的,那如果我们需要设置的是对象类型的值那又怎么设置呢?
这里和(e)中是一样的,唯一不同的就是配置文件,所以这里只展示配置文件
<?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="stu" class="com.university.spring5.Stu">
<property name="coursesList">
<list>
<ref bean="courses1"></ref>
<ref bean="courses2"></ref>
</list>
</property>
</bean>
<bean id="courses1" class="com.university.spring5.Courses">
<property name="cName">
<value>马克思主义</value>
</property>
</bean>
<bean id="courses2" class="com.university.spring5.Courses">
<property name="cName">
<value>军事理论</value>
</property>
</bean>
</beans>
四、IoC操作Bean管理(基于xml)-自动装配
在上面的属性注入中都是手动给属性注入值得,即手动装配
1、什么是自动装配
在Spring中根据指定的规则(属性的名称或者时属性类型),Spring自动将匹配的属性值进行注入
2、实现
Emp类
package com.university.autoWrite;
public class Emp {
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
@Override
public String toString() {
return "Emp{" +
"dept=" + dept +
'}';
}
}
Dept类
package com.university.autoWrite;
public class Dept {
}
配置文件
<?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">
<!--Spring中实现自动装配
bean标签属性autoWrie,配置自动装配
autowire属性常用两个值:
byName根据属性名称注入,在这里我们相关的类的id名需要和我们定义的变量名一致,比如在这里我们Dept类的id是dept,在我们即将注入的Emp类使用到Dept的地方的变量名也是dept。
byType根据属性类型注入,根据类型注入的时候相同类型的bean就不能出现两个,如果在这里我们与两个Dept的两个bean就会报错
-->
<bean id="emp" class="com.university.autoWrite.Emp" autowire="byName"></bean>
<bean id="dept" class="com.university.autoWrite.Dept"></bean>
<bean id="emp1" class="com.university.autoWrite.Emp" autowire="byType"></bean>
</beans>
测试类
```java
package com.university.autoWrite;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AutoWriteTest {
@Test
public void autoWireTestByName() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean6.xml");
Emp emp = applicationContext.getBean("emp", Emp.class);
System.out.println(emp.toString());
}
@Test
public void autoWireTestByType() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean6.xml");
Emp emp = applicationContext.getBean("emp1", Emp.class);
System.out.println(emp.toString());
}
}
运行结果