应用集成——数据库集成与简单实现IoC容器

应用集成——数据库集成与简单实现IoC容器

项目地址:郝凯VioletEverGarden/ioc应用集成
问题描述:

​ 设分别存在两个数据库,每个数据库都有一个学生表,表名与其中的属性名都不相同。我们需要编写一个系统,在不使用市面上框架的情况下,对两个数据库进行集成,取两个数据库中的数据集合起来呈现给用户。

目前项目实现功能:

  • 不限制数据库数量,只要添加一些数据源信息即可对数据库进行整合。实现可拓展。
  • 可以手动编写属性的xml映射,也可以调用test方法自动获取数据创建xml映射。便于用户使用
  • 单例容器的实现:
    • 实现基本的容器功能
    • Bean注解实现
    • Component注解实现
    • Autowire注解实现

1.数据库集成思路

​ 首先是对数据库的区分,这里自己定义了不同数据库的特征。假设是不同学校的数据库,因为学生Id含有区分学校的学校编号,所以我以这个编号作为区分。在数据库中以id的前三位作为学校编号便于处理。

​ 例如

String id = "111345346"; //其中111表示学校编号

​ 其次多数据库的连接问题。既然数据源是可以变动的,那么就把数据源抽象出来,每个数据源都是一个对象:

public class Table {
        String dataBase; //连接数据库url 需要包含username与password
    String tableName; //表名称
    String xmlLocation; //属性映射xml文件路径
    List<String> list; //数据库属性名称
    
    public Table(String dataBase,String tableName,String xmlLocation){
        this.dataBase = dataBase;
        this.tableName = tableName;
        this.xmlLocation = xmlLocation;
        this.list = ParsingXML.parsing(xmlLocation);//这个方法是读取属性映射文件,将结果保存在一个list当中
    }

    public String getDataBase() {
        return dataBase;
    }

    public void setDataBase(String dataBase) {
        this.dataBase = dataBase;
    }

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public String getXmlLocation() {
        return xmlLocation;
    }

    public void setXmlLocation(String xmlLocation) {
        this.xmlLocation = xmlLocation;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }
}

下面是解析Xml文件与生成xml文件的方法:

import com.hk.constant.Configure;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

public class ParsingXML {
    public static void main(String[] args) throws IOException {
        ParsingXML.parsing(Configure.tableMap.get("111").getXmlLocation());

    }

    public static List<String> parsing(String xmlLocation)  {
        ArrayList<String> properties = new ArrayList<>();
        try {
            //读取xml文件内容
            FileInputStream is = new FileInputStream(xmlLocation);
            InputStreamReader streamReader = new InputStreamReader(is);
            BufferedReader reader = new BufferedReader(streamReader);
            String line;
            StringBuilder stringBuilder = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line);
            }
            reader.close();
            String xml = new String(stringBuilder);


            //创建DocumentBuilderFactory实例,指定DocumentBuilder
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dbf.newDocumentBuilder();
            //从xml字符串中读取数据
            InputStream inputStream = new ByteArrayInputStream(xml.getBytes());
            Document doc = builder.parse(inputStream);
            //取的根元素
            Element root = (Element) doc.getDocumentElement();
            //得到根元素所有子元素的集合
            NodeList nodelist = root.getChildNodes();
            //得到list集合的size()
            int size = nodelist.getLength();
            //新建参数列表
            /*
            我们定义参数列表中的数据必须按照StudentInfo的的顺序填写
             */
            for(int i = 0;i < size;i++){
                Node node = nodelist.item(i);
                if(node.getNodeName()!="#text"){
                    properties.add(node.getTextContent());
                    System.out.println(nodelist.item(i).getNodeName()+"==="+node.getTextContent());
                }

            }

        } catch (ParserConfigurationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SAXException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return properties;
    }


    public static void createXml(Connection conn, String path) {
        try {
            // 创建解析器工厂
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = factory.newDocumentBuilder();
            Document document = db.newDocument();
            // 不显示standalone="no"
            document.setXmlStandalone(true);
            /*根节点*/
            Element mapper = document.createElement("mapper");
            // 向mapper根节点中添加子节点property
            List<Element> propertys=new ArrayList<>();
           
            for(int i = 0 ; i< 5;i++){
                propertys.add(document.createElement("property"));
            }
            PreparedStatement preparedStatement = conn.prepareStatement("select * from student");
            //结果集元数据
            ResultSetMetaData resultSetMetaData = preparedStatement.getMetaData();
            //表列数
            int size = resultSetMetaData.getColumnCount();
            List<String> columnNames = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                columnNames.add(resultSetMetaData.getColumnName(i + 1));
            }
            for (int i = 0; i < size; i++) {
                propertys.get(i).setTextContent(columnNames.get(i));
                mapper.appendChild(propertys.get(i));
            }
            document.appendChild(mapper);
            // 创建TransformerFactory对象
            TransformerFactory tff = TransformerFactory.newInstance();
            // 创建 Transformer对象
            Transformer tf = tff.newTransformer();
            // 输出内容是否使用换行
            tf.setOutputProperty(OutputKeys.INDENT, "yes");
            // 创建xml文件并写入内容
            tf.transform(new DOMSource(document), new StreamResult(new File(path)));
            System.out.println("生成xml成功");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("生成xml失败");
        }
    }
}

