spring是一站式轻量级的开源框架,“容器”是这个框架特征之一。有了这个容器,对象不再是我们主动创建,而是交给spring容器管理,从而实现程序的解耦。spring容器的核心就是IOC(控制反转)和DI(依赖注入),基于这两个核心,今天自己封装一个spring容器。
0.用到的工具类
工具类所需的Jar包
第一个工具类
public class CommonUtils {
/**
* 字符串首字母转小写
* @return
*/
public static String lowerFirst(String oldStr) {
char[] chars = oldStr.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
/**
* 将map集合封装到bean
* @param map
* @param object
*/
@SuppressWarnings("rawtypes")
public static void toBean(Map map, Object object) {
try {
BeanUtils.populate(object, map);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
第二个工具类是用于扫描包的下的类,由于能力有限,所以参照博客:https://blog.csdn.net/u013871439/article/details/70231288。这里就放其中两个方法,具体实现请点链接。
public class PackageUtils {
/**
* 从包package中获取所有的Class
*
* @param packageName
* @return classes
* 包下所有类的类类型
*/
public static List> getClasses(String packageName) {}
/**
* 以文件的形式来获取包下的所有Class
*
* @param packageName
* @param packagePath
* @param recursive
* @param classes
*/
private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List> classes) {}
}
1.解析xml文件
Jar包
解析xml文件用到dom4j技术
我在test.xml使用到的标签有:
0.
1.
2.
3.
创建两个实体类,用于存放xml文件中的节点和属性信息
节省篇幅只展示字段...
public class Bean {
private String id;//bean的id
private String className;//类名+包名
private List propertyList;//属性集合
}
public class Property {
private String name;//属性的名称
private String value;//属性值
private String ref;//要注入的对象
}
/**
* 获取xml中的信息
* @author 百逸同学
*
*/
public class XmlLoad {
/**
* 创建根节点对象
* @param url
*
xml文件路径
* @return rootElm
*
返回根节点对象
*/
public static Element CreateRootElement(String url) {
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new File(url));
Element rootElm = document.getRootElement();
return rootElm;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
/**
*
获取bean节点和bean的属性
* @param url
*
xml文件路径
* @return beanMap
*
Map
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static Map getBeanMap(String url) throws Exception{
Map beanMap = new HashMap();
List elements = CreateRootElement(url).elements("bean");
for (Element element : elements) {
Bean bean = new Bean();
if(element.attribute("id") == null){
throw new Exception("bean属性id找不到");
} else {
bean.setId(element.attribute("id").getValue());
}
if(element.attribute("class") == null){
throw new Exception("bean属性class找不到");
} else {
bean.setClassName(element.attribute("class").getValue());
}
List propertyList = getPropertyList(element);
bean.setPropertyList(propertyList);
beanMap.put(bean.getId(), bean);
}
return beanMap;
}
/**
* 获取bean子节点property
* @param element
* 当前bean的Element对象
* @return propertyList
* List
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static List getPropertyList(Element element) throws Exception{
List propertyList = new ArrayList();
List elements = element.elements("property");
for (Element element1 : elements) {
Property property = new Property();
if(element1.attribute("name") == null){
throw new Exception("property属性name找不到");
} else {
property.setName(element1.attribute("name").getValue());
}
if(element1.attribute("value") == null){
property.setValue(null);
} else {
property.setValue(element1.attribute("value").getValue());
}
if(element1.attribute("ref") == null){
property.setRef(null);
} else {
property.setRef(element1.attribute("ref").getValue());
}
propertyList.add(property);
}
return propertyList;
}
/**
* 获取扫描标签中的包名
* @param url
* xml文件路径
* @return packageName
*
返回包名
*/
public static String getPackageNameByXML(String url){
Element element = CreateRootElement(url).element("scanner");
if(element == null){
return null;
}
String packageName= element.attribute("base-package").getValue();
return packageName;
}
}
XmlLoad.java不做过多说明,就是获取xml中所配置的信息,具体使用方法请看dom4j的API
2.创建我们的容器工厂
//工厂的接口
public interface BeanFactory {
Object getBean(String beanId) throws Exception;
@SuppressWarnings("rawtypes")
Object getBean(Class clazz) throws Exception;
}
/**
* 容器工厂实现类
* @author 百逸同学
*
*/
public class BeanFactoryImpl implements BeanFactory {
private String url; // xml文件路径
/**
* 存放对象
* Map<对象名,对象>
* 必须为static
*/
private static Map beanInstanceMap = new HashMap();
public BeanFactoryImpl() {
super();
}
public BeanFactoryImpl(String url) {
super();
this.url = url;
/*
* 在构造方法调用实例化方法,实例当前类时就实例容器中的对象
* 就像使用spring时, new ClassPathXmlApplicationContext("applicationContext.xml");
*/
instance();
}
/**
* 从容器中取出对象
*/
@Override
public Object getBean(String beanId) throws Exception {
Object object = null;
if (beanInstanceMap.containsKey(beanId)) {
object = beanInstanceMap.get(beanId);
} else {
throw new Exception("找不到类");
}
return object;
}
@SuppressWarnings("rawtypes")
@Override
public Object getBean(Class clazz) throws Exception {
Object object = null;
if (beanInstanceMap.containsKey(clazz.getSimpleName().toLowerCase())) {
object = beanInstanceMap.get(clazz.getSimpleName().toLowerCase());
} else {
throw new Exception("找不到类");
}
return object;
}
/**
* 实例化对象,该方法在构造方法中调用(构造方法有说明)
* 分两种方式
* 1.使用注解
* 2.使用xml配置
*/
@SuppressWarnings({ "rawtypes" })
private void instance() {
try {
/*1.使用注解 */
String packageName = XmlLoad.getPackageNameByXML(this.url);//获取xml文件中要扫描的包名
if (packageName != null) {
List> classes = PackageUtils.getClasses(packageName);//得到指定包中所有类的类类型集合
classHandler(classes);//调用类处理方法,处理得到的类集合
}
/*2.使用xml配置*/
Map beanMap = XmlLoad.getBeanMap(this.url);//得到xml文件中配置的bean
beanMap.forEach((className, bean) -> {//开始循环
try {
Class clazz = Class.forName(bean.getClassName());
Object object = clazz.newInstance();//先实例当前bean,以便接下来操纵对象
List propertyList = bean.getPropertyList();//得到当前bean的property
/*property分为1.普通类型2.对象类型*/
if (propertyList != null) {
Map proMap = new HashMap();//存放普通类型属性的属性名和值
for (Property property : propertyList) {
if (property.getValue() != null) {//当property为普通类型
proMap.put(property.getName(), property.getValue());
}
if (property.getRef() != null) {//当property为对象类型
if (beanInstanceMap.get(property.getRef()) != null) {
Object diBean = beanInstanceMap.get(property.getRef());//从容器中获取要注入的对象
PropertyDescriptor pd = new PropertyDescriptor(property.getName(),//通过Java内省机制注入
object.getClass());
pd.getWriteMethod().invoke(object, diBean);
}
}
}
CommonUtils.toBean(proMap, object);//用工具类把普通类型的属性Map(proMap)注入到对象中
}
beanInstanceMap.put(bean.getId(), object);//最后把处理过的对象添加到容器
} catch (Exception e) {
e.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 类处理方法
* 当使用注解时,处理指定包下的带有JavaBean(自定义)注解的类
* 以及该类下面带有Resource(JDK)注解的属性
* @param classes
*/
public void classHandler(List> classes) {
try {
Object object = null;
for (Class> class1 : classes) {
JavaBean annotation = class1.getAnnotation(JavaBean.class);
if (annotation != null) {
object = class1.newInstance();
Field[] fields = class1.getDeclaredFields();
for (Field field : fields) {
Resource annotation1 = field.getAnnotation(Resource.class);
/*如果当前字段有Resource注解,则通过属性名来获取容器中的对象 */
if (annotation1 != null) {
Object diBean = beanInstanceMap.get(field.getName());
if (diBean != null) {
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), object.getClass());
pd.getWriteMethod().invoke(object, diBean);
}
}
}
}
if (!annotation.value().trim().equals("")) {//自定义注解JavaBean有Value可以自定义类的名称,如果有则按自定义
beanInstanceMap.put(annotation.value(), object);
return;
}
beanInstanceMap.put(CommonUtils.lowerFirst(class1.getSimpleName()), object);//没有自定义名称则类名首字母转小写
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
应该不用做过多解释了,代码注释的比较清楚
3.总结
上面的代码模拟了spring帮我们创建对象以及管理对象的过程,对个人来说意义还是蛮大的。写完对spring框架有了一个新的认识,也不再觉得spring框架抽象难以理解。编码过程中用到的大量反射,让我使用反射的技术有所提高,也再一次感受到Java反射机制的强大与神奇。同时,对面向对象的理解更加深入。
当然,上面的代码封装的并不好。规范也较差,如if套for又套if...阅读性实在太差。当然,欢迎各位前辈和小伙伴们对该代码有好的建议,或者更好的改进。