手写spring+springmvc+mybatis框架篇【springIOC容器】
作者:小亮,原创投稿
个人主页:https://blog.csdn.net/qq_27631217
知音专栏
启动IOC容器为initBean方法,下面贴一下这两个类的关系图。
首先是applicationContext
其次是InitBean
XmlApplicationContext :为解析xml文件的类,在spring源码中Resouce接口是用来解析多种文件格式的xml文件的接口,可能参数时inputStream,也可能是byteArray等,但是我们这里比较简单,直接用new File()传递xml文件。
将读取到的对象用如下对象来保存
Map<String, GenericBeanDefinition> beanDefinitionXmlMap =
new ConcurrentHashMap<>(256);
spring也是这么做的。这个也就是我们说的容器。这里介绍一下我定义的这个beanDefinitionXmlMap 对象,beanDefinitionXmlMap 中的key为我们xml文件中的id,value为GenericBeanDefinition 对象。
每一个GenericBeanDefinition 对象其实就代表一个bean,在GenericBeanDefinition 对象中,className就是对应的实体类的class的名字,而一个ChildBeanDefinition 对象就代表一个子元素(一个property:属性注入 或者一个constructor-arg元素:构造器注入)
package spring.factory;
import lombok.Data;
import java.util.List;
/**
* Created by Xiao Liang on 2018/6/29.
* 用来存放xml中注入的bean
*/
public class GenericBeanDefinition {
/**
* className和xml中的class对应
*/
private String className;
/**
* 这是bean下面的属性集合
*/
private List<ChildBeanDefinition> childBeanDefinitionList;
}
package spring.factory;
import lombok.Data;
/**
* Created by Xiao Liang on 2018/6/27.
*/
public class ChildBeanDefinition {
private String childrenType;// 这个是property或者constructor-arg类型
private String attributeOne;//这个是第一个值
private String attributeTwo;//这个是第二个值
}
我定义的IOCRULES是用枚举来表示的,下面贴一下我定义的注入规则。
package spring.xmlRules;
import lombok.Getter;
/**
* @Author xiao liang
* Ioc中xml配置的规则
*/
public enum IocRules {
BEAN_RULE("bean", "id", "class"),
SNAN_RULE("component-scan", "base-package", "null"),
/**
* set注入的规则
*/
SET_INJECT("property", "name", "value"),
/**
* 构造器注入的规则,使用构造器注入的时候必须指定顺序。
*/
CONS_INJECT("constructor-arg", "value", "index");
private String type;
private String name;
private String value;
IocRules(String property, String name, String value) {
this.type = property;
this.name = name;
this.value = value;
}
}
XmlApplicationContext:其实这里最好是只提供接口,然后让子类来实现。但是为了简化,方便起见,直接写在了这里。最关键的是标红的两个方法,一个是getBeanDefinitionMap,一个是getComponentList。
第一个是解析xml文件并且将属性值保存在容器(beanDefinitionXmlMap对象)中,第二个是获取一个链表,这个链表是存在注解扫描的并且排列好实例化顺序后的链表。这个扫描+实例化顺序的核心功能在ScanUtil工具类中。这个类也是难点。
package spring.xml;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import spring.Utils.StringUtils;
import spring.Utils.scan.ScanUtil;
import spring.constants.Constants;
import spring.exception.XmlException;
import spring.factory.ChildBeanDefinition;
import spring.factory.GenericBeanDefinition;
import spring.xmlRules.IocRules;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Xiao Liang on 2018/6/28.
* 封装解析xml的方法,模仿Ioc注入 BeanDefinition。实际注入的是GenericBeanDefinition
*/
4j
public class XmlApplicationContext {
/**
* @Description 将xml中的bean元素注入到容器中的方法
*
* @return 返回值是指定xml中的bean的容器
*/
public Map<String, GenericBeanDefinition> getBeanDefinitionMap(String contextConfigLocation) {
Map<String, GenericBeanDefinition> beanDefinitionXmlMap = new ConcurrentHashMap<>(256);
List<Element> elementsList = getElements(contextConfigLocation);
//遍历每一个bean,注入beanDefinitionMap
for (Element element :
elementsList) {
if (element.getName().equals("bean")){
//声明一个bean的map,用来盛放当前bean子元素的容器
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
List<ChildBeanDefinition> childBeanDefinitionList = new ArrayList<>();
String beanId = element.attributeValue(IocRules.BEAN_RULE.getName());
String beanClass = element.attributeValue(IocRules.BEAN_RULE.getValue());
//保证子元素确实存在
if (!StringUtils.isEmpty(beanId) && !StringUtils.isEmpty(beanClass)) {
//当前bean的className
genericBeanDefinition.setClassName(beanClass);
//当前bean的所有子元素
List<Element> elements = element.elements();
if (elements != null) {
for (Element childrenElement :
elements) {
//如果匹配set注入规则,则注入到容器
if (childrenElement.getName().equals(IocRules.SET_INJECT.getType())) {
ChildBeanDefinition childBeanDefinition = new ChildBeanDefinition();
childBeanDefinition.setChildrenType(IocRules.SET_INJECT.getType());
String name = IocRules.SET_INJECT.getName();
String value = IocRules.SET_INJECT.getValue();
setChildBeanDefinitionByType(childrenElement, childBeanDefinition, name, value, childBeanDefinitionList);
}
//如果匹配构造器注入规则,则注入到容器
else if (childrenElement.getName().equals(IocRules.CONS_INJECT.getType())) {
ChildBeanDefinition childBeanDefinition = new ChildBeanDefinition();
childBeanDefinition.setChildrenType(IocRules.CONS_INJECT.getType());
String name = IocRules.CONS_INJECT.getName();
String value = IocRules.CONS_INJECT.getValue();
setChildBeanDefinitionByType(childrenElement, childBeanDefinition, name, value, childBeanDefinitionList);
}
}
} else {
log.info("{}下面没有子元素", beanId);
}
genericBeanDefinition.setChildBeanDefinitionList(childBeanDefinitionList);
beanDefinitionXmlMap.put(beanId, genericBeanDefinition);
}
}
}
return beanDefinitionXmlMap;
}
/**
* @Description 根据指定的xml,获得注解扫描的bean容器
* @param contextConfigLocation
* @return
*/
public List<String> getComponentList(String contextConfigLocation){
List<String> componentList = new ArrayList<>();
List<Element> elementsList = getElements(contextConfigLocation);
for (Element element :
elementsList) {
if (element.getName().equals(IocRules.SNAN_RULE.getType())) {
String packageName = element.attributeValue(IocRules.SNAN_RULE.getName());
componentList.addAll(resolveComponentList(packageName));
}
}
return componentList;
}
/**
* 根据要扫描的包名,返回有注解扫描的类
* @param packageName
* @return
*/
public List<String> resolveComponentList(String packageName){
if (StringUtils.isEmpty(packageName)){
throw new XmlException("请正确设置"+IocRules.SNAN_RULE.getType()+"的属性");
}
List<String> componentList = new ArrayList<>();
List<String> componentListAfter = ScanUtil.getComponentList(packageName);
componentList.addAll(componentListAfter);
return componentList;
}
/**
* 将每个bean的子元素注入容器
*
* @param element
* @param childBeanDefinition
* @param name
* @param value
* @param childBeanDefinitionList
*/
private void setChildBeanDefinitionByType(Element element, ChildBeanDefinition childBeanDefinition, String name, String value,
List<ChildBeanDefinition> childBeanDefinitionList) {
if (childBeanDefinition != null) {
childBeanDefinition.setAttributeOne(element.attributeValue(name));
childBeanDefinition.setAttributeTwo(element.attributeValue(value));
childBeanDefinitionList.add(childBeanDefinition);
} else {
throw new XmlException("未按照格式配置xml文件或者暂不支持改配置属性");
}
}
/**
* 解析xml的工厂,根据路径名获取根元素下面的所有子元素
* @param contextConfigLocation
* @return
*/
private List<Element> getElements(String contextConfigLocation) {
// 创建saxReader对象
SAXReader reader = new SAXReader();
// 通过read方法读取一个文件 转换成Document对象
Document document = null;
String pathName = Constants.PATH + contextConfigLocation;
try {
document = reader.read(new File(pathName));
} catch (DocumentException e) {
log.error("文件没有找到,{}", pathName);
}
//获取根节点元素
Element node = document.getRootElement();
//获取所有的bean
List<Element> elementsList = node.elements();
return elementsList;
}
}
难点一:ScanUtil类,这个类是spring容器的核心以及难点,我说一下过程,按照这个过程来看代码比较好理解。
分为几个步骤:
首先通过getClassName获取指定包下的所有类名的集合。
遍历每个类名的集合,用resolveComponent方法来扫描注解在类上的注解@MyController @MyService @MyRepository,
如果类上面有这些注解,则开始扫描此类上的属性上有没有@MyAutowired注解,如果存在@MyAutowired注解,则去属性对应的类上面递归上面的过程。直到类中没有@MyAutowired注解的时候。将类名添加到ComponentList链表中。在此过程中,如果类名是接口,并且有实现类的时候,添加到ComponentList链表中的是实现类的名字。为了完成此步骤,我设计了一个makeInterfaceAndImplMap方法用来绑定接口和其实现类,所以此框架目前只支持一个接口,一个实现类。
在第三步中,如果类上没有注解,并且是一个接口,此时默认是需要动态代理的接口,将此类名直接添加到ComponentList链表中。
package spring.Utils.scan;
import lombok.extern.slf4j.Slf4j;
import spring.Utils.AnnotationUtils;
import spring.Utils.ListAddUtils;
import spring.annotation.MyAutowired;
import spring.annotation.MyController;
import spring.annotation.MyRepository;
import spring.annotation.MyService;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Xiao Liang on 2018/6/27.
* 扫描工具类(核心方法是getClassName和getComponentList)
* 1 扫描包下的注解
* 2 扫描包下的类名
*/
4j
public class ScanUtil {
private static List<String> listClassName = new ArrayList<>();
private static List<String> componentList = new ArrayList<>();
private static Map<String, String> interfaceAndImplMap = new ConcurrentHashMap<>();
/**
* 扫描指定包下面的所有类名
*
* @param packageName,包名
* @return 类名的集合,
*/
public static List<String> getClassName(String packageName) {
Enumeration<URL> urls = null;
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
String newPackageName = packageName.replace(".", "/");
try {
urls = contextClassLoader.getResources(newPackageName);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
File packageFile = new File(url.getPath());
File[] files = packageFile.listFiles();
if (files == null) {
break;
}
for (File file :
files) {
//如果是class,则添加到list中返回
if (file.getName().endsWith(".class")) {
String templeName = (packageName.replace("/", ".") + "." + file.getName());
String newTempleName = templeName.substring(0, templeName.lastIndexOf("."));
listClassName.add(newTempleName);
}
//如果是package,则继续遍历
else {
String nextPackageName = newPackageName + "." + file.getName();
getClassName(nextPackageName);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return listClassName;
}
/**
* 返回 有注解的实例化顺序的链表
*/
public static List<String> getComponentList(String packageName) {
//获取所有类
List<String> classNameList = getClassName(packageName);
//将扫描的接口和其实现类,使用map对应上,模仿spring接口注入,复杂的原因是java不支持从接口获取实现类
makeInterfaceAndImplMap(classNameList);
for (String className :
classNameList) {
try {
//实例化每个类
resolveComponent(className);
} catch (ClassNotFoundException e) {
log.error("扫描注解的时候,{}没有找到", className);
e.printStackTrace();
}
}
return componentList;
}
/**
* getComponentList();递归调用的子方法
*
* @param className
*/
public static void resolveComponent(String className) throws ClassNotFoundException {
Class<?> aClass = Class.forName(className);
//在此处添加要识别的注解,也是每次扫描的顺序,最好遵循习惯
addNewAnnotation(MyController.class, aClass);
addNewAnnotation(MyService.class, aClass);
addNewAnnotation(MyRepository.class, aClass);
}
public static <A extends Annotation> void addNewAnnotation(Class<A> annotationClass, Class<?> aClass) throws ClassNotFoundException {
//如果类上有注解,判断属性上有没有注解
if (!AnnotationUtils.isEmpty(aClass.getAnnotation(annotationClass))) {
Field[] fields = aClass.getDeclaredFields();
if (fields == null || fields.length == 0) {
ListAddUtils.add(componentList, aClass.getName());
} else {
//跳出递归的语句,也就是最底层的类,如果所有属性没有@MyAutowired注解,则注入到链表中
if (isEmptyAutowired(fields)) {
ListAddUtils.add(componentList, aClass.getName());
} else {
//如果属性上有@MyAutowired,则继续递归
for (Field field :
fields) {
//递归具体的查找到底哪个属性上有@MyAutowired。
if (field.getAnnotation(MyAutowired.class) != null) {
//如果有则根据类名查找类,然后去对应的类中递归此过程
String newFieldName = field.getType().getName();
//如果是接口,则用其实现类注入
if (Class.forName(newFieldName).isInterface()) {
String nextName = convertInterfaceToImpl(newFieldName);
if (!componentList.contains(nextName)) {
resolveComponent(nextName);
}
} else {
resolveComponent(newFieldName);
}
}
}
ListAddUtils.add(componentList, aClass.getName());
}
}
}
//如果是需要动态的代理注入的接口,加入到实例化的链表中
else if (aClass.isInterface() && interfaceAndImplMap.get(aClass.getName()).equals("proxy")) {
ListAddUtils.add(componentList, aClass.getName());
}
}
/**
* 判断一组属性里面有没有注解
*
* @param fields
* @return
*/
private static boolean isEmptyAutowired(Field[] fields) {
for (Field field :
fields) {
if (!AnnotationUtils.isEmpty(field.getAnnotation(MyAutowired.class))) {
return false;
}
}
return true;
}
/**
* 工具类,组装接口和实现类
*
* @param classNameList
* @return
*/
private static Map<String, String> makeInterfaceAndImplMap(List<String> classNameList) {
Class<?> aClass = null;
//interfaceNameList是所有接口类名的链表
List<String> interfaceNameList = new ArrayList<>();
//这个链表保存的是有实现类的接口的链表名,默认没有实现类的接口即为需要动态注的链表
List<String> interfaceExist = new ArrayList<>();
//循环类名,将类名注入到链表中
for (String className :
classNameList) {
try {
aClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (aClass.isInterface()) {
interfaceNameList.add(aClass.getName());
}
}
for (String className :
classNameList) {
Class<?> bClass = null;
try {
bClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Class<?>[] interfaces = bClass.getInterfaces();
//如果是接口的实现类
if (interfaces != null && interfaces.length != 0) {
for (String interfaceName :
interfaceNameList) {
for (Class<?> interfaceClass :
interfaces) {
//如果既有接口,也有实现类,则组成map
if (interfaceName.equals(interfaceClass.getName())) {
interfaceAndImplMap.put(interfaceName, className);
interfaceExist.add(interfaceName);
}
}
}
}
}
//需要动态代理注入的接口,在map中用value = proxy来识别
interfaceNameList.removeAll(interfaceExist);
if (interfaceNameList != null && interfaceNameList.size() > 0) {
for (String spareInterfaceName :
interfaceNameList) {
interfaceAndImplMap.put(spareInterfaceName, "proxy");
}
System.out.println("已经存在的" + interfaceNameList);
}
return null;
}
/**
* 工具类:接口转换为实现类
*
* @param newFileName
* @return
*/
private static String convertInterfaceToImpl(String newFileName) {
Set<Map.Entry<String, String>> entries = interfaceAndImplMap.entrySet();
for (Map.Entry<String, String> entry :
entries) {
if (newFileName.equals(entry.getKey()) && !entry.getValue().equals("proxy")) {
return entry.getValue();
} else if (newFileName.equals(entry.getKey()) && entry.getValue().equals("proxy")) {
return entry.getKey();
}
}
return null;
}
}
这样获得实例化顺序的链表ComponentList之后,开始实例化,也就是initBean这个类。实例化通过反射new Instance()方法获得对象,绑定后的对象用beanContainerMap来保存。
但是这里存在一个问题,就是我们动态代理添加到ComponentList的是接口名称,接口名称不能直接new Instance(),所以这里标红处用的是动态代理实例化的对象,这部分代码是针对Mybatis的接口注入的。先不用管具体是什么意思,后续会讲解。
这里还要注意的是先解析xml还是先解析注解扫描的问题,spring是优先解析xml文件的bean,然后执行的注解注入。和这里的顺序一致。
package spring.factory;
import lombok.extern.slf4j.Slf4j;
import spring.Utils.AnnotationUtils;
import spring.annotation.MyAutowired;
import spring.constants.Constants;
import spring.mybatis.MySqlSession;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Xiao Liang on 2018/6/27.
*/
4j
public class InitBean extends BeanDefinition {
//初始化后的bean容器 key为class名,value为实例化对象
public Map<String, Object> beanContainerMap = new ConcurrentHashMap<>();
/**
* 初始化bean容器方法
* 注意,扫描的bean会覆盖xml中配置的bean,spring也是这样,扫描的注入和装配都是在xml之后
* MyAutowired暂时是根据名称装配和扫描
*/
public void initBeans() {
//初始化xml配置
initXmlBeans(Constants.contextConfigLocation);
initXmlBeans(Constants.springmvcConfigLocation);
//初始化扫描注解的配置
initAutowiredBeans(Constants.contextConfigLocation);
}
/**
* 初始化xml中bean内容的方法
*/
public void initXmlBeans(String contextConfigLocation) {
ApplicationContext applicationContext = new ApplicationContext(contextConfigLocation);
Class<?> aClass = null;
//从容器中取出bean,用application的getbean方法依次加载bean
Map<String, GenericBeanDefinition> beanDefinitionMap = super.getbeanDefinitionXmlMap(contextConfigLocation);
Set<Map.Entry<String, GenericBeanDefinition>> entries = beanDefinitionMap.entrySet();
for (Map.Entry<String, GenericBeanDefinition> entry :
entries) {
String beanId = entry.getKey();
String className = entry.getValue().getClassName();
try {
aClass = Class.forName(className);
} catch (ClassNotFoundException e) {
log.error("xml中{}无法实例化", className);
e.printStackTrace();
}
beanContainerMap.put(className, aClass.cast(applicationContext.getBean(beanId)));
}
}
/**
* 将所有的componentList(也就是加注解的类)里面的bean实例化
*
* @return
*/
public void initAutowiredBeans(String contextConfigLocation) {
List<String> componentList = super.getComponentList(contextConfigLocation);
System.out.println("实例化的顺序" + componentList);
//扫描到有注解的类,初始化类的名单
for (String className :
componentList) {
//将每一个类初始化
try {
initClass(className);
} catch (ClassNotFoundException e) {
log.error("{}没有找到", className);
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
/**
* 初始化每一个类的方法,初始化的时候由于spring要实现使用接口注入,所以比较麻烦
* 需要根据类名来判断是否有接口,然后在将接口名和实现类对应上装配到容器中
*
* @param className
*/
public void initClass(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> aClass = Class.forName(className);
//先判断这个类有没有接口,如果有接口,将接口装配
Class<?>[] interfaces = aClass.getInterfaces();
//如果类是接口,注入的对象是动态代理的对象
if (aClass.isInterface()){
MySqlSession mySqlSession = new MySqlSession();
beanContainerMap.put(aClass.getName(),mySqlSession.getMapper(aClass, Constants.mybatisConfigLocation));
}
//如果不是接口的实现类,也就是controller层
else if (interfaces == null || interfaces.length == 0) {
noInterfaceInit(className, className);
}
else {
for (Class<?> interfaceClass :
interfaces) {
boolean flag = isExistInContainer(className);
//容器中如果有,则直接使用这个对象进行装配
if (flag) {
beanContainerMap.put(interfaceClass.getName(), aClass.newInstance());
} else {
//如果容器中没有,则先实例化实现类,然后再装配到容器中
noInterfaceInit(className, interfaceClass.getName());
}
}
}
}
/**
* @param className
* @param interfaceName
*/
public void noInterfaceInit(String className, String interfaceName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> aClass = Class.forName(className);
//bean实例化
System.out.println("实例化的名字"+aClass.getName());
Object object = aClass.newInstance();
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field :
declaredFields) {
//如果属性上有MyAutowired注解,则先将属性注入进去
if (!AnnotationUtils.isEmpty(field.getAnnotation(MyAutowired.class))) {
//System.out.println("发现注解");
//设置私有属性可见
field.setAccessible(true);
//如果有注解,在实例化链表里面搜寻类名
Set<Map.Entry<String, Object>> entries = beanContainerMap.entrySet();
for (Map.Entry<String, Object> entry :
entries) {
String type = field.getType().getName();
if (entry.getKey().equals(type)){
field.set(object, entry.getValue());
}
}
}
}
beanContainerMap.put(interfaceName, object);
}
/**
* 属于工具类,不是很重要
* 在实例化该类之前先判断该类在容器中是否存在
*
* @param className
* @return
*/
public boolean isExistInContainer(String className) {
Set<Map.Entry<String, Object>> entries = beanContainerMap.entrySet();
if (entries != null) {
for (Map.Entry<String, Object> map :
entries) {
if (map.getKey().equals(className)) {
return true;
} else {
return false;
}
}
}
return false;
}
}
至此,spring容器的开发暂时告一段落,下一篇介绍springmvc的实现。
我将此项目上传到了github,需要的童鞋可以点击下方阅读原文自行下载。
好文推荐栏
推荐大而全的【后端技术精选】