​ 现在将数据源都看成呢个一个一个对象,对象存放在那里呢?因为每次连接数据库都要使用到url,所以把他们单独放在一个配置类中,需要修改配置源也在配置类中修改。

/**
 * @author haokai
 * 常量定义
 * 配置类
 */
public class Configure {
    public static HashMap<String, Table> tableMap;

    static {
        tableMap = new HashMap<>();
        Table table111 = new Table("jdbc:mysql://101.200.215.126:3306/t_user_1?user=root&password=123456&useUnicode=true&characterEncoding=UTF8&useSSL=false",
                "student","E:\\大三上\\集成\\mvc-test\\MVC-test\\WebContent\\mapper\\StudentMapper_1.xml"
        );
        tableMap.put("111",table111);

        Table table222 = new Table("jdbc:mysql://101.200.215.126:3306/t_user_2?user=root&password=123456&useUnicode=true&characterEncoding=UTF8&useSSL=false",
                "student_1","E:\\大三上\\集成\\mvc-test\\MVC-test\\WebContent\\mapper\\StudentMapper_2.xml"
        );
        tableMap.put("222",table222);
    }

    public static String characterEncoding = "UTF-8";
}

​ 现在可以通过Congifure.tableMap直接获取数据源配置信息,我们再将项目的业务逻辑分层:

  • connetor ,调用DriverManager.getConnection(url)方法,获取与数据库连接的Connection对象
  • dao, 调用connector获取的connection对象,进行createStatement()与excute()来实现增删改查
  • DoServlet(Service),调用dao层,完成业务的逻辑实现。
package com.hk.connecter;

import bean.Table;
import com.hk.constant.Configure;
import com.hk.container.sterotype.Component;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
@Component
public class Connector {

    public Table getTable(String id){
        return Configure.tableMap.get(id.substring(0,3));
    }

    public Connection connect(String s) throws ClassNotFoundException, SQLException {
        Connection conn=null;
        Class.forName("com.mysql.jdbc.Driver");
        String url = getTable(s).getDataBase();
        conn= DriverManager.getConnection(url);
        System.out.println("==== connected ===");
        return conn;
    }

    //关闭数据库资源
    public void close(Statement stat, Connection conn) throws SQLException{
        if(stat!=null){
            stat.close();
        }
        if(conn!=null){
            conn.close();
        }
    }
}
package com.hk.dao;

import bean.StudentInfo;
import bean.Page;
import com.hk.connecter.Connector;
import com.hk.constant.Configure;
import bean.Table;
import com.hk.container.sterotype.Autowired;
import com.hk.container.sterotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Component
public class StudentMapper {

    @Autowired
    Connector connector;

