作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可。

最近工作很忙,但当初打算学习spring源码的事已经基本告一段落,只是一直没时间写这些记录性的文字。

本次鄙人学习spring的源码,有点囫囵吞枣的感觉,其实这样并非就不好,spring作为一个应用平台,自然不是那么好研究透彻的,而且也不太可能有人把spring的源码全部清楚的过上一遍,哪怕是spring的缔造者。不过最主要的原因是我们确实没有必要把源码全部过一遍。

在看spring源码的过程中,学习到了很多东西,这就足够了,而且现在如果在使用spring的过程中出现任何问题,不会再像以前一样有时候会束手无策,甚至把全部希望寄托于度娘,这其实是不正确的思想。从源码出发,可以发现,其实以前觉得无头绪的问题,现在都会迎刃而解。这就是研究源码最大的收获吧。

言归正传,来说说AOP。AOP的中文翻译是面向切面编程,与面向对象,面向接口,面向服务等概念看起来挺相似的。其实最近一直看有关JAVA的书籍,多多少少也对于这些有些理解,所谓面向切面,即是使用切面与其它事物产生关系。面向对象强调一切皆对象,也可以说面向接口是一切皆接口,面向服务则是一切皆服务,而切面也是一样,一切皆切面。

为什么这么说,面向对象就不说了,就拿面向接口来说吧,强调的就是我们操纵的都是一些接口,所以我们永远不会依赖于实现,面向切面的思想,其实也很类似,即我们操纵的都是一些切面,不会依赖于流程。也正是因为如此,所以我们可以优雅的解决WEB应用中一些原本很棘手或者不好处理的问题。

以上都是一些理论性的东西,也都是鄙人自己最近的感悟,虽然不知对错,但我觉得这种感悟很重要。

下面我们具体说说AOP,目前由AOP联盟给出了AOP的标准,AOP联盟的规范只是提供了几个接口定义,为了统一AOP的标准,下面来看看主要的几个接口。

Advice接口:

这是一个空接口,里面没有任何方法,只是用来标识一个通知,在spring中,所有的advice都是此接口的扩展,比如BeforeAdvice,AfterAdvice等。

Interceptor接口:

Advice的子接口,这个接口和advice都不直接使用,一般是要扩展以后去实现特殊的拦截。

Joinpoint接口:

代表了一个运行时的连接点。

Invocation接口:

代表了程序中的一个调用,可以被拦截器Interceptor拦截。

下面还有几个接口,不再一一介绍,从下一层继承开始,interpretor的继承体系已经开始依赖于invocation。这从某种意义上来说,advice是依赖于joinpoint的,但这并非绝对。

下面给出这几个接口的UML图,关系比较清晰。

下面介绍下spring中的AOP核心接口。

Advice体系:

Spring采用AOP联盟的Advice作为超级接口,扩展了很多子接口,比如BeforeAdvice,AfterAdvice等等,稍后以AfterReturningAdvice为例,讨论下spring的通知体系。

Pointcut接口:

spring采用Pointcut作为切点的抽象,其中有一个方法返回一个MethodMatcher,作用很明显,就是说切点决定了要切入哪些方法。这里其实是定义了一个匹配规则。比如正则匹配,可以只匹配前缀为save的方法等等。

Advisor:

通知器或者说通知者,我们从现实角度来说,通知者当然需要知道要通知什么。所以Advisor依赖于Advice,而Advisor旗下的子接口PointAdvisor还依赖于Pointcut,也就是说这个接口更确切的定位应该是包含了要通知谁和要通知什么,也就是说要能获得Advice和Pointcut。

下面我们先用一个例子说明spring的AOP是如何工作的,在spring的bean中有一种特殊的bean,叫FactoryBean。这并不是一个普通的bean,而是用来产生bean的一个bean。这样说起来有点绕口,但这个接口就是用来做这个事的,它是为了实现一系列特殊加工过的bean而产生的接口。

比如AOP中,我们其实就是要对一个bean进行增强,进行加工,让它在运行的过程中可以做一些特殊的事情。

ProxyFactoryBean就是一个为了AOP实现的特殊的FactoryBean,它的作用很明显就是产生proxy的bean,也就是被我们增强过的bean,在这里给出它的源码。

