内容简介:IOC控制反转就是通过反射机制帮我们托管了所有的类。我想要自己实现的就是使用XML注入Bean和使用注解(SpringIOC的XML版本使用Dom4j和反射技术解析XML和注入类
IOC控制反转就是通过反射机制帮我们托管了所有的类。
我想要自己实现的就是使用XML注入Bean和使用注解( @Service
之类的)注入Bean
Spring的Xml版本IOC原理
SpringIOC的XML版本使用Dom4j和反射技术解析XML和注入类
所有的Bean在ApplicationContext创建的时候就会初始化
XML版本注入
自行解析XML
一个自己解析XML的小Demo,使用Dom4j解析XML,如下
public class XmlUtils { public static void main(String[] args) throws DocumentException { XmlUtils xmlUtils = new XmlUtils(); xmlUtils.readXml("student.xml"); } public void readXml(String xmlPath) throws DocumentException { SAXReader saxReader = new SAXReader(); Document document = saxReader.read(getResourceAsSteam(xmlPath)); Element rootElement = document.getRootElement(); getNodes(rootElement); } private static void getNodes(Element rootElement) { //获取节点名称 System.out.print("节点名称:" + rootElement.getName()+"\t\t"); //获取节点属性 List<Attribute> attributes = rootElement.attributes(); for (Attribute attribute : attributes) { System.out.print("属性:"+attribute.getName()+"---"+attribute.getText()+"\t\t"); } //获取属性值 String value = rootElement.getTextTrim(); if (!StringUtils.isEmpty(value)) { System.out.print("节点值:" + value+"\t\t"); } System.out.println(); //遍历子节点 Iterator<Element> elementIterator = rootElement.elementIterator(); while (elementIterator.hasNext()) { Element next = elementIterator.next(); getNodes(next); } } private InputStream getResourceAsSteam(String xmlPath) { return this.getClass().getClassLoader().getResourceAsStream(xmlPath); } } 复制代码
自己实现XML获取Bean的ApplicationContext
-
实现步骤
- 读取配置XML
- 查看传入的BeanId和Xml中的BeanId是否一致
- 使用反射创建对象并且返回
- 按照上面的步骤实现自己的ApplicationContext 核心方法代码如下(这个getBean方法就是按照上面的步骤实现的,我把步骤的具体实现都抽取出去了)
/** 用于GetBean的方法*/ public Object getBean(String beanId) throws DocumentException, IllegalAccessException, InstantiationException, ClassNotFoundException { if (StringUtils.isEmpty(beanId)) { throw new RuntimeException("BeanId为空"); } //解析Xml,获取所有节点 List<Element> elements = readXml(); if (elements == null||elements.isEmpty()) { throw new RuntimeException("没有任何Bean信息"); } //查找对应的ClassName String className = getClassName(beanId, elements); if (StringUtils.isEmpty(className)) { throw new RuntimeException("没有配置类信息"); } //利用反射机制创建Bean return newInstance(className); } 复制代码
全文如下
public class ExtClassPathXmlApplicationContext { private String xmlPath; public ExtClassPathXmlApplicationContext(String xmlPath) { this.xmlPath = xmlPath; } /** 用于GetBean的方法*/ public Object getBean(String beanId) throws DocumentException, IllegalAccessException, InstantiationException, ClassNotFoundException { if (StringUtils.isEmpty(beanId)) { throw new RuntimeException("BeanId为空"); } //解析Xml,获取所有节点 List<Element> elements = readXml(); if (elements == null||elements.isEmpty()) { throw new RuntimeException("没有任何Bean信息"); } //查找对应的ClassName String className = getClassName(beanId, elements); if (StringUtils.isEmpty(className)) { throw new RuntimeException("没有配置类信息"); } //利用反射机制创建Bean return newInstance(className); } /**解析Xml文件,获取所有节点*/ private List<Element> readXml() throws DocumentException { SAXReader saxReader = new SAXReader(); Document document = saxReader.read(getResourceAsSteam()); Element rootElement = document.getRootElement(); List<Element> elements = rootElement.elements(); return elements; } } 复制代码
然后在主方法里创建上面的Context,使用getBean方法,就可以拿到想要的Bean了(和Spring的ClassPathApplicationContext一样)
使用注解注入Bean
一些需要注意的性质
- 需要把已知带有注解的类装入一个集合里,便于随时取用
- 加载时才初始化上面的集合,还需要注意线程安全问题
- 使用懒加载模式加载Bean
- 只实现了单例,所有getBean都是创建了一个实例
- 只实现了使用默认的beanId进行注入(类名第一个字母小写),不能自定义ID
实现注解装配Bean并且通过getBean方法获取Bean
-
实现步骤
- 使用反射机制,扫包,获取所有的类(使用了一个开源的扫包的 工具 类。。没有自己实现)
- 判断每个类上是否有注入bean的注解
- 使用反射机制进行初始化类
- 按照上面的步骤实现自己的ApplicationContext 核心代码如下
/**初始化Bean容器*/ private void initBeans() throws IllegalAccessException, InstantiationException { beans = new ConcurrentHashMap<String, Object>(); //使用扫包工具获得包下所有的类 List<Class<?>> classes = ClassUtils.getClasses(packageName); //判断所有的类上面是否有注解,有的话就会加入到Bean容器里面去 findClassExistAnnotation(classes); if (beans == null || beans.isEmpty()) { throw new RuntimeException("没有类加上了注解"); } } 复制代码
全文如下
public class ExtAnnotationApplicationContext { private String packageName; /**保存有Service注解的类*/ private ConcurrentHashMap<String, Object> beans = null; public ExtAnnotationApplicationContext(String packageName) throws InstantiationException, IllegalAccessException { this.packageName = packageName; initBeans(); } /**初始化Bean容器*/ private void initBeans() throws IllegalAccessException, InstantiationException { beans = new ConcurrentHashMap<String, Object>(); //使用扫包工具获得包下所有的类 List<Class<?>> classes = ClassUtils.getClasses(packageName); //判断所有的类上面是否有注解,有的话就会加入到Bean容器里面去 findClassExistAnnotation(classes); if (beans == null || beans.isEmpty()) { throw new RuntimeException("没有类加上了注解"); } } /**扫包,把有注解的类加入到bean容器里*/ private void findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException { for (Class classInfo : classes) { //判断是否有注解 Annotation annotation = classInfo.getAnnotation(ExtService.class); if (annotation != null) { //到这里表示有这个注解 String className = classInfo.getName(); //默认Id是首字母小写 beans.put(toLowerCaseFirestOne(classInfo.getSimpleName()), newInstance(classInfo)); } } } /**类名的首字母小写*/ private String toLowerCaseFirestOne(String className) { return new StringBuilder().append(Character.toLowerCase(className.charAt(0))).append(className.substring(1)).toString(); } /**获取Bean的方法*/ public Object getBean(String beanId) throws IllegalAccessException, InstantiationException { if (StringUtils.isEmpty(beanId)) { throw new RuntimeException("BeanID为空"); } return beans.get(beanId); } /**利用反射机制创建Bean*/ private Object newInstance(Class classInfo) throws IllegalAccessException, InstantiationException { if (classInfo == null) { throw new RuntimeException("没有这个ID的bean"); } return classInfo.newInstance(); } /**依赖注入传入类的属性*/ private void attrAssign(Class<?> classInfo) { //获取这个类所有的属性 Field[] fields = classInfo.getFields(); //判断当前属性是否有注解 for (Field field : fields) { ExtService extService = field.getAnnotation(ExtService.class); if (extService != null) { //到这里说明这个属性里有这个注解 String fieldName = field.getName(); } } } } 复制代码
现在获取到Bean只用在类上加上自己的Service注解然后使用getBean方法传入类名的首字母小写就可以了
实现自动装配(依赖注入)
-
自动装配/依赖注入原理(实现步骤)
- 使用反射机制获取当前类的所有属性
- 判断当前类是否存在注解
- 使用默认名称在Bean容器里查找对象,然后赋值
- 核心代码
/**自动注入注入这个对象的属性*/ private void attrAssign(Object object) throws IllegalAccessException { //获取这个类所有的属性 Field[] fields = object.getClass().getDeclaredFields(); //判断当前属性是否有注解 for (Field field : fields) { ExtService extService = field.getAnnotation(ExtService.class); if (extService != null) { //到这里说明这个属性里有这个注解,在从容器里获取对象然后给这个属性赋值 String fieldName = field.getName(); Object target = beans.get(fieldName); if (target == null) { throw new RuntimeException("注入\"" + fieldName + "\"属性失败,bean容器里没有这个对象"); } //允许访问私有属性 field.setAccessible(true); //第一个参数是这个属性所在的对象 field.set(object,target); } } } 复制代码
这个方法我们需要在Bean容器初始化完成之后,把所有的bean容器的Object里做一遍,达到依赖注入的效果,如下(如果给所有的类都实现注入Bean容器里的bean的话,就是依赖注入 @Autowired
了)
public ExtAnnotationApplicationContext(String packageName) throws InstantiationException, IllegalAccessException { this.packageName = packageName; initBeans(); //在所有Bean容器里所有bean自动注入所有的Bean for (Map.Entry<String, Object> entry : beans.entrySet()) { System.out.println("beanId:"+entry.getKey()); Object bean = entry.getValue(); attrAssign(bean); } } 复制代码
增加了依赖注入的Context全文如下
public class ExtAnnotationApplicationContext { private String packageName; /**保存有Service注解的类*/ private ConcurrentHashMap<String, Object> beans = null; public ExtAnnotationApplicationContext(String packageName) throws InstantiationException, IllegalAccessException { this.packageName = packageName; initBeans(); //在所有Bean容器里所有bean自动注入所有的Bean for (Map.Entry<String, Object> entry : beans.entrySet()) { System.out.println("beanId:"+entry.getKey()); Object bean = entry.getValue(); attrAssign(bean); } } /**初始化Bean容器*/ private void initBeans() throws IllegalAccessException, InstantiationException { beans = new ConcurrentHashMap<String, Object>(); //使用扫包工具获得包下所有的类 List<Class<?>> classes = ClassUtils.getClasses(packageName); //判断所有的类上面是否有注解,有的话就会加入到Bean容器里面去 findClassExistAnnotation(classes); if (beans == null || beans.isEmpty()) { throw new RuntimeException("没有类加上了注解"); } } /**扫包,把有注解的类加入到bean容器里*/ private void findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException { for (Class classInfo : classes) { //判断是否有注解 Annotation annotation = classInfo.getAnnotation(ExtService.class); if (annotation != null) { //到这里表示有这个注解 String className = classInfo.getName(); //默认Id是首字母小写 beans.put(toLowerCaseFirestOne(classInfo.getSimpleName()), newInstance(classInfo)); } } } /**类名的首字母小写*/ private String toLowerCaseFirestOne(String className) { return new StringBuilder().append(Character.toLowerCase(className.charAt(0))).append(className.substring(1)).toString(); } /**获取Bean的方法*/ public Object getBean(String beanId) throws IllegalAccessException, InstantiationException { if (StringUtils.isEmpty(beanId)) { throw new RuntimeException("BeanID为空"); } return beans.get(beanId); } /**利用反射机制创建Bean*/ private Object newInstance(Class classInfo) throws IllegalAccessException, InstantiationException { if (classInfo == null) { throw new RuntimeException("没有这个ID的bean"); } return classInfo.newInstance(); } /**自动注入注入这个对象的属性*/ private void attrAssign(Object object) throws IllegalAccessException { //获取这个类所有的属性 Field[] fields = object.getClass().getDeclaredFields(); //判断当前属性是否有注解 for (Field field : fields) { ExtResource extResource = field.getAnnotation(ExtResource.class); if (extResource != null) { //允许访问私有属性 field.setAccessible(true); //到这里说明这个属性里有这个注解,在从容器里获取对象然后给这个属性赋值 String fieldName = field.getName(); Object target = beans.get(fieldName); if (target == null) { throw new RuntimeException("注入\"" + fieldName + "\"属性失败,bean容器里没有这个对象"); } //第一个参数是这个属性所在的对象 field.set(object,target); } } } } 复制代码
以上所述就是小编给大家介绍的《手写源码(二):自己实现SpringIOC》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 手写源码(四):自己实现Mybatis
- 手写源码(三):自己实现SpringMVC
- 手写源码(一):自己实现Spring事务
- EventBus 源码详细分析:手写 EventBus 核心代码
- 阿里架构师手写Tomcat——Session源码解析
- node进阶——之事无巨细手写koa源码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Impractical Python Projects
Lee Vaughan / No Starch Press / 2018-11 / USD 29.95
Impractical Python Projects picks up where the complete beginner books leave off, expanding on existing concepts and introducing new tools that you’ll use every day. And to keep things interesting, ea......一起来看看 《Impractical Python Projects》 这本书的介绍吧!