资讯专栏INFORMATION COLUMN

仿照 Spring 实现简单的 IOC 和 AOP - 上篇

layman / 1300人阅读

摘要:不过那个实现太过于简单,和,相去甚远。在接下来文章中,我也将从易到难,实现不同版本的和。切面切面包含了通知和切点,通知和切点共同定义了切面是什么,在何时,何处执行切面逻辑。

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. 接下来要上代码了。不过客官别急,上代码前,容我对代码结构做一下简单介绍:

SimpleIOC     // IOC 的实现类,实现了上面所说的4个步骤
SimpleIOCTest    // IOC 的测试类
Car           // IOC 测试使用的 bean
Wheel         // 同上 
ioc.xml       // bean 配置文件

容器实现类 SimpleIOC 的代码:

public class SimpleIOC {

    private Map 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 {
        // 加载 xml 配置文件
        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");
                
                // 加载 beanClass
                Class beanClass = null;
                try {
                    beanClass = Class.forName(className);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    return;
                }

                   // 创建 bean
                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");

                            // 利用反射将 bean 相关字段访问权限设为可访问
                        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));
                        }

                        // 将 bean 注册到 bean 容器中
                        registerBean(id, bean);
                    }
                }
            }
        }
    }

    private void registerBean(String id, Object bean) {
        beanMap.put(id, bean);
    }
}

容器测试使用的 bean 代码:

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 内容:


    
        
        
    

    
        
        
        
        
        
    

IOC 测试类 SimpleIOCTest:

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)

    通知定义了要织入目标对象的逻辑,以及执行时机。
    Spring 中对应了 5 种不同类型的通知:
    · 前置通知(Before):在目标方法执行前,执行通知
    · 后置通知(After):在目标方法执行后,执行通知,此时不关系目标方法返回的结果是什么
    · 返回通知(After-returning):在目标方法执行后,执行通知
    · 异常通知(After-throwing):在目标方法抛出异常后执行通知
    · 环绕通知(Around): 目标方法被通知包裹,通知在目标方法执行前和执行后都被会调用

切点(Pointcut)

    如果说通知定义了在何时执行通知,那么切点就定义了在何处执行通知。所以切点的作用就是
    通过匹配规则查找合适的连接点(Joinpoint),AOP 会在这些连接点上织入通知。

切面(Aspect)

   切面包含了通知和切点,通知和切点共同定义了切面是什么,在何时,何处执行切面逻辑。 

说完概念,接下来我们来说说简单 AOP 实现的步骤。这里 AOP 是基于 JDK 动态代理实现的,只需3步即可完成:

定义一个包含切面逻辑的对象,这里假设叫 logMethodInvocation

定义一个 Advice 对象(实现了 InvocationHandler 接口),并将上面的 logMethodInvocation 和 目标对象传入

将上面的 Adivce 对象和目标对象传给 JDK 动态代理方法,为目标对象生成代理

上面步骤比较简单,不过在实现过程中,还是有一些难度的,这里要引入一些辅助接口才能实现。接下来就来介绍一下简单 AOP 的代码结构:

MethodInvocation 接口  // 实现类包含了切面逻辑,如上面的 logMethodInvocation
Advice 接口        // 继承了 InvocationHandler 接口
BeforeAdvice 类    // 实现了 Advice 接口,是一个前置通知
SimpleAOP 类       // 生成代理类
SimpleAOPTest      // SimpleAOP 从测试类
HelloService 接口   // 目标对象接口
HelloServiceImpl   // 目标对象

MethodInvocation 接口代码:

public interface MethodInvocation {
    void invoke();
}

Advice 接口代码:

public interface Advice extends InvocationHandler {}

BeforeAdvice 实现代码:

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 实现代码:

public class SimpleAOP {
    public static Object getProxy(Object bean, Advice advice) {
        return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader(), 
                bean.getClass().getInterfaces(), advice);
    }
}

HelloService 接口,及其实现类代码:

public interface HelloService {
    void sayHelloWorld();
}

