Java模拟Sping,实现其IOC和AOP核心
Posted 松饼人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java模拟Sping,实现其IOC和AOP核心相关的知识,希望对你有一定的参考价值。
接着上一篇,在上一篇完成了有关IOC的注解实现,这一篇用XML的方式实现IOC,并且完成AOP。
简易的IOC框图
注解的方式实现了左边的分支,那么就剩下右边的XML分支:
XmlContext:
这个类是也是AbstractApplicationContext的子类,和AnnotationContext相似,只不过这里是要解析XML文件而不是注解:
(关于XML文件的解析之前给过一篇博客:【Java】XML文件的解析
对于XML文件的处理,是不太容易的,会产生很多问题,后面只是实现核心步骤,很多属性就不考虑了!
首先给出XmlBean,和AnnotationBean一样,都是继承自BeanElement
1 public class XmlBean implements BeanElement { 2 private boolean DI; 3 private Object object; 4 private Object proxy; 5 private Map<Field, String> wiredMap; 6 // key:object的为注入成员 value:依赖的className 7 // 将不能注入的成员先保存起来 8 9 protected XmlBean() { 10 this(true, null, null); 11 } 12 13 protected XmlBean(Object object, Object proxy) { 14 this(true, object, proxy); 15 } 16 17 protected XmlBean(boolean dI, Object object, Object proxy) { 18 DI = dI; 19 this.object = object; 20 this.proxy = proxy; 21 } 22 23 protected void addWiredElement(Field field, String ref) throws RepeatProperty { 24 if (wiredMap == null) { 25 wiredMap = new HashMap<>(); 26 } 27 if (wiredMap.containsKey(field)) { 28 throw new RepeatProperty(object.getClass() + "成员:" + field.getName() + "已定义!"); 29 } 30 wiredMap.put(field, ref); 31 } 32 33 protected void setDI(boolean DI) { 34 this.DI = DI; 35 } 36 37 protected Map<Field, String> getWiredMap() { 38 return wiredMap; 39 } 40 41 @Override 42 @SuppressWarnings("unchecked") 43 public <E> E getProxy() { 44 return (E) proxy; 45 } 46 47 @Override 48 public Object getObject() { 49 return object; 50 } 51 52 @Override 53 public boolean isDI() { 54 return DI; 55 } 56 57 }
XmlContext
1 public class XmlContext extends AbstractApplicationContext { 2 protected XmlContext() { 3 } 4 5 protected XmlContext(String xmlPath) { 6 innerParseXml(xmlPath); 7 } 8 9 // 和注解方式中的做法一样,只不过产生的是XML方式的BeanElement 10 private XmlBean addXmlBean(Class<?> klass, Object object, String classId, String className) throws BeansException { 11 Object proxy = aopFactory.creatCGLibProxy(klass, object); 12 XmlBean bean = new XmlBean(object, proxy); 13 add(classId, className, bean); 14 return bean; 15 } 16 17 protected void innerParseXml(String xmlPath) { 18 // 找到根标签 19 new XMLReader() { 20 @Override 21 public void dealElment(Element element, int index) { 22 // 处理bean标签 23 new XMLReader() { 24 @Override 25 public void dealElment(Element element, int index) { 26 // 得到id属性和class属性的值 27 String classId = element.getAttribute("id"); 28 String className = element.getAttribute("class"); 29 try { 30 // 由class得到类 31 Class<?> klass = Class.forName(className); 32 // 处理constructor标签 33 new XMLReader() { 34 @Override 35 public void dealElment(Element element, int index) { 36 // TODO 处理有参数的构造方法,这里就会遇到许多问题,在这里我就不处理了,后面会给出解决思路 37 } 38 }.parse(element, "constructor-arg"); 39 // 由于上面没有处理带参数的构造方法,这里直接通过反射机制调用无参构造产生对象 40 // 并且利用产生的对象生成代理对象,最后得到Bean放入beanMap中 41 Object object = klass.newInstance(); 42 XmlBean bean = addXmlBean(klass, object, classId, className); 43 44 // 处理property标签 45 new XMLReader() { 46 @Override 47 public void dealElment(Element element, int index) { 48 try { 49 dealProperty(element, klass, bean); 50 } catch (XmlPropertyMustNeedNameException e) { 51 e.printStackTrace(); 52 } catch (Exception e) { 53 e.printStackTrace(); 54 } 55 } 56 }.parse(element, "property"); 57 } catch (Exception e1) { 58 e1.printStackTrace(); 59 } 60 } 61 }.parse(element, "bean"); 62 } 63 }.parse(XMLReader.openDocument(xmlPath), "SimpleSpring"); 64 } 65 66 private void dealProperty(Element element, Class<?> klass, XmlBean bean) 67 throws XmlPropertyMustNeedNameException, Exception { 68 // 得到property标签name属性的值 69 String fieldName = element.getAttribute("name"); 70 if (fieldName.length() <= 0) { 71 throw new XmlPropertyMustNeedNameException("Bean" + klass.getName() + "的Property标签必须声明name属性!"); 72 } 73 // 通过反射机制得到成员 74 Field field = klass.getDeclaredField(fieldName); 75 // 得到该成员的类型 76 Class<?> fieldType = field.getType(); 77 // 得到value属性 78 String value = element.getAttribute("value"); 79 // 得到ref属性 80 String ref = element.getAttribute("ref"); 81 82 // 判断ref和value是否同时存在 83 if (value.length() > 0 && ref.length() > 0) { 84 throw new CanNotJudgeParameterException("value:" + value + " ref:" + ref + "只能存在一个!"); 85 } 86 Object arg = null; 87 // value存在,则直接通过类型转换给成员赋值 88 if (value.length() > 0) { 89 if (!fieldType.isPrimitive() && !fieldType.equals(String.class)) { 90 throw new ValueOnlyPrimitiveType("Value只能用于八大基本类型!"); 91 } 92 // TypeConversion是我自己写的,将字符串转换为基本类型的工具 93 arg = TypeConversion.getValue(value, fieldType.getSimpleName()); 94 field.setAccessible(true); 95 field.set(bean.getObject(), arg); 96 } 97 if (ref.length() > 0) { 98 // ref属性存在,由于存在相互依赖关系,所以现在不做处理,只是将其保存起来 99 // 设置该bean的状态为尚未注入 100 bean.setDI(false); 101 bean.addWiredElement(field, ref); 102 } 103 } 104 105 }
XmlContext能做的工作也十分有限,只能完成简单的注入,剩下的注入工作留给下一级处理!
在这里之所以没有处理constructor标签,是因为对与构造方法的处理存在许多因素:
比如:
1 public class Test { 2 public Test(String one, int two) { 3 ...... 4 } 5 public Test(int two, String one) { 6 ...... 7 } 8 }
通过XML文件读取出来的都是字符串,如何区分它是字符串“123”,而不是int类型123?这两个构造方法到底执行哪个?
再比如说:
1 public Test(int one, int two, Student student) { 2 ...... 3 } 4 5 public Test(String one, int two, Student student) { 6 ...... 7 } 8 9 public Test(int two, String one, Student student) { 10 ...... 11 }
通过反射机制,我们就需要得到构造方法的集合getConstructors();然后筛选出参数个数符合要求的子集,再遍历这个子集的每一个构造方法,然后遍历当前构造方法的所有参数,一个一个比对参数类型是否符合要求,直到找到符合要求的那一个为止,但是,如果说我们是想执行第三个构造方法,它却找到的是第一个,完全就出问题了!
所以Spring的解决办法是给出一个type属性
1 <bean id="xxx" class="xxx.xxx.Test"> 2 <constructor-arg idnex="0" value="1" type="int.class"> 3 <constructor-arg idnex="1" value="2" type="java.lang.String"> 4 <constructor-arg idnex="2" ref="student"> 5 </bean>
只有这样做才能真真区分,所以以后在使用Spring的constructor标签时,当构造方法有歧义时,一定要给出type属性,避免出错,也减少了查找时的遍历!
接下来就是最后一个类,xml分支的最高容器:
ClassPathXmlApplicationContext
上面的XmlContext只是完成了基本的注入问题,还有后续有关于注入之间的依赖关系,甚至是依赖循环(关于依赖循环在我的上一篇中有专门介绍,这里就不再介绍了)
1 public class ClassPathXmlApplicationContext extends XmlContext { 2 public ClassPathXmlApplicationContext() { 3 } 4 5 public ClassPathXmlApplicationContext(String xmlPath) { 6 super(xmlPath); 7 } 8 9 public ClassPathXmlApplicationContext parseXml(String xmlPath) { 10 innerParseXml(xmlPath); 11 return this; 12 } 13 14 @Override 15 public <T> T getBean(Class<T> klass) throws BeansException { 16 String className = klass.getName(); 17 BeanElement bean = beanMap.get(className); 18 19 if (bean == null) { 20 throw new BeansException("Bean :" + klass + "不存在!"); 21 } 22 // 在这里还是只考虑XmlBean的注入,不考虑AnnotationBlean注解的完成情况 23 if (!bean.isDI() && bean instanceof XmlBean) { 24 autowired(className, (XmlBean)bean); 25 } 26 27 return bean.getProxy(); 28 } 29 30 private void autowired(String klassName, XmlBean bean) throws BeansException { 31 // 和AnnotationBean的解决思路一样,先设置状态为已注入,防止循环依赖的无限递归 32 bean.setDI(true); 33 // 得到尚未注入的成员map 34 Map<Field, String> wiredMap = bean.getWiredMap(); 35 if (wiredMap == null || wiredMap.isEmpty()) return; 36 // 遍历map 37 for (Field field : wiredMap.keySet()) { 38 String ref = wiredMap.get(field); 39 String tagClassName = beanNameMap.get(ref); 40 // ref如果是id则在beanNameMap中找,如果是className就在beanMap中找 41 BeanElement wiredBean = tagClassName == null ? beanMap.get(ref) : beanMap.get(tagClassName); 42 if (bean == null) { 43 return; 44 } 45 if (!wiredBean.isDI() && wiredBean instanceof XmlBean) { 46 autowired(ref, (XmlBean)wiredBean); 47 } 48 field.setAccessible(true); 49 try { 50 field.set(bean.getObject(), wiredBean.getObject()); 51 } catch (Exception e) { 52 throw new BeansException(klassName + "依赖关系不正确!"); 53 } 54 } 55 wiredMap.clear(); 56 } 57 58 }
看过注解方式的话再看XML就会发现两者其实是一回事,都是通过两者提供的映射关系,利用反射机制完成注入!
只不过两者提供的映射关系在解析起来时各有各的特点!
Xml方式的实现这里就简单实现了,来看看使用情况:
1 public class StudentA { 2 String name; 3 private StudentB B; 4 5 public StudentA() { 6 } 7 8 @Override 9 public String toString() { 10 return "A:" + name + "->" + B; 11 } 12 13 } 14 15 @Component 16 public class StudentB { 17 private String name; 18 private StudentC C; 19 20 public StudentB() { 21 } 22 23 @Override 24 public String toString() { 25 return "B:" + name + "->" + C; 26 } 27 28 } 29 30 @Component 31 public class StudentC { 32 private String name; 33 private StudentA A; 34 35 public StudentC() { 36 } 37 38 @Override 39 public String toString() { 40 return "C:" + name; 41 } 42 43 }
xml的配置:
1 <SimpleSpring> 2 <bean id="haha" class="com.zc.ioc.demo.StudentA"> 3 <property name="name" value="我是A"></property> 4 <property name="B" ref="com.zc.ioc.demo.StudentB"></property> 5 </bean> 6 <bean class="com.zc.ioc.demo.StudentB"> 7 <property name="name" value="我是B"></property> 8 <property name="C" ref="com.zc.ioc.demo.StudentC"></property> 9 </bean> 10 <bean class="com.zc.ioc.demo.StudentC"> 11 <property name="name" value="我是C"></property> 12 <property name="A" ref="haha"></property> 13 </bean> 14 </SimpleSpring>
主函数:
1 public static void main(String[] args) throws BeansException { 2 // 或者是使用BeanFactory beanFactory = new ClassPathXmlApplicationContext("/test_simple_spring.xml"); 3 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/test_simple_spring.xml"); 4 StudentA bean = applicationContext.getBean(StudentA.class); 5 System.out.println(bean); 6 }
输出:
那么试一试注解和Xml方式的混合使用:
1 @Component 2 public class StudentA { 3 @Value(value="我是A") 4 String name; 5 @Autowired 6 private StudentB B; 7 8 public StudentA() { 9 } 10 11 @Override 12 public String toString() { 13 return "A:" + name + "->" + B; 14 } 15 16 } 17 18 @Component 19 public class StudentB { 20 @Value(value="我是B") 21 private String name; 22 @Autowired 23 private StudentC C; 24 25 public StudentB() { 26 } 27 28 @Override 29 public String toString() { 30 return "B:" + name + "->" + C; 31 } 32 33 } 34 @Component 35 public class StudentC { 36 @Value(value="我是C") 37 private String name; 38 @Autowired 39 private StudentD D; 40 41 @Autowired 42 private StudentA A; 43 44 public StudentC() { 45 } 46 47 @Override 48 public String toString() { 49 return "C:" + name + "->" + D; 50 } 51 52 } 53 54 public class StudentD { 55 private String name; 56 57 public StudentD() { 58 } 59 60 @Override 61 public String toString() { 62 return "D:" + name; 63 } 64 65 }
Xml配置:
1 <SimpleSpring> 2 <bean class="com.zc.ioc.demo.StudentD"> 3 <property name="name"Sping AOP Capabilities and Goals