- 刚刚开始学习Spring,我了解到bean的生命周期。一般来讲,首先执行无参构造,然后执行set方法设置bean的属性,然后执行xxxAware接口的setxxx方法,然后执行BeanPostProcessor的PostProcessBeforeInitialization方法,然后执行PostConstruct注解标注的方法,然后执行InitializingBean接口的AfterPropertiesSet方法,然后执行配置的init-method方法,然后执行BeanPostProcessor的PostProcessAfterInitialization方法… 然后用户可以获取这个bean。在bean销毁的时候,则执行PreDestroy注解标注的方法,然后执行Disposable接口的destroy方法,然后执行配置的destroy-method方法… 这是一个很复杂的过程。那么我们能不能根据xml配置文件的内容,用比较简单的方式生成对象实例呢?
- 本文利用Dom4j + 反射 + 设计模式,一切从0开始,简单实现从xml配置文件获取对象实例。最终的结果如下所示。
@Slf4j
public class BeenTest {
@Test
public void test () throws Exception{
ShizuoContext context = new ClassPathXmlShizuoContext("src\\user.xml");
log.info("从Map中获取been");
User user = (User) context.getBeen("user1", User.class);
User user2 = (User) context.getBeen("user1", User.class);
System.out.println("获取been:"+user.toString());
System.out.println(user == user2);//true
}
@Test
public void test2() throws Exception {
ShizuoContext context = new ClassPathXmlShizuoContext("src\\user.xml");
BeenFactory factory = context.getBeenFactory();
BeenFactory factory2 = context.getBeenFactory();
System.out.println("工厂是否是同一个:"+(factory2 == factory));//true
}
}
junit4 indi.huishi.bean.BeenTest,test
[INFO ] 2021-05-07 16:45:22,126 method:indi.huishi.bean.ShizuoGetBeenMap.getBeenMap(ShizuoGetBeenMap.java:28)
创建一个SaxReader输入流,去读取 xml配置文件,生成Document对象
[INFO ] 2021-05-07 16:45:22,165 method:indi.huishi.bean.ShizuoGetBeenMap.getBeenMap(ShizuoGetBeenMap.java:33)
利用反射机制调用无参构造
[INFO ] 2021-05-07 16:45:22,165 method:indi.huishi.bean.ShizuoGetBeenMap.getBeenMap(ShizuoGetBeenMap.java:61)
调用setId方法,设置属性的值:33
[INFO ] 2021-05-07 16:45:22,165 method:indi.huishi.bean.ShizuoGetBeenMap.getBeenMap(ShizuoGetBeenMap.java:61)
调用setName方法,设置属性的值:Huishi
[INFO ] 2021-05-07 16:45:22,165 method:indi.huishi.bean.ShizuoGetBeenMap.getBeenMap(ShizuoGetBeenMap.java:61)
调用setPassword方法,设置属性的值:123456
[INFO ] 2021-05-07 16:45:22,175 method:indi.huishi.bean.ShizuoGetBeenMap.getBeenMap(ShizuoGetBeenMap.java:68)
为对象实例设置name: user1
[INFO ] 2021-05-07 16:45:22,175 method:indi.huishi.bean.ShizuoGetBeenMap.getBeenMap(ShizuoGetBeenMap.java:73)
保存到Been Map
[INFO ] 2021-05-07 16:45:22,175 method:indi.huishi.bean.BeenTest.test(BeenTest.java:19)
从Map中获取been
获取been:User(id=33, name=Huishi, password=123456)
true
- 本文创建对象的原理是Java反射+工厂模式,注意只是借鉴了Spring源码的风格,本文执行的具体流程顺序与Spring有巨大的差异。为了和Spring相区别,我们不妨叫它been。
- 本文获取对象实例流程如下:been工厂初始化-将Dom4j解析xml文件得到的name和been保存在HashMap容器-工厂根据用户输入的名称返回对应的been。
1.Dom4j 解析
- 首先写一个简单的xml配置文件,作为我们要解析的内容。
<?xml version="1.0" encoding="UTF-8"?>
<bean id = "user1" class = "indi.huishi.pojo.User">
<property name = "id" value = "33"/>
<property name = "name" value = "Huishi"/>
<property name = "password" value = "123456"/>
</bean>
我们只需要知道,对于根节点(Element
) bean:
- 它的
attribute
是它后面的内容id = "user1" class = "indi.huishi.pojo.User"
- 它的子节点是
<property name = "id" value = "33"/>
<property name = "name" value = "Huishi"/>
<property name = "password" value = "123456"/>
它们都可以通过Iterator
进行遍历。
- 以上键值对可以通过
getName
和getText
方法得到。 - 所以可以写一个简单的代码遍历它们:
/**
* @Author: Huishi
* @Date: 2021/5/6 22:41
*/
public class Dom4JTest {
@Test
public void test1() throws Exception {
// 创建一个SaxReader输入流,去读取 xml配置文件,生成Document对象
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("src\\user.xml");
System.out.println(document);
Element element = document.getRootElement();
// 打印根节点的attribute
Iterator attributes = element.attributeIterator();
while (attributes.hasNext()) {
Attribute attr = (Attribute) attributes.next();
System.out.println(attr.getName() + " " + attr.getText());
}
// 准备遍历子节点
Iterator iterator = element.elementIterator();
while (iterator.hasNext()){
Element ele = (Element)iterator.next();
// System.out.println(ele.getName());//property
// 打印子节点的attribute
Iterator attributeIterator = ele.attributeIterator();
while (attributeIterator.hasNext()) {
Attribute attr = (Attribute) attributeIterator.next();
System.out.println(attr.getName() + " " + attr.getText());
}
System.out.println("---------------------------------");
}
}
}
是不是很简单
org.dom4j.tree.DefaultDocument@e7d9f1 [Document: name src\user.xml]
id user1
class indi.huishi.pojo.User
name id
value 33
---------------------------------
name name
value Huishi
---------------------------------
name password
value 123456
---------------------------------
- 以上是Dom4j的简单入门。Dom4j可以具体参考【Dom4j】Dom4j完整教程详解
2.Java反射
- 首先建立一个和xml文件对应的实体类
/**
* @Author: Huishi
* @Date: 2021/5/6 22:45
*/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String id;
private String name;
private String password;
}
- Java反射机制可以在运行期间获得类的信息,例如类的构造、属性、方法、注解等等。我们可以利用
Class.forName()
方法, 根据xml中的全类名获取它的Class对象。 - 使用无参构造生成实例instance:我们不妨叫它been。
// 利用反射机制获取对象实例
String textClass = element.attribute("class").getText();
System.out.println(textClass);
Class<?> aClass = Class.forName(textClass);
Constructor<?> constructor = aClass.getDeclaredConstructor();
Object instance = constructor.newInstance();
- 有了Class对象,就可以查看这个类包含的方法,确保lombok确实生成了get set方法。
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
- 类似地,我们也可以根据子节点的name 来得到对应的set方法,然后使用该set方法根据value中的值进行赋值。这也是遍历子节点的过程,所以可以通过前面的代码改造实现。
// 准备遍历子节点
Iterator iterator = element.elementIterator();
while (iterator.hasNext()){
Element ele = (Element)iterator.next();
// System.out.println(ele.getName());//property
// 打印子节点的attribute: name value
Iterator attributeIterator = ele.attributeIterator();
while (attributeIterator.hasNext()) {
Attribute attr = (Attribute) attributeIterator.next();
System.out.println(attr.getName() + " " + attr.getText());//name id value 33
// 如果遍历的是name 例如是id 那么生成一个methodName 为setId
if ("name".equals(attr.getName())) {
methodName = "set" + attr.getText().substring(0, 1).toUpperCase() + attr.getText().substring(1);
System.out.println(methodName);
// 如果遍历的是value 例如是"20" 那么找到类中的method并调用invoke方法
}else if("value".equals(attr.getName())){
Method method = aClass.getMethod(methodName, String.class);
valueName = attr.getText();
System.out.println(valueName);
method.invoke(instance, valueName);
}
}
System.out.println(instance);
System.out.println("---------------------------------");
}
- 类似Spring的bean容器,我们将设置好id和属性值的been装入一个HashMap。
// 为对象实例设置id
String textId = element.attribute("id").getText();
System.out.println(textId);//user1
// 将对象和对应的id加入map
Map<String, Object> beans = new HashMap<>();
beans.put(textId, instance);
System.out.println(beans);
3.工厂模式 单例模式
- 以上我们根据输入xml文件的url,返回包含been的HashMap。我们将其设置为静态方法。
- 借鉴Spring,思路大概如下:需要写ApplicationContext和BeanFactory两部分内容,前者使用后者调用类似于
getBeanFactory().getBean()
的方法获取需要的bean。(当然这里只是借鉴,Spring里面的原理请参考Spring源码)。 - 这是Spring中ApplicationContext 和BeanFactory的关系:
- 我们借鉴一些Spring的思路,设计的类图如下图所示:
- 1.模拟ApplicationContext的接口和抽象类:抽象类的
getBeen()
方法模仿Spring的AbstractApplicationContext 类中的getBeanFactory().getBean()
的方法,使用工厂模式获取been。
/**
* 提供getBeenFactory()方法, 同时继承BeenFactory接口
*/
public interface ShizuoContext extends BeenFactory{
BeenFactory getBeenFactory() throws Exception;
}
/**
* 模仿Spring的AbstractApplicationContext 使用工厂模式
*/
abstract class AbstractShizuoContext implements ShizuoContext{
@Override
public abstract BeenFactory getBeenFactory() throws Exception;
@Override
public <T> T getBeen(String name, Class<T> requiredType) throws Exception {
return getBeenFactory().getBeen(name, requiredType);
}
}
- 模拟ApplicationContext实现类:首先,在构造函数输入配置文件的路径。其次,我们实现抽象方法,使用单例模式创建ShizuoFactory工厂对象,传入路径参数;最后,子类不需要写父类已经写好的
getBeen
方法。 - 这里使用单例模式可以确保
getBeenFactory()
只有一个shizuoBeenFactory工厂,工厂初始化,然后getBeen()
获取been。
/**
* 继承抽象类
*/
public class ClassPathXmlShizuoContext extends AbstractShizuoContext{
private String configLocation;
// 单例模式
private ShizuoBeenFactory shizuoBeenFactory = null;
public void setConfigLocation(String configLocation) {
this.configLocation = configLocation;
}
public ClassPathXmlShizuoContext(String url) throws Exception {
super();
this.setConfigLocation(url);
}
@Override
public BeenFactory getBeenFactory(){
if (shizuoBeenFactory == null){
synchronized (ClassPathXmlShizuoContext.class){
if (shizuoBeenFactory == null){
try {
shizuoBeenFactory = new ShizuoBeenFactory(configLocation);
} catch (Exception e){
throw new RuntimeException();
}
}
}
}
return shizuoBeenFactory;
}
}
- 测试单例:
@Test
public void test2() throws Exception {
ShizuoContext context = new ClassPathXmlShizuoContext("src\\user.xml");
BeenFactory factory = context.getBeenFactory();
BeenFactory factory2 = context.getBeenFactory();
System.out.println("工厂是否是同一个:"+(factory2 == factory));
}
工厂是否是同一个:true
- 2.BeanFactory:这一部分主要是
getBeen
方法的具体实现。
public interface BeenFactory {
// 获取been
<T> T getBeen(String name, Class<T> requiredType) throws Exception;
}
abstract class AbstractBeenFactory implements BeenFactory{
private Map<String, Object> beenMap;
@Override
public <T> T getBeen(String name, Class<T> requiredType){
T o = null;
if (this.beenMap != null){
o = (T)this.beenMap.get(name);
}
return o;
}
}
- BeanFactory实现类:在
getBeenFactory()
调用之后获取been容器。因为将been保存在了HashMap里面,所以多次调用getBeen
方法产生的实例也是相同的。
/**
* 实现类
*/
class ShizuoBeenFactory extends AbstractBeenFactory{
private Map<String, Object> beenMap;
// 构造函数,输入xml配置文件路径
public ShizuoBeenFactory(String configLocation) throws Exception{
this.beenMap = ShizuoGetBeenMapUtils.getBeenMap(configLocation);
}
@Override
public <T> T getBeen(String name, Class<T> requiredType){
T o = null;
if (this.beenMap != null){
o = (T)this.beenMap.get(name);
}
return o;
}
}
- 测试一下
/**
* 模拟测试
*/
@Slf4j
public class BeenTest {
@Test
public void test () throws Exception{
ShizuoContext context = new ClassPathXmlShizuoContext("src\\user.xml");
log.info("从Map中获取been");
User user = (User) context.getBeen("user1", User.class);
User user2 = (User) context.getBeen("user1", User.class);
System.out.println("获取been:"+user.toString());
System.out.println(user == user2);//true
}
}
- 结果
[INFO ] 2021-05-07 23:01:55,576 method:indi.huishi.bean.BeenTest.test(BeenTest.java:30)
从Map中获取been
[INFO ] 2021-05-07 23:01:55,576 method:indi.huishi.bean.ShizuoGetBeenMapUtils.getBeenMap(ShizuoGetBeenMapUtils.java:28)
创建一个SaxReader输入流,去读取 xml配置文件,生成Document对象
[INFO ] 2021-05-07 23:01:55,654 method:indi.huishi.bean.ShizuoGetBeenMapUtils.getBeenMap(ShizuoGetBeenMapUtils.java:33)
利用反射机制调用无参构造
[INFO ] 2021-05-07 23:01:55,654 method:indi.huishi.bean.ShizuoGetBeenMapUtils.getBeenMap(ShizuoGetBeenMapUtils.java:61)
调用setId方法,设置属性的值:33
[INFO ] 2021-05-07 23:01:55,654 method:indi.huishi.bean.ShizuoGetBeenMapUtils.getBeenMap(ShizuoGetBeenMapUtils.java:61)
调用setName方法,设置属性的值:Huishi
[INFO ] 2021-05-07 23:01:55,670 method:indi.huishi.bean.ShizuoGetBeenMapUtils.getBeenMap(ShizuoGetBeenMapUtils.java:61)
调用setPassword方法,设置属性的值:123456
[INFO ] 2021-05-07 23:01:55,670 method:indi.huishi.bean.ShizuoGetBeenMapUtils.getBeenMap(ShizuoGetBeenMapUtils.java:68)
为对象实例设置name: user1
[INFO ] 2021-05-07 23:01:55,670 method:indi.huishi.bean.ShizuoGetBeenMapUtils.getBeenMap(ShizuoGetBeenMapUtils.java:73)
保存到Been Map
获取been:User(id=33, name=Huishi, password=123456)
true