public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHelloWorld() {
        System.out.println("hello world!");
    }
}

SimpleAOPTest 代码:

public class SimpleAOPTest {
    @Test
    public void getProxy() throws Exception {
        // 1. 创建一个 MethodInvocation 实现类
        MethodInvocation logTask = () -> System.out.println("log task start");
        HelloServiceImpl helloServiceImpl = new HelloServiceImpl();
        
        // 2. 创建一个 Advice
        Advice beforeAdvice = new BeforeAdvice(helloServiceImpl, logTask);
        
        // 3. 为目标对象生成代理
        HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloServiceImpl,beforeAdvice);
        
        helloServiceImplProxy.sayHelloWorld();
    }
}

输出结果:

以上实现了简单的 IOC 和 AOP,不过实现的 IOC 和 AOP 还很简单,且只能独立运行。在下一篇文章中,我将实现一个较为复杂的 IOC 和 AOP,大家如果有兴趣可以去看看。好了,本篇文章到此结束。

本文在知识共享许可协议 4.0 下发布,转载请注明出处
作者:coolblog
为了获得更好的分类阅读体验,
请移步至本人的个人博客:http://www.coolblog.xyz


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70200.html

相关文章

  • 仿照 Spring 实现简单 IOC AOP - 下篇

    摘要:在上文中,我实现了一个很简单的和容器。比如,我们所熟悉的就是在这里将切面逻辑织入相关中的。初始化的工作算是结束了,此时处于就绪状态,等待外部程序的调用。其中动态代理只能代理实现了接口的对象,而动态代理则无此限制。 1. 背景 本文承接上文,来继续说说 IOC 和 AOP 的仿写。在上文中,我实现了一个很简单的 IOC 和 AOP 容器。上文实现的 IOC 和 AOP 功能很单一,且 I...

    AlexTuan 评论0 收藏0
  • Spring IOC 容器源码分析系列文章导读

    摘要:本文是容器源码分析系列文章的第一篇文章,将会着重介绍的一些使用方法和特性,为后续的源码分析文章做铺垫。我们可以通过这两个别名获取到这个实例,比如下面的测试代码测试结果如下本小节,我们来了解一下这个特性。 1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本。经过十几年的迭代,现在的 Spring 框架已经非常成熟了...

    NSFish 评论0 收藏0
  • Java后端

    摘要:,面向切面编程,中最主要的是用于事务方面的使用。目标达成后还会有去构建微服务,希望大家多多支持。原文地址手把手教程优雅的应用四手把手实现后端搭建第四期 SpringMVC 干货系列:从零搭建 SpringMVC+mybatis(四):Spring 两大核心之 AOP 学习 | 掘金技术征文 原本地址:SpringMVC 干货系列:从零搭建 SpringMVC+mybatis(四):Sp...

    joyvw 评论0 收藏0
  • Spring Boot [组件学习-Spring]

    摘要:框架最初是由编写的,并且年月首次在许可下发布。在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知在建议方法调用之前和之后,执行通知。方法执行之后,不考虑其结果,执行通知。 导读: 在上篇文章的结尾提到了Spring Boot 提供了一系列的框架整合(Starter POMs)帮助我们提升开发效率,但是这并不意味着我们不需要学习这些框架,反而更需要去学习,通过学习这些框架可以使...

    raoyi 评论0 收藏0
  • Java深入-框架技巧

    摘要:从使用到原理学习线程池关于线程池的使用,及原理分析分析角度新颖面向切面编程的基本用法基于注解的实现在软件开发中,分散于应用中多出的功能被称为横切关注点如事务安全缓存等。 Java 程序媛手把手教你设计模式中的撩妹神技 -- 上篇 遇一人白首,择一城终老,是多么美好的人生境界,她和他历经风雨慢慢变老,回首走过的点点滴滴,依然清楚的记得当初爱情萌芽的模样…… Java 进阶面试问题列表 -...

    chengtao1633 评论0 收藏0

发表评论

0条评论

layman

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<