Dom4j+Java反射+工厂模式 模拟Spring实现从xml配置文件获取对象实例

  • 刚刚开始学习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进行遍历。

  • 以上键值对可以通过getNamegetText方法得到。
  • 所以可以写一个简单的代码遍历它们:
/**
 * @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
---------------------------------

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,思路大概如下:需要写ApplicationContextBeanFactory两部分内容,前者使用后者调用类似于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
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值