public class ProxyFactoryBean extends ProxyCreatorSupport
        implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {

    /**
     * This suffix in a value in an interceptor list indicates to expand globals.
     */
    public static final String GLOBAL_SUFFIX = "*";

    protected final Log logger = LogFactory.getLog(getClass());

    private String[] interceptorNames;

    private String targetName;

    private boolean autodetectInterfaces = true;

    private boolean singleton = true;

    private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();

    private boolean freezeProxy = false;

    private transient ClassLoader proxyClassLoader = ClassUtils.getDefaultClassLoader();

    private transient boolean classLoaderConfigured = false;

    private transient BeanFactory beanFactory;

    /** Whether the advisor chain has already been initialized */
    private boolean advisorChainInitialized = false;

    /** If this is a singleton, the cached singleton proxy instance */
    private Object singletonInstance;

在这里,一样没有贴出全部的源码,我们主要关心它的属性,其中interpretorNames和targetName就是这个类最主要的属性,前者代表的是需要加强哪些东西以及需要怎样加强,也就是advice和pointcut。而后者代表的则是我们针对谁来做这些加强,即我们的目标对象。

这里面的增强比较灵活,我来说一下需要的属性。

1.首先interpretorNames是必须赋予的属性,这个属性指定了通知器或者是通知的名字。如果传入的是通知Advice,则会被自动包装为通知器。

2.targetName是我们要增强的目标对象,这个对象如果有实现的接口,则会采用JDK的动态代理实现,否则将需要第三方的jar包cglib。

下面给出一段代码,来测试一下这种增强方式。首先我们需要一个bean.xml的配置文件,去配置这些属性。这样就将IOC和AOP结合起来使用了。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="testAdvisor" class="com.springframework.aop.test.TestAdvisor"></bean>
    <bean id="testTarget" class="com.springframework.aop.test.TestTarget"></bean>
    <bean id="testAOP" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="targetName">
            <value>testTarget</value>
        </property>
        <property name="interceptorNames">
            <list>
                <value>testAdvisor</value>
            </list>
        </property>
    </bean>

</beans>

下面我们看看目标对象的源码。

public class TestTarget{

    public void test() {
        System.out.println("target.test()");
    }

    public void test2(){
        System.out.println("target.test2()");
    }
}

很简单,只有两个普通的方法,下面我们看通知器的源码。

public class TestAdvisor implements PointcutAdvisor{

    public Advice getAdvice() {
        return new TestAfterAdvice();
    }

    public boolean isPerInstance() {
        return false;
    }

    public Pointcut getPointcut() {
        return new TestPointcut();
    }

}

我使用的是包含了切点的通知器,所以在这里我们还要给出通知和切点,我写了两个简单的通知和切点。

public class TestAfterAdvice implements AfterReturningAdvice{

    public void afterReturning(Object returnValue, Method method,
            Object[] args, Object target) throws Throwable {
        System.out.println("after " + target.getClass().getSimpleName() + "." + method.getName() + "()");
    }

}

下面是切点,切点的描述是我们只增强test方法,不增强test2方法。

public class TestPointcut implements Pointcut{

    public ClassFilter getClassFilter() {
        return ClassFilter.TRUE;
    }

    public MethodMatcher getMethodMatcher() {
        return new MethodMatcher() {

            public boolean matches(Method method, Class<?> targetClass, Object[] args) {
                if (method.getName().equals("test")) {
                    return true;
                }
                return false;
            }

            public boolean matches(Method method, Class<?> targetClass) {
                if (method.getName().equals("test")) {
                    return true;
                }
                return false;
            }

            public boolean isRuntime() {
                return true;
            }
        };
    }

}

好了,下面我们的准备工作都已经做好了。来看一下见证奇迹的时刻吧,我们使用IOC容器来帮我们完成增强,以下这段代码的运行需要cglib,测试代码如下:

public class TestAOP {

    public static void main(String[] args) {
        ApplicationContext applicationContext = new FileSystemXmlApplicationContext("classpath:beans.xml");
        TestTarget target = (TestTarget) applicationContext.getBean("testAOP");
        target.test();
        System.out.println("------无敌分割线-----");
        target.test2();
    }

}

输入结果会发现,在target.test方法执行后,我们增强的afterReturningAdvice已经起作用了,但是我们将切点定义在了test方法,所以test2方法并没有被增强。

这一章主要是和各位一起感受一下springAOP的神奇,下一章,将会是spring学习之路的最后一章,我会和各位一起看一下上述我们的增强过程是如何完成的。

spring源码学习之路---AOP初探(六)的更多相关文章

  1. spring源码学习之路---IOC初探(二)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章当中我没有提及具体的搭 ...

  2. spring源码学习之路---深入AOP(终)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...

  3. spring源码学习之路---环境搭建(一)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近已经开始了spring源 ...

  4. Spring 源码学习(4) —— 动态AOP使用示例

    在实际工作中, 此bean可能是满足业务需要的核心逻辑, 例如test()方法中可能会封装着某个核心业务, 如果在test()方法前后加入日志来跟踪调试, 直接修改源码并不符合面向对象的设计模式, 而 ...

  5. spring源码学习之路---IOC实现原理(三)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章我们已经初步认识了Be ...

  6. spring源码学习之路---深度分析IOC容器初始化过程(四)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近由于工作和生活,学习耽搁 ...

  7. spring源码学习之路---IOC容器初始化要义之bean定义载入(五)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,时间不多,研究 ...

  8. Spring 源码学习——Aop

    Spring 源码学习--Aop 什么是 AOP 以下是百度百科的解释:AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程通过预编译的方式和运行期动态代理实 ...

  9. Spring源码学习

    Spring源码学习--ClassPathXmlApplicationContext(一) spring源码学习--FileSystemXmlApplicationContext(二) spring源 ...

随机推荐

  1. 改变win7驱动图标

    一.背景 自己做的USB设备是HID设备,注册到设备管理器中就是"HID Compliant device",显得很业余,然后想去改变这个图标和名称,也就有了此篇文章 二.正文 还 ...

  2. JQuery的父、子、兄弟节点查找方法

    jQuery.parent(expr)           //找父元素 jQuery.parents(expr)          //找到所有祖先元素,不限于父元素 jQuery.children ...

  3. 【Win10开发】处理PC上的后退键

    我们知道在win10手机上和平板上都会有后退键,那么PC上该怎么办呢?没关系我们慢慢揭晓. 如果你已经是UWP的忠实用户,那么肯定会见到如下的后退键. 那么我们如何来做出来呢?, 我们首先打开App. ...

  4. monkeyrunner之eclipse中运行monkeyrunner脚本之环境搭建(四)

    monkeyrunner脚本使用Python语法编写,但它实际上是通过Jython来解释执行. Jython是Python的Java实现,它将Python代码解释成Java虚拟机上的字节码并执行,这种 ...

  5. Android百度地图开发(二)地图覆盖物

    上一篇文章写道如何在一个mapview中简单的显示地图:本次学习一下如何在地图上添加一些覆盖物. 1.设置显示交通地图: // 显示交通地图 mapView.setTraffic(true); 2.设 ...

  6. asp.net FileUpload 控件上传文件 以二进制的形式存入数据库并将图片显示出来

    图片上传事件代码如下所示: byte[] binary = upload.FileBytes; StringBuilder sqlStrSb = new StringBuilder(); sqlStr ...

  7. Redis util

    主要是字符串通配符匹配和数字与字符串互转的几个函数. // 通配符模式匹配 int stringmatchlen(const char* p, int plen, const char* s, int ...

  8. 高性能 TCP &amp;amp; UDP 通信框架 HP-Socket v3.2.2 正式公布

    HP-Socket 是一套通用的高性能 TCP/UDP 通信框架,包括服务端组件.client组件和 Agent 组件,广泛适用于各种不同应用场景的 TCP/UDP 通信系统,提供 C/C++.C#. ...

  9. Vijos1057 盖房子(DP经典题)

    之前没有怎么刷过dp的题,所以在此学习了~(感谢walala大神的思路,给了我很大的启发) 也算是自己学习的另一种dp题型吧 先贴上状态转移方程: if(a[i][j]) f[i][j]=min(f[ ...

  10. 【转】地球坐标系 (WGS-84) 到火星坐标系 (GCJ-02) 的转换算法

    // // Copyright (C) 1000 - 9999 Somebody Anonymous // NO WARRANTY OR GUARANTEE // using System; name ...