Spring IOC的使用以及原理
1. IOC中bean对象的使用以及模拟实例化过程
关于bean对象的实例化有以下三种方法:
- 构造器实例化(最常用)
- 静态工厂方法实例化
- 实例方法工厂实例化
我们以第一种方法介绍实例化的过程:
在创建的maven项目的基础上,为了实现一个简单地spring框架的功能,需要在pom文件中引入SpringContext的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.16</version>
</dependency>
之后在对应的main文件夹下创建resources文件夹,在里面写.xml配置文件,配置文件名称、数量没有规定。之后,如果需要将某个对象实例化,那么就将该该对象以id和class的形式配置在配置文件中,之后再在测试程序中通过new ClassPathXmlApplicationContext("配置文件名.xml")形式获取到bean容器运行的上下文,之后通过getBean(“配置文件中bean的id”)获取到所需要的类的实例,再调用其中的方法。
package org.example.dao;
public class UserDao {
public void test(){
System.out.println("userDao!!! test");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 设置javabean对应的bean标签-->
<bean id="userDao" class="org.example.dao.UserDao" ></bean>
<bean id="userService" class="org.example.service.UserService"></bean>
</beans>
public class App
{
public static void main( String[] args )
{
ApplicationContext ac = new ClassPathXmlApplicationContext("spring01.xml");
UserDao userDao = (UserDao) ac.getBean("userDao");
userDao.test();
}
}
这是整个运行效果。
2. IOC注入
为了降低整个系统之间的耦合度,同时方便各个类之间的相互调用,我们使用IOC依赖注入来达成这一目标,将在一个bean中注入新的属性这一功能交给spring来完成。IOC注入分为自动注入和手动注入
2-1. 手动注入
- setter注入
- 构造器注入
- 静态工厂注入
- 实例工厂注入
- 基于注解的注入
最主要是前两种,因此对前两种加以说明。
setter方法注入
- 在被注入的bean对应的类文件中添加注入对象的变量形式以及构造方法
- 在需要被注入的bean标签里面加入
标签,其中name是注入的属性名称,ref是被注入的bean对象的id,如果注入的对象是一个bean,那么这时候的name和ref是一致的。
package org.example.dao;
import org.example.service.UserService;
public class UserDao {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void test(){
System.out.println("userDao!!! test");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<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
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
"
>
<!-- 设置javabean对应的bean标签-->
<bean id="userDao" class="org.example.dao.UserDao" >
<property name="userService" ref="userService"></property>
</bean>
<bean id="userService" class="org.example.service.UserService"></bean>
</beans>
构造器注入
和setter注入一样,也需要先写好变量名以及构造方法,但是这里的构造方法是被注入对象的构造方法
package org.example.dao;
import org.example.service.UserService;
public class UserDao {
private UserService userService;
public UserDao(UserService userService) {
this.userService = userService;
}
// public void setUserService(UserService userService) {
// this.userService = userService;
// }
public void test(){
System.out.println("userDao!!! test");
userService.test();
}
}
之后在配置文件的bean标签中加入的
<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
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
"
>
<!-- 设置javabean对应的bean标签-->
<bean id="userDao" class="org.example.dao.UserDao" >
<constructor-arg name="userService" ref="userService"></constructor-arg>
</bean>
<bean id="userService" class="org.example.service.UserService"></bean>
</beans>
正常情况下使用构造器注入是没有问题的,但是如果互相注入会导致循环依赖问题。构造器在实例化的时候会先去找构造器的参数,然后再去实例化对象,如果出现循环依赖,可以通过改为set注入解决,那为什么set注入不会循环依赖呢?因为set注入是先实例化对象,然后再找被注入的属性set进来。
2-2. 基于注解的自动注入
需要在配置文件中开启自动注入
<context:annotation-config>
关于@resources有如下说明
1 注解默认通过属性名称查找对应的bean对象(属性字段名称与bean标签的id属性一致)
2 如果属性字段名称不一样,则会通过类型(class)查找
3 属性字段可以提供set方法,也可以不提供
4 注解可以声明在属性字段上,或者set方法级别
5 可以设置注解的name属性,name属性值要与bean标签的id属性值一致(如果设置了name属性,则会使用name属性查询bean对象)
6 当注入接口时,如果接口只有一个实现类,则正常实例化;如果接口有多个实现类,则需要使用name属性指定需要被实例化的bean对象
@Autowired要注意的点:
1 该注解默认通过类型(class)查找bean对象,与属性字段无关,这个@resources不同
2 属性可以提供set方法,也可以不提供set方法
3 注解可以声明在属性级别或set级别
4 可以添加@Qualifier结合使用,通过value属性值查找bean对象(value属性必须设置,且值要与bean比钱的id属性值对应)
@resource注解和@Autowired注解是有很大区别的,比如@Resource首先是通过名字去寻找被注入的bean,而@Autowired则是通过类型(class)去查找bean对象
3. SpringIOC扫描器
bean太多了总不可能每个bean都配置,所以使用扫描器来得到bean对象,扫描的话剧需要给类标注注解,让扫描器知道这个类要被扫描。扫描器的特点如下:
1 设置自动化扫描的范围
如果bean对象未在指定的保范围,即使声明了注解,也可能无法实例化
2 使用指定的注解(声明在类级别) bean对象的id属性默认是类的首字母小写
Dao层:
@Repository
Service层
@Service
Controller层
@Controller
任意类
@Component
4. IOC仿写
在IOC的过程中,最重要的就是ClassPathXmlApplicationContext这个方法找到实例化的bean对象的容器上下文,因此尝试手动实现这个类
4-1 手动实现ClassPathXmlApplicationContext
package org.example.spring;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 模拟Spring的实现
* 1 通过构造器得到对应的配置文件
* 2 通过dom4j解析配置文件(本程序中的spring.xml)得到list集合(list存放xml文件中的bean标签的id和class属性值)
* <p>
* 3 通过反射得到对应的实例化对象,放置在map中,键为id,值为class对应的实例(遍历list集合 通过获取对应的class属性,获取class属性的方法是利用class.forName(XX).newInstance)
* <p>
* 4 通过id属性获取指定的实例化对象
*/
public class MyClassPathXmlApplicationContext implements MyFactory {
/**
* 存放从配置文件中获取到的bean标签得到的id和class文件
*/
List<MyBean> beanList = new ArrayList<>();
/**
* map存放实例化对象,key存放id value存放实例化对象
*/
private Map<String, Object> map = new HashMap<>();
/**
* 1 因为需要通过带参构造器来获得对应的配置文件,因此这里需要一个构造方法
*
* @param filename
*/
public MyClassPathXmlApplicationContext(String filename) {
/**
* 2 通过dom4j解析配置文件 得到list集合
*
*
*
*/
this.parsXml(filename);
/**
* 3 通过反射得到实例化对象,放置在map中
*/
this.instanceBean();
}
/**
* 2.1 获取解析器
* 2.2 获取配置文件的URL
* 2.3 通过解析器配置文件
* 2.4 通过xpath语法解析 获取beans标签下的所有bean标签
* 2.5 指定解析语法解析文档对象 返回元素集合
* 2.6 判断元素集合是否为空
* 2.7 如果元素集合不为空 便遍历集合
* 2.8 获取bean标签元素的属性(id和class属性)
* 2.9 获取MyBean对象,将id和class属性值设置到对象中,再将对象设置到MyBean集合中
*/
private void parsXml(String filename) {
/**
* 2.1 获取解析器
*/
SAXReader saxReader = new SAXReader();
/**
* 2.2 获取配置文件的URL
*/
/**
* 再说明一下
* Object类的getClass()方法返回的是该对象的运行时类,一个对象的运行时类是该对象通过new创建时指定的类
*/
URL url = this.getClass().getClassLoader().getResource(filename);
/**
* 2.3 通过解析器解析配置文件
*/
try{
Document document = saxReader.read(url);
/**
* 2.4 通过xpath语法解析 获取beans标签下的所有bean标签
*/
XPath xPath = document.createXPath("beans/bean");
/**
* 2.5 指定解析语法解析文档对象 返回元素集合
*/
List<Element> elementList = xPath.selectNodes(document);
/**
* 2.6 判断元素集合是否为空
*/
if(elementList!=null&&elementList.size()>0){
/**
* 2.7 如果元素集合不为空 便遍历集合
*/
for (Element el : elementList) {
/**
* 2.8 获取bean标签元素的属性(id和class属性)
*/
String id = el.attributeValue("id");
String clazz = el.attributeValue("class");
/**
* 2.9 获取MyBean对象,将id和class属性值设置到对象中,再将对象设置到MyBean集合中
*/
MyBean myBean = new MyBean(id,clazz);
beanList.add(myBean);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 3.1 判断集合是否为空,如果不为空,则遍历集合,获取对象的id和class属性
* 3.2 通过类的全路径名反射得到实例化对象class.forName(class).newInstance()
* 3.3 将对应的id和实例化好的bean对象设置到map中
*/
private void instanceBean() {
/**
* 3.1 判断集合是否为空,如果不为空,则遍历集合,获取对象的id和class属性
*/
if(beanList!=null&&beanList.size()>0){
for (MyBean bean : beanList) {
String id = bean.getId();
String clazz = bean.getClazz();
try{
/**
* 3.2 通过类的全路径名反射得到实例化对象class.forName(class).newInstance()
*/
Object object = Class.forName(clazz).newInstance();
/**
* 3.3 将对应的id和实例化好的bean对象设置到map中
*/
map.put(id,object);
}catch (Exception e){
e.printStackTrace();
}
}
}
}
/**
* 4 通过id属性从map中获取指定的实例化对象
*
* @param id
* @return
*/
@Override
public Object getBean(String id) {
Object object = map.get(id);
return object;
}
}
在这个类的实现过程中还需要bean工厂以及Mybean对象
package org.example.spring;
import lombok.*;
/**
* bean属性对象
*
* 用来存放配置文件中bean标签对应的id和class属性
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyBean {
//bean标签的id属性值
private String id;
//bean标签的class属性值
private String clazz;
}
package org.example.spring;
/**
* 创建一个工厂作为bean对象工厂接口的定义
*/
public interface MyFactory {
//通过id属性值获取对象
public Object getBean(String id);
//工厂的实现类
}