    //插入方法
    public void insert(HttpServletRequest request, HttpServletResponse response) throws ClassNotFoundException, SQLException {
        Connection conn=null;
        Statement stat=null;
        String id=request.getParameter("id");

        String name=request.getParameter("name");
        String age=request.getParameter("age");
        String gender=request.getParameter("gender");
        String major=request.getParameter("major");
        conn=connector.connect(id);
        stat=conn.createStatement();
        //获取参数列表
        List<String> list = connector.getTable(id).getList();
        System.out.println("start");
        stat.execute("insert into student("+list.get(0)+","+list.get(1)+","+list.get(2)+","+list.get(3)+","+list.get(4)+") values("+id+",'"+name+"',"+age+",'"+gender+"','"+major+"')");
        System.out.println("end");
        connector.close(stat,conn);
        System.out.println("close");
    }
    //查询方法
    public ArrayList<StudentInfo> select(String id, String name) throws ClassNotFoundException, SQLException{
        Connection conn=null;
        Statement stat=null;
        ResultSet rs=null;
        ArrayList<StudentInfo> result = new ArrayList<>();
        System.out.println("id===>"+ (id==null?"null":id));
        if("".equals(id)||id==null){
            for(Map.Entry<String, Table> entry: Configure.tableMap.entrySet()){
                String key = entry.getKey();
                conn=connector.connect(key);
                stat=conn.createStatement();
                //获取table
                Table table = Configure.tableMap.get(key);
                if("".equals(name)){
                    rs=stat.executeQuery("select * from "+table.getTableName());
                }else {
                    rs=stat.executeQuery("select * from "+table.getTableName()+ "where "+table.getList().get(1)+"='"+name+"'");
                }
                getResult(rs,result,table.getList());
                connector.close(stat,conn);
            }
            return result;
        }else {
            conn=connector.connect(id);
            stat=conn.createStatement();
            //获取table
            Table table = connector.getTable(id);
            if("".equals(name)|| name == null){
                rs=stat.executeQuery("select * from "+table.getTableName()+" where "+table.getList().get(0)+"="+id);
            }else {
                rs=stat.executeQuery("select * from "+table.getTableName()+" where "+table.getList().get(0)+"="+id+" and "+table.getList().get(1)+"='"+name+"'");
            }
            getResult(rs,result,table.getList());
            connector.close(stat,conn);
            return result;
        }

    }

    public ArrayList<StudentInfo> getResult(ResultSet rs,ArrayList<StudentInfo> result,List<String> list)throws ClassNotFoundException, SQLException{
        list.forEach(System.out::println);
        while(rs.next())
        {
            StudentInfo st=new StudentInfo();
            st.setId(rs.getInt(list.get(0)));
            st.setName(rs.getString(list.get(1)));
            st.setAge(rs.getInt(list.get(2)));
            st.setGender(rs.getString(list.get(3)));
            st.setMajor(rs.getString(list.get(4)));
            result.add(st);
        }
        if(rs!=null){
            rs.close();
        }
        return result;
    }


    public void delete(HttpServletRequest request, HttpServletResponse response) throws ClassNotFoundException, SQLException, ServletException, IOException {
        Connection conn=null;
        Statement stat=null;
        String id2=request.getParameter("id");
        conn=connector.connect(id2);
        Table table = connector.getTable(id2);
        stat=conn.createStatement();
        stat.execute("delete from "+table.getTableName()+" where "+table.getList().get(0)+"="+id2+"");
        request.getRequestDispatcher("delete.jsp").forward(request, response);
    }
    //信息修改方法
    public void update1(HttpServletRequest request, HttpServletResponse response) throws ClassNotFoundException, SQLException, ServletException, IOException{
        String id4=request.getParameter("id");
        request.setAttribute("result", select(id4,""));
        request.getRequestDispatcher("update1.jsp").forward(request, response);
    }
    public void update(HttpServletRequest request, HttpServletResponse response) throws ClassNotFoundException, SQLException, ServletException, IOException{
        Connection conn=null;
        Statement stat=null;
        String id3=request.getParameter("id");
        String name3=request.getParameter("name");
        String age3=request.getParameter("age");
        String gender3=request.getParameter("gender");
        String major3=request.getParameter("major");
        conn=connector.connect(id3);
        stat=conn.createStatement();
        Table table = connector.getTable(id3);
        stat.execute("update "+table.getTableName()+" set "+table.getList().get(0)+"="+id3+","
                +table.getList().get(1)+"='"+name3+"',"
                +table.getList().get(2)+"="+age3+","
                +table.getList().get(3)+"='"+gender3+"'," +
                ""+table.getList().get(4)+"='"+major3+"' where "+table.getList().get(0)+"="+id3+"");
        request.setAttribute("result", select(id3,""));
        request.getRequestDispatcher("update.jsp").forward(request, response);

    }


