————《java高级程序设计》深入反射的实例

一、前提

当我们使用对象B时,一般的情况是使用new B()的方式,但是在我们一个大的项目里面,有复杂的业务逻辑,这时B已经无法修改了。那么如何解决这个问题呢?「Spring的核心概念控制反转」就可以解决这个问题。

那么「控制反转」是什么呢?

对于我们初学者来说「控制反转」「依赖注入」是很生涩的词语,但其实就是动态的对象管理和对象依赖,本来我们的对象都是new出来的,而我们如果使用Spring 则把对象交给「IOC容器」来管理,这样我们对这个对象的控制权就转交给了这个「IOC容器」。

那么「IOC容器」又是什么呢?

IOC容器」就相当一个「工厂」(接口BeanFactory ,即使IOC的具体表现),我们把对象交给这个「工厂」来管理包括Spring 中的对象注册(有很多种方式如配置文件、标注、也可以根据自己的需求扩展注册方式),与取出对象。

而「依赖注入」即是「控制反转」的实现方式,对象不需要自行创建或管理它们的依赖关系,依赖关系将自动注入到它们的对象中去,如:A需要B的功能,但是又不能直接实例化,那么它就只能依赖Spring的自动注入,就是把用户需要的实例从注入到用户实体中。

使用IOC的好处:

  • 统一管理对象,便于修改

  • 耦合性降低(调用方无需自己组装,也无需关心对象的实现,直接从「IOC容器」取)

  • 使我们的代码易于维护

二、需求分析
  1. 含有一个IOC的简单Spring。含有对象注册,对象管理,外部获取对象功能
  2. 对象注册要求方式多样,可灵活扩展。
三、项目设计
  1. 使用BeanInfo来描述信息,包含对象的标识
  2. 对于IOC容器设定一个顶层接口BeanFactory,该接口中定义通过对象名称获取对象的方法 getBean(String name)AbstractBeanFactory实现该接口,在该类中实现解析生成目标对象,以及获取目标对象方法,并在该类中添加注册器接口,以便能从注册器中读取注册的对象。
  3. 对于注册器来说,提供一个顶层的接口 SourceReader ,并在其中添加加载用户注册的对象的方法loadbeans(String path),具体的实现根据不同的方式而定。本项目中需有一个默认的测试实现XMLSourceReader ,该对象负责模拟读取用户注册的对象,并把这些对象封装成BeanInfo放入一个Map中。
  4. 设定一个上下文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)结果

在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