理解Spring IOC(依赖注入),看这一个实例就好了!
————《java高级程序设计》深入反射的实例文章目录一、前提二、需求分析三、项目设计四、实例UML图五、源码(1)储存注册对象信息(2)IOC容器(3)注册器(4)测试实体与xml文件(4)测试实体与xml文件(5)测试(6)结果一、前提当我们使用对象B时,一般的情况是使用new B()
————《java高级程序设计》深入反射的实例
一、前提
当我们使用对象B时,一般的情况是使用new B()的方式,但是在我们一个大的项目里面,有复杂的业务逻辑,这时B已经无法修改了。那么如何解决这个问题呢?「Spring的核心概念控制反转」就可以解决这个问题。
那么「控制反转」是什么呢?
对于我们初学者来说「控制反转」「依赖注入」是很生涩的词语,但其实就是动态的对象管理和对象依赖,本来我们的对象都是new
出来的,而我们如果使用Spring
则把对象交给「IOC容器」来管理,这样我们对这个对象的控制权就转交给了这个「IOC容器」。
那么「IOC容器」又是什么呢?
「IOC容器」就相当一个「工厂」(接口BeanFactory
,即使IOC的具体表现),我们把对象交给这个「工厂」来管理包括Spring
中的对象注册(有很多种方式如配置文件、标注、也可以根据自己的需求扩展注册方式),与取出对象。
而「依赖注入」即是「控制反转」的实现方式,对象不需要自行创建或管理它们的依赖关系,依赖关系将自动注入到它们的对象中去,如:A需要B的功能,但是又不能直接实例化,那么它就只能依赖Spring
的自动注入,就是把用户需要的实例从注入到用户实体中。
使用IOC的好处:
-
统一管理对象,便于修改
-
耦合性降低(调用方无需自己组装,也无需关心对象的实现,直接从「IOC容器」取)
-
使我们的代码易于维护
二、需求分析
- 含有一个IOC的简单Spring。含有对象注册,对象管理,外部获取对象功能
- 对象注册要求方式多样,可灵活扩展。
三、项目设计
- 使用
BeanInfo
来描述信息,包含对象的标识 - 对于IOC容器设定一个顶层接口
BeanFactory
,该接口中定义通过对象名称获取对象的方法getBean(String name)
。AbstractBeanFactory
实现该接口,在该类中实现解析生成目标对象,以及获取目标对象方法,并在该类中添加注册器接口,以便能从注册器中读取注册的对象。 - 对于注册器来说,提供一个顶层的接口
SourceReader
,并在其中添加加载用户注册的对象的方法loadbeans(String path)
,具体的实现根据不同的方式而定。本项目中需有一个默认的测试实现XMLSourceReader
,该对象负责模拟读取用户注册的对象,并把这些对象封装成BeanInfo
放入一个Map
中。 - 设定一个上下文
XMLContext
,该上下文负责选择使用哪种注册方式,并决定何时加载注册的对象。
四、实例UML图
五、源码
(1)储存注册对象信息
public class BeanInfo {
private String id; //对象的标识
private String type; //对象的类型,既是全类名
private Map<String,Object> properties = new HashMap<>(); //对象的属性以及值的集合
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Map<String, Object> getProperties() {
return properties;
}
public void addProperty(String name, Object value) {
properties.put(name,value);
}
public void setProperties(Map<String, Object> properties) {
this.properties = properties;
}
}
(2)IOC容器
- BeanFactory(顶层接口)
public interface BeanFactory {
/**
* 根据对象的名称标识来获取对象实例
* @return
*/
Object getBean(String name);
}
- AbstractBeanFactory(抽象父类)
/**
* @Auther: Parsifal
* @Date: 2021/05/07/14:51
* @Description:
* 最顶层的IOC实现
* 该类负责从注册器中取出注册对象
* 实现从对象描述信息转换为对象实例的过程
* 根据名称获取对象的方法
*/
public abstract class AbstractBeanFactory implements BeanFactory{
private String filePath; //注册文件路径
private Map<String,BeanInfo> contain; //注册对象信息Map
protected SourceReader reader; //对象注册读取器
public AbstractBeanFactory(String filePath) {
this.filePath = filePath;
}
/**
* 抽象方法:需要由子类实现,用于指定使用什么样的注册读取器
* @param reader 指定的注册读取器
*/
protected abstract void setSourceReader(SourceReader reader);
/**
* 从注册器中读取注册对象信息Map
*/
public void registerBeans(){
this.contain = this.reader.loadBean(filePath);
}
/**
* 实现BeanFactory定义的根据名称获取指定对象的方法
* @param name
* @return
*/
@Override
public Object getBean(String name) {
BeanInfo beanInfo = this.contain.get(name);
//根据名字获取对象的描述信息
if (beanInfo == null) {
//如果存在对象的描述信息,返回null,也可抛开异常
return null;
}else {
//根据对象信息解析并生成指定对象的实例
return this.parseBean(beanInfo);
}
}
/**
* 解析并生成对象实例
* 该方法主要通过反射完成,步骤如下:
* 1.根据类名,加载指定类,并获取该类的Class对象clazz
* 2.使用class对象clazz实例化该类,获取一个对象,注意,这里实例化对象时,采用的是无参构造方法,因此要求注册的对象必须含有无参构造方法
* 3.逐个设置对象字段的值,这里采用setter Method方式,而不是直接使用Field对象的原因是,用户有可能在setter对象中,对注入的值进行额外处理,如格式化等
* 4.返回对象实例
* @param beanInfo 指定对象的信息
* @return
*/
protected Object parseBean(BeanInfo beanInfo){
try {
//通过对象的全类名获取指定类
Class<?> clazz =Class.forName(beanInfo.getType());
//实例化对象(无参构造方法)
Object bean = clazz.newInstance();
//获取所有的方法
Method[] methods = clazz.getDeclaredMethods();
//遍历beanInfo中所有的属性,进行赋值
for (String property:beanInfo.getProperties().keySet()){
//获取该属性对于的setter方法
String setter = "set"+ StringUtil.firstCharToUp(property);
for (Method method:methods){
//比较方法名字是否相同
if (setter.equals(method.getName())){
//根据属性名称获取对应的值
Object value= beanInfo.getProperties().get(property);
Class<?>[] parameterTypes = method.getParameterTypes();
//使用setter方法对该属性赋值
method.invoke(bean,StringUtil.convert(parameterTypes[0],value.toString()));
break;
}
}
}
return bean;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
- XMLContext(选择注册方式的实现类)
/**
* @Auther: Parsifal
* @Date: 2021/05/07/15:29
* @Description: 上下文的构造方法
* 该方法中指明注册器读取器
* 并在构造该方法是一次性加载注册的对象
*
*/
public class XMLContext extends AbstractBeanFactory {
public XMLContext(String filePath) {
super(filePath);
// 设置扫描器
this.setSourceReader(new XMLSourceReader());
// 注册对象信息 从xml文件中读取信息
this.registerBeans();
}
/**
* 用于指定使用什么样的注册读取器
* @param reader 指定的注册读取器
*/
@Override
protected void setSourceReader(SourceReader reader) {
this.reader=reader;
}
}
(3)注册器
- SourceReader(顶层接口)
/**
* @Auther: Parsifal
* @Date: 2021/05/07/14:55
* @Description:
* 注册读取器接口
* 负责读取用户注册的对象
* 继承该接口的类可以实现多种读取方式,如从配置文件中读取,根据标注读取,从网络中读取
*/
public interface SourceReader {
/**
* 读取用户注册的对象信息
* @param filePath 读取路径
* @return 注册对象信息Map
*/
Map<String,BeanInfo> loadBean(String filePath);
}
- XMLSourceReader(xml文件读取器)
/**
* @Auther: Parsifal
* @Date: 2021/05/07/15:28
* @Description: xml注册读取器,并模拟实现读取注册对象信息的方法
*/
public class XMLSourceReader implements SourceReader{
/**
* 实现读取注册对象的信息方法
* @param filePath 读取路径
* @return
*/
@Override
public Map<String, BeanInfo> loadBean(String filePath) {
//文档解析工厂
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
//储存BeanInfo的map
HashMap<String, BeanInfo> entity = new HashMap<>();
try {
//获得文件构造器
DocumentBuilder db = dbFactory.newDocumentBuilder();
//根据文件路径获得元素
Document document = db.parse(filePath);
//获得根元素 <entity>
Element root = document.getDocumentElement();
//获得<entity>下的 <bean>即是实体
NodeList beans = root.getElementsByTagName("bean");
// for (int i = 0; i <beans.getLength(); i++) {
BeanInfo beanInfo = new BeanInfo();
//获取有bean元素标签的第一个element
Element element = (Element) beans.item(0);
//获得 实体的ID即是实体的名称 Person
String id = element.getAttribute("id");
beanInfo.setId(id);
//获得 实体的class 即是 实体的类型 即全名 SpringIOCTest.com.parsifal.entity.Person
String type = element.getAttribute("class");
beanInfo.setType(type);
//获得实体的属性名称以及相应值
NodeList properties = element.getElementsByTagName("property");
for (int j = 0; j < properties.getLength(); j++) {
Element property = (Element) properties.item(j);
beanInfo.addProperty(property.getAttribute("name"),property.getAttribute("value"));
}
entity.put(id,beanInfo);
// }
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
return entity;
}
}
(4)测试实体与xml文件
//测试接口
public interface Speakable {
public void speak();
}
//测试实体
public class Person implements Speakable {
private String name;
private int age;
public Person() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
System.out.println(Person.class);
}
@Override
public void speak() {
System.out.println("name:"+name+"age:"+age);
}
}
(4)测试实体与xml文件
<entity>
<bean id="Person" class="SpringIOCTest.com.parsifal.entity.Person">
<property name="name" value="李四"></property>
<property name="age" value="18"></property>
</bean>
</entity>
(5)测试
//测试
public static void main(String[] args) {
BeanFactory factory = new XMLContext("src\\SpringIOCTest\\resources\\test.xml");
Speakable speakable = (Speakable) factory.getBean("Person");
speakable.speak();
}
(6)结果
更多推荐
所有评论(0)