    //条件查询跳转
    public void dispatch(HttpServletRequest request, HttpServletResponse response) throws ClassNotFoundException, SQLException, ServletException, IOException{
        String id5=request.getParameter("id");
        String name5=request.getParameter("name");
        if(select(id5,name5).isEmpty()){
            request.getRequestDispatcher("selectnothing.jsp").forward(request, response);
        }
        else{
            request.setAttribute("result", select(id5,name5));
            request.getRequestDispatcher("idnameselect.jsp").forward(request, response);
        }
    }
    //设置分页相关参数方法
    public Page setpage(HttpServletRequest request, HttpServletResponse response) throws ClassNotFoundException, SQLException{
        String crd=request.getParameter("currentRecord");
        //String id=request.getParameter("id");
        //  String name=request.getParameter("name");
        ArrayList<StudentInfo> result=select("","");
        Page pager=new Page();
        pager.setTotalRecord(result.size());
        pager.setTotalPage(result.size(),pager.getPageSize());
        if(crd!=null)
        {
            int currentRecord=Integer.parseInt(crd);
            pager.setCurrentRecord(currentRecord);
            pager.setCurrentPage(currentRecord,pager.getPageSize());
        }
        return pager;
    }
    //获得分页显示的子集
    public void difpage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, ClassNotFoundException, SQLException{
        // String id=request.getParameter("id");
        //    String name=request.getParameter("name");
        ArrayList<StudentInfo> result=select("","");
        Page pager=new Page();
        pager=setpage(request,response);
        List<StudentInfo> subResult=null;
        int currentRecord=pager.getCurrentRecord();
        if(currentRecord==0){
            if(pager.getTotalRecord()<8){
                subResult=(List<StudentInfo>) result.subList(0,pager.getTotalRecord());
            }
            else{
                subResult=(List<StudentInfo>) result.subList(0,pager.getPageSize());
            }
        }
        else if(pager.getCurrentRecord()+pager.getPageSize()<result.size())
        {
            subResult=(List<StudentInfo>) result.subList(pager.getCurrentRecord(),pager.getCurrentRecord()+pager.getPageSize());
        }
        else
        {
            subResult=(List<StudentInfo>) result.subList(pager.getCurrentRecord(),result.size());
        }
        request.setAttribute("pager", pager);
        request.setAttribute("subResult", subResult);
        request.getRequestDispatcher("layout.jsp").forward(request, response);
    }
}
package com.hk.dbservlet;

import com.hk.container.sterotype.Autowired;
import com.hk.container.sterotype.Component;
import com.hk.dao.StudentMapper;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.SQLException;
@Component
public class DoServlet {

    @Autowired
    StudentMapper studentMapper;

