1. 背景我在大四实习的时候开始接触 J2EE 方面的开发工作,也是在同时期接触并学习 Spring 框架,到现在也有快有两年的时间了。不过之前没有仿写过 Spring IOC 和 AOP,只是宏观上对 Spring IOC 和 AOP 原理有一定的认识。所以为了更进一步理解 Spring IOC 和 AOP 原理。在工作之余,参考了一些资料和代码,动手实现了一个简单的 IOC 和 AOP,并实现了如下功能:
根据 xml 配置文件加载相关 bean 对 BeanPostProcessor 类型的 bean 提供支持 对 BeanFactoryAware 类型的 bean 提供支持 实现了基于 JDK 动态代理的 AOP 整合了 IOC 和 AOP,使得二者可很好的协同工作 在实现自己的 IOC 和 AOP 前,我的想法比较简单,就是实现一个非常简单的 IOC 和 AOP,哪怕是几十行代码实现的都行。后来实现后,感觉还很有意思的。不过那个实现太过于简单,和 Spring IOC,AOP 相去甚远。后来想了一下,不能仅满足那个简单的实现,于是就有了这个仿写项目。相对来说仿写的代码要复杂了一些,功能也多了一点,看起来也有点样子的。尽管仿写出的项目仍然是玩具级,不过写仿写的过程中,还是学到了一些东西。总体上来说,收获还是很大的。在接下来文章中,我也将从易到难,实现不同版本的 IOC 和 AOP。好了,不多说了,开始干活。
2. 简单的 IOC 和 AOP 实现 2.1 简单的 IOC先从简单的 IOC 容器实现开始,最简单的 IOC 容器只需4步即可实现,如下:
加载 xml 配置文件,遍历其中的标签 获取标签中的 id 和 class 属性,加载 class 属性对应的类,并创建 bean 遍历标签中的标签,获取属性值,并将属性值填充到 bean 中 将 bean 注册到 bean 容器中 如上所示,仅需4步即可,是不是觉得很简单。好了,Talk is cheap, Show me the code. 接下来要上代码了。不过客官别急,上代码前,容我对代码结构做一下简单介绍:
1 2 3 4 5 SimpleIOC SimpleIOCTest Car Wheel ioc.xml
容器实现类 SimpleIOC 的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public class SimpleIOC { private Map<String, Object> beanMap = new HashMap<>(); public SimpleIOC (String location) throws Exception { loadBeans(location); } public Object getBean (String name) { Object bean = beanMap.get(name); if (bean == null ) { throw new IllegalArgumentException("there is no bean with name " + name); } return bean; } private void loadBeans (String location) throws Exception { InputStream inputStream = new FileInputStream(location); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = factory.newDocumentBuilder(); Document doc = docBuilder.parse(inputStream); Element root = doc.getDocumentElement(); NodeList nodes = root.getChildNodes(); for (int i = 0 ; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node instanceof Element) { Element ele = (Element) node; String id = ele.getAttribute("id" ); String className = ele.getAttribute("class" ); Class beanClass = null ; try { beanClass = Class.forName(className); } catch (ClassNotFoundException e) { e.printStackTrace(); return ; } Object bean = beanClass.newInstance(); NodeList propertyNodes = ele.getElementsByTagName("property" ); for (int j = 0 ; j < propertyNodes.getLength(); j++) { Node propertyNode = propertyNodes.item(j); if (propertyNode instanceof Element) { Element propertyElement = (Element) propertyNode; String name = propertyElement.getAttribute("name" ); String value = propertyElement.getAttribute("value" ); Field declaredField = bean.getClass().getDeclaredField(name); declaredField.setAccessible(true ); if (value != null && value.length() > 0 ) { declaredField.set(bean, value); } else { String ref = propertyElement.getAttribute("ref" ); if (ref == null || ref.length() == 0 ) { throw new IllegalArgumentException("ref config error" ); } declaredField.set(bean, getBean(ref)); } registerBean(id, bean); } } } } } private void registerBean (String id, Object bean) { beanMap.put(id, bean); } }
容器测试使用的 bean 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Car { private String name; private String length; private String width; private String height; private Wheel wheel; } public class Wheel { private String brand; private String specification ; }
bean 配置文件 ioc.xml 内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <beans > <bean id ="wheel" class ="com.titizz.simulation.toyspring.Wheel" > <property name ="brand" value ="Michelin" /> <property name ="specification" value ="265/60 R18" /> </bean > <bean id ="car" class ="com.titizz.simulation.toyspring.Car" > <property name ="name" value ="Mercedes Benz G 500" /> <property name ="length" value ="4717mm" /> <property name ="width" value ="1855mm" /> <property name ="height" value ="1949mm" /> <property name ="wheel" ref ="wheel" /> </bean > </beans >
IOC 测试类 SimpleIOCTest:
1 2 3 4 5 6 7 8 9 10 11 public class SimpleIOCTest { @Test public void getBean () throws Exception { String location = SimpleIOC.class.getClassLoader().getResource("spring-test.xml" ).getFile(); SimpleIOC bf = new SimpleIOC(location); Wheel wheel = (Wheel) bf.getBean("wheel" ); System.out.println(wheel); Car car = (Car) bf.getBean("car" ); System.out.println(car); } }
测试结果:
以上是简单 IOC 实现的全部内容,难度不大,代码也不难看懂,这里不再多说了。下面说说简单 AOP 的实现。
2.2 简单的 AOP 实现AOP 的实现是基于代理模式的,这一点相信大家应该都知道。代理模式是AOP实现的基础,代理模式不难理解,这里就不花篇幅介绍了。在介绍 AOP 的实现步骤之前,先引入 Spring AOP 中的一些概念,接下来我们会用到这些概念。
通知(Advice)
1 2 3 4 5 6 7 通知定义了要织入目标对象的逻辑,以及执行时机。 Spring 中对应了 5 种不同类型的通知:· 前置通知(Before ):在目标方法执行前,执行通知 · 后置通知(After ):在目标方法执行后,执行通知,此时不关系目标方法返回的结果是什么 · 返回通知(After - returning ):在目标方法执行后,执行通知 · 异常通知(After - throwing ):在目标方法抛出异常后执行通知 · 环绕通知(Around ): 目标方法被通知包裹,通知在目标方法执行前和执行后都被会调用
切点(Pointcut)
1 2 如果说通知定义了在何时执行通知,那么切点就定义了在何处执行通知。所以切点的作用就是 通过匹配规则查找合适的连接点(Joinpoint),AOP 会在这些连接点上织入通知。
切面(Aspect)
1 切面包含了通知和切点,通知和切点共同定义了切面是什么,在何时,何处执行切面逻辑。
说完概念,接下来我们来说说简单 AOP 实现的步骤。这里 AOP 是基于 JDK 动态代理实现的,只需3步即可完成:
定义一个包含切面逻辑的对象,这里假设叫 logMethodInvocation 定义一个 Advice 对象(实现了 InvocationHandler 接口),并将上面的 logMethodInvocation 和 目标对象传入 将上面的 Adivce 对象和目标对象传给 JDK 动态代理方法,为目标对象生成代理 上面步骤比较简单,不过在实现过程中,还是有一些难度的,这里要引入一些辅助接口才能实现。接下来就来介绍一下简单 AOP 的代码结构:
1 2 3 4 5 6 7 MethodInvocation 接口 Advice 接口 BeforeAdvice 类 SimpleAOP 类 SimpleAOPTest HelloService 接口 HelloServiceImpl
MethodInvocation 接口代码:
1 2 3 public interface MethodInvocation { void invoke () ; }
Advice 接口代码:
1 public interface Advice extends InvocationHandler {}
BeforeAdvice 实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class BeforeAdvice implements Advice { private Object bean; private MethodInvocation methodInvocation; public BeforeAdvice (Object bean, MethodInvocation methodInvocation) { this .bean = bean; this .methodInvocation = methodInvocation; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { methodInvocation.invoke(); return method.invoke(bean, args); } }
SimpleAOP 实现代码:
1 2 3 4 5 6 public class SimpleAOP { public static Object getProxy (Object bean, Advice advice) { return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader(), bean.getClass().getInterfaces(), advice); } }
HelloService 接口,及其实现类代码:
1 2 3 4 5 6 7 8 9 10 public interface HelloService { void sayHelloWorld () ; } public class HelloServiceImpl implements HelloService { @Override public void sayHelloWorld () { System.out.println("hello world!" ); } }
SimpleAOPTest 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class SimpleAOPTest { @Test public void getProxy () throws Exception { MethodInvocation logTask = () -> System.out.println("log task start" ); HelloServiceImpl helloServiceImpl = new HelloServiceImpl(); Advice beforeAdvice = new BeforeAdvice(helloServiceImpl, logTask); HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloServiceImpl,beforeAdvice); helloServiceImplProxy.sayHelloWorld(); } }
输出结果:
以上实现了简单的 IOC 和 AOP,不过实现的 IOC 和 AOP 还很简单,且只能独立运行。在下一篇文章中,我将实现一个较为复杂的 IOC 和 AOP,大家如果有兴趣可以去看看。好了,本篇文章到此结束。
附录:Spring 源码分析文章列表 Ⅰ. IOC Ⅱ. AOP Ⅲ. MVC