应用集成——数据库集成与简单实现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方法返回的对象。
加载步骤:
- 转换对应 bean Name ,这里传入的参数可能是别名,也可能是 FactoryBean ,所以需要进行一系列的解析。
- 尝试从缓存中加载单例,如果加载不成功则再次尝试从in etonFactories 中加载。
- bean 的实例化,依赖于ClassLoader.loadClass(beanClassName).newInstance().
- 依赖检查,防止循环依赖。
- 属性注入,递归加载bean依赖的其他bean。
单例容器的实现
大致明白了spring容器的实现过程,我们自己定义容器的话需要解决的问题有
- Bean定义的数据结构
- Bean定义的信息如何读取。注解实现又要如何读取。
- 如何解析Bean定义
- Bean的实例如何创建
- Bean的依赖注入
- 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
- 从BeanDefinition中获取BeanName以及ClassPath
- 检查容器中的map是否已经存在该BeanName的对象,若已存在则遍历下一个,如果不存在先创建bean
- 创建bean调用 Thread.currentThread().getContextClassLoader().loadClass(beanClassName).newInstance ()实现
- 创建完成后从该bean的AnnotationMetadata中获取带有@Autowire注解的属性,对该属性进行递归创建注册(返回第3步),属性bean创建完成后直接赋值给属性。
- 所有的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();
}
}
}