    public void doPost(HttpServletRequest request, HttpServletResponse response)

            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        String methodName=request.getParameter("methodName");
        int method=Integer.parseInt(methodName);
        try {
            switch(method)
            {
                case 0:
                    studentMapper.insert(request,response);
                case 1:
                    studentMapper.difpage(request,response);
                    break;
                case 2:
                    studentMapper.delete(request,response);
                    break;
                case 3:
                    studentMapper.update(request,response);
                    break;
                case 4:
                    studentMapper.update1(request,response);
                    break;
                case 5:
                    studentMapper.dispatch(request,response);
                    break;
            }
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

可以加以改进的地方:

  • 对Sql语句的处理可以抽出来,将生成sql语句分为三个部分:
    • 按业务分为增删改查四个模板,按关键字取出模板
    • 对模板进行裁剪与增加,为满足业务需求
    • 表的属性填充:从table对象中的list获取属性名填充至sql语句中
    • Sql语句的生成应该可以使用工厂模式
  • 数据源的存放可以使用yaml的格式存放,这样更为合理,也更方便编写数据源,要新增一个解析yaml文件的功能才行。

2.IoC容器实现部分

spring源码学习:

​ spring的ioc容器源码结构非常复杂,很多继承眼花缭乱,我自己是先抛开复杂的继承关系(虽然这个继承十分重要),便于理解IoC容器的执行过程。

Bean Definition:

​ 顾名思义,BeanDefinition的保存Bean的定义信息的,一般而言我们在spring的项目中以xml配置给出,在springboot中以注解形式表现。

下图是Bean的信息在Spring中的大致经历过程:

在这里插入图片描述

ResourceLoader

​ 定义bean的信息最初给的是资源文件路径,由ResourceLoader将文件路径转化成Resource对象,该对象包装了流的获取。其中包含了流的编码检测过程。

DocumentLoader

通过Resource获取的流,获取Document对象,包含了模式检验过程

DefaultBeanDefinitionDocumentReader

这个类名字很长,作用也和他的名字一样,是spring中读取Document对象的默认实现类,读取Document中的字节流,为后面解析Element准备上下文。

BeanDefinitionParseDelegate

这个类的源码很长,实际上就是解析Element对象中的每一个属性值,填充到BeanDefinition对象。

​ 以上是Bean定义信息在spring中的转化过程,这个过程也是 loadBeanDefinition()这个方法的过程,在AbstractBeanFactory中保存在一个Map中,以对象名作为键,beanDefinition作为值,所以我们的容器也可以走这个过程。

​ 将Bean定义信息从xml文件转化到程序中后,执行的就是Bean的注册与加载。

Bean加载

FactoryBean 与 BeanFactory

这里有必要区分一下这两个看起来很相似的类

​ FactoryBean表示的是还是一个Bean、一个组件,尽管他起着工厂的作用。在这里和BeanFactory的定义有不小的区别,在是spring的容器中获取FactoryBean不是获取实例本身,而是获取实例的getObject方法返回的对象。

加载步骤:

  1. 转换对应 bean Name ,这里传入的参数可能是别名,也可能是 FactoryBean ,所以需要进行一系列的解析。
  2. 尝试从缓存中加载单例,如果加载不成功则再次尝试从in etonFactories 中加载。
  3. bean 的实例化,依赖于ClassLoader.loadClass(beanClassName).newInstance().
  4. 依赖检查,防止循环依赖。
  5. 属性注入,递归加载bean依赖的其他bean。
    在这里插入图片描述

单例容器的实现

大致明白了spring容器的实现过程,我们自己定义容器的话需要解决的问题有

  1. Bean定义的数据结构
  2. Bean定义的信息如何读取。注解实现又要如何读取。
  3. 如何解析Bean定义
  4. Bean的实例如何创建
  5. Bean的依赖注入
  6. Bean的获取方式

所以我们以上述几个方面来实现我们自己的容器。

首先是Bean定义的数据结构:

public class BeanDefinition {
    private String id;
    private String beanClassName;
    public BeanDefinition(String id, String beanClassName) {
        this.id = id;
        this.beanClassName = beanClassName;
    }
    public String getBeanClassName() {return this.beanClassName;}
    public String getId() {return id;}
}

这里我们直接使用注解实现,所以Bean的依赖相关不在Definition中存储,只需要记录bean名称和bean类型即可。

其次是Bean信息的获取,既然我们要使用注解实现要解决如何扫描包以及扫描包的路径存放。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--<context:annotation-config/>-->
    <context:component-scan
            base-package="com.hk">
    </context:component-scan>

</beans>

​ 我这里将扫描包的路径存放在bean.xml文件中,并由SAXReader去将字节流转化成文档格式。然后解析其中的context标签。

​ 至于扫描包,我们在获取到base-package的路径后,对该路径进行递归扫描,找到所有的class文件。

​ 然后一个一个将文件委派给ClassVIsitor去解析class文件。解析的文件将会包装成AnnotationMetadata,其中存放了类注解和所有属性注解,我们依赖于AnnotationMetadata格式对Bean进行依赖注入。

public class AnnotationMetadata extends ClassVisitor {
    // 存放类注解,有序的链表
    private final Set<String> annotationSet = new LinkedHashSet<>(4);
    // 存放当前类所有注解,类注解+属性注解
    private final Map<String, LinkedHashMap> attributeMap = new LinkedHashMap(4);

    private String className;

    public AnnotationMetadata() {
        super(SpringAsmInfo.ASM_VERSION);
    }

    @Override
    public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {

        String className = Type.getType(desc).getClassName();
        this.annotationSet.add(className);
        return new AnnotationAttributesReadingVisitor(className, this.attributeMap);
    }

    public void visit(int version, int access, String name, String signature, String supername, String[] interfaces) {
        this.className = ClassUtils.convertResourcePathToClassName(name);
    }

    public String getClassName() {
        return this.className;
    }

    public boolean hasAnnotation(String annotationType) {
        return this.annotationSet.contains(annotationType);
    }

    public LinkedHashMap getAnnotationAttributes(String annotationType) {
        return this.attributeMap.get(annotationType);
    }


}
final class AnnotationAttributesReadingVisitor extends AnnotationVisitor {

    private final String annotationType;

    private final Map<String, LinkedHashMap> attributesMap;

    LinkedHashMap<String, Object> attributes = new LinkedHashMap<String, Object>();

    public AnnotationAttributesReadingVisitor(
            String annotationType, Map<String, LinkedHashMap> attributesMap) {
        super(SpringAsmInfo.ASM_VERSION);

        this.annotationType = annotationType;
        this.attributesMap = attributesMap;
    }

    @Override
    public final void visitEnd() {
        this.attributesMap.put(this.annotationType, this.attributes);
    }

    @Override
    public void visit(String attributeName, Object attributeValue) {
        this.attributes.put(attributeName, attributeValue);
    }
}

​ 在我们将所有的Bean都包装成AnnotationMetadata之后,就要开始注册与创建Bean。

(我们的容器是单例容器,所以比spring容器的处理要简单的多,同时我也没有做防止循环依赖的提前暴露准备工作)

注册过程:我们遍历beanDefinitionMap, 获取每一个beanDefinition

  1. 从BeanDefinition中获取BeanName以及ClassPath
  2. 检查容器中的map是否已经存在该BeanName的对象,若已存在则遍历下一个,如果不存在先创建bean
  3. 创建bean调用 Thread.currentThread().getContextClassLoader().loadClass(beanClassName).newInstance ()实现
  4. 创建完成后从该bean的AnnotationMetadata中获取带有@Autowire注解的属性,对该属性进行递归创建注册(返回第3步),属性bean创建完成后直接赋值给属性。
  5. 所有的Bean创建完成并且依赖注入完成后容器的工作就结束了。
import bean.BeanDefinition;
import com.hk.container.annotation.AnnotationMetadata;
import com.hk.container.sterotype.Component;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.asm.ClassReader;
import org.springframework.util.ClassUtils;

import java.beans.Introspector;
import java.io.*;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class BeanDefinitionReader {
    private Map<String, BeanDefinition> beanDefinitionMap =new ConcurrentHashMap<>();

    public void loadBeanDefinitions(String configFile) {
        InputStream is = null;
        try {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();

            is = cl.getResourceAsStream(configFile); // 根据 configFile 获取 petstore-v1.xml 文件的字节流

            SAXReader reader = new SAXReader();
            Document doc = reader.read(is); // 将字节流转成文档格式

            Element root = doc.getRootElement(); // <beans>
            Iterator iter = root.elementIterator();
            while (iter.hasNext()) {
                Element ele = (Element) iter.next();
                parseComponentElement(ele);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 根据 component-scan 指定路径,找到路径下所有包含 @Component 注解的 Class 文件,作为 beanDefinition
     *
     * @param ele
     */
    private void parseComponentElement(Element ele) throws IOException {

        // 获取 component-scan 的路径
        String basePackagesStr = ele.attributeValue("base-package");
        String[] basePackages = basePackagesStr.split(",");
        for (String basePackage : basePackages) {

            File[] files = getFiles(basePackage);
            for (File file : files) {
                AnnotationMetadata annotationMetadata = getAnnotationMetadata(file);
                // 通过 hasAnnotation 判断是否有 @Component 注解
                if (annotationMetadata.hasAnnotation(Component.class.getName())) {
                    String beanId = (String) annotationMetadata.getAnnotationAttributes(Component.class.getName()).get("value");

                    String beanClassName = annotationMetadata.getClassName();
                    if (beanId == null) {
                        // 通过 class 路径获取类名,并将首字母小写
                        beanId = Introspector.decapitalize(ClassUtils.getShortName(beanClassName));
                    }

                    BeanDefinition bd = new BeanDefinition(beanId, beanClassName);
                    this.beanDefinitionMap.put(beanId, bd);
                }
            }
        }
    }



    /**
     * 利用字节码技术,将注解元数据存放在 AnnotationMetadata 中,一个 file 对应一个 AnnotationMetadata
     * <p>
     * 待优化:去除 AnnotationMetadata,直接获取注解
     *
     * @param file
     * @return
     * @throws IOException
     */
    public AnnotationMetadata getAnnotationMetadata(File file) throws IOException {

        // file 是路径,is 相当于字节码文件流
        InputStream is = new BufferedInputStream(new FileInputStream(file));
        // 此时使用了 Spring 框架的 ClassReader,待优化为使用原生类
        ClassReader classReader;

        try {
            // 通过文件流设置 classReader
            classReader = new ClassReader(is);
        } finally {
            is.close();
        }

        AnnotationMetadata visitor = new AnnotationMetadata();
        // classReader 利用字节码技术,从文件流中获取元数据,设置到 AnnotationMetadata 中
        classReader.accept(visitor, ClassReader.SKIP_DEBUG);

        return visitor;
    }

    /**
     * 获取指定路径下的所有 Class 文件
     *
     * @param basePackage
     * @return
     */
    private File[] getFiles(String basePackage) {
        String location = ClassUtils.convertClassNameToResourcePath(basePackage);
        URL url = Thread.currentThread().getContextClassLoader().getResource(location);
        File rootDir = new File(url.getFile());
        Set<File> matchingFiles = new LinkedHashSet<>(8);
        doRetrieveMatchingFiles(rootDir, matchingFiles);
        return matchingFiles.toArray(new File[0]);
    }

    /**
     * 通过递归获取文件夹下的文件
     *
     * @param dir
     * @param result
     */
    private void doRetrieveMatchingFiles(File dir, Set<File> result) {

        File[] dirContents = dir.listFiles();
        if (dirContents == null) {
            return;
        }
        for (File content : dirContents) {
            if (content.isDirectory()) {
                if (!content.canRead()) {
                } else {
                    doRetrieveMatchingFiles(content, result);
                }
            } else {
                result.add(content);
            }
        }
    }

    public BeanDefinition getBeanDefinition(String beanID) {

        return this.beanDefinitionMap.get(beanID);
    }


    public Map<String, BeanDefinition> getBeanDefinitionMap(){
        return this.beanDefinitionMap;
    }
}

import bean.BeanDefinition;
import com.hk.container.context.ClassPathXmlApplicationContext;
import com.hk.container.reader.BeanDefinitionReader;
import com.hk.container.sterotype.Autowired;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;


public class BeanRegister {
    /**
     * ApplicationContext 特点,第一次加载即注入所有 bean 到容器
     */
    private BeanDefinitionReader reader;

    private ClassPathXmlApplicationContext classPathXmlApplicationContext;

    public BeanRegister(BeanDefinitionReader reader,ClassPathXmlApplicationContext classPathXmlApplicationContext) {
        this.reader = reader;
        this.classPathXmlApplicationContext = classPathXmlApplicationContext;
    }

    public void prepareBeanRegister() {
        for (String beanId : reader.getBeanDefinitionMap().keySet()) {
            BeanDefinition bd = this.reader.getBeanDefinition(beanId);
            // 单例模式,一个类对应一个 Bean,不是通过 id。常规单例模式是多次调用方法,只生成一个实例。此处是只会调用依次生成实例方法。
            Object bean = this.classPathXmlApplicationContext.getSingleton(beanId);
            if (bean == null) {
                bean = createBean(bd);
                this.registerSingleton(beanId, bean);
            }
        }
    }

    public void registerSingleton(String beanName, Object singletonObject) {
        Object oldObject = this.classPathXmlApplicationContext.getSingletonObjects().get(beanName);
        if (oldObject != null) {
            System.out.println("error," + oldObject + " had already registered");
        }
        this.classPathXmlApplicationContext.getSingletonObjects().put(beanName, singletonObject);
    }

    public Object createBean(BeanDefinition bd) {
        // 创建实例
        Object bean = instantiateBean(bd);
        // 填充属性(依赖注入)
        populateBean(bean);

        return bean;
    }


    private Object instantiateBean(BeanDefinition bd) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        String beanClassName = bd.getBeanClassName();
        try {
            Class<?> clz = cl.loadClass(beanClassName);
            System.out.println(beanClassName);
            return clz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 利用反射,将字段与对象关联
     * 没有 setter 方法,利用 Field 的field.set();有 setter 方法,利用 Method 的 Method.invoke()
     *
     * @param bean
     */
    private void populateBean(Object bean) {
        // 通过反射得到当前类所有的字段信息(Field 对象)。getFields() 获取公有字段
        Field[] fields = bean.getClass().getDeclaredFields();
        try {
            for (Field field : fields) {
//                Annotation ann = findAutowiredAnnotation(field);
                // 判断字段是否有 @Autowired 注解
                Annotation ann = field.getAnnotation(Autowired.class);
                // 根据是否有 Autowired 注解来决定是否注入
                if (ann != null) {
                    // 实际上,这里不是简单的通过 name 获取依赖,而是根据类型获取 getAutowiredBean(bean)
                    Object value = classPathXmlApplicationContext.getBean(field.getName());
                    if (value != null) {
                        // 设置字段可连接,相当于将非 public(private、default、package) 更改为 public
                        field.setAccessible(true) ;
                        // 通过反射设置字段的值
                        field.set(bean, value);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值