SpringAOP之切点和切面的深入理解

在看这篇文章之前需要首先理解 Spring AOP 增强的知识,如果你想先了解增强的知识可以移步到 我另外一篇博客《Spring动态代理之详细DEBUG日志模式》里面有关于增强的知识 如果除了增强还有 关于CGlib 和 Proxy 代理的知识不太理解可以再移步到 《动态代理之详细DEBUG日志模式》好了,目前是建基于你都明白 动态代理 和 Spring 增强的基础上进行对Spring 中的 切点(pointcut) 和 切面(advisor)的介绍


如果只有advice 我们一般情况下将一个目标类中的所有方法都进行代理,但是我们希望可以定位的某些类的特定方法,这个时候我们就需要使用到切点(pointcut),然而切面就是切点和增强(advice)的结合。


首先我们通过一个UserService的业务去理解切面和切点的知识,一个有两个方法一个是注册 一个是登录,我们会对这两个方法进行织入演示,所以首先提出这个Service的代码,非常简单的代码:

/**
 * Created by yanzhichao on 27/04/2017.
 */
public class UserService {

    public void login(String username,String password){
        System.out.println("Login : " + username + ", pwd : " + password);
    }

    public void register(String username,String password){
        System.out.println("register : " + username + " , pwd " + password);
    }

}


首先我们先说StaticMethodMatcherPointcutAdvice(这个类名长到我想吐)的使用,主要我们是希望通过这个静态方法匹配切面(原谅我直译)对UserService中的login进行织入。而register不织入:

/**
 * Created by yanzhichao on 27/04/2017.
 */
public class StaticLoginMethodAdvisor extends StaticMethodMatcherPointcutAdvisor {

    //匹配login方法进行织入
    public boolean matches(Method method, Class<?> aClass) {
        return "login".equals(method.getName());
    }

    //定义对什么类有效
    @Override
    public ClassFilter getClassFilter() {
        return new ClassFilter() {
            public boolean matches(Class<?> aClass) {
                //定义对UserService类或者它的派生类有效
                return UserService.class.isAssignableFrom(aClass);
            }
        };
    }
}

然后定义advice 增强,就在方法执行的时候打印一下执行的什么方法的LOG

/**
 * Created by yanzhichao on 27/04/2017.
 */
public class UserAdvice implements MethodBeforeAdvice {

    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println(">>>>>>>>>>" + method.getName() + " invoked !!!");
    }
}

由于我想测试一下advisor中的getClassFilter方法是否有用,所以我再定义了一个AdminService

/**
 * Created by yanzhichao on 27/04/2017.
 */
public class AdminService {

    public void login(String username,String password){
        System.out.println("Admin Login : " + username + ", pwd : " + password);
    }

    public void register(String username,String password){
        System.out.println("Admin register : " + username + " , pwd " + password);
    }

}

同样有login方法,所以再执行两个Service的login方法的时候可以看看输出结果。

最后配置文件的代码如下:

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


    <!-- 未代理的类 -->
    <bean id="userService" class="com.maxfunner.service.impl.UserService" />
    <bean id="adminService" class="com.maxfunner.service.impl.AdminService" />

    <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />

    <bean id="LoginMethodAdvisor" class="com.maxfunner.advisor.StaticLoginMethodAdvisor">
        <property name="advice">
            <bean class="com.maxfunner.advice.UserAdvice" />
        </property>
    </bean>

    <!-- 注意使用了abstract主要是作为一个配置模板去使用,由于我们没有接口实现方式去写Service所以这里使用proxyTargetClass -->
    <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"  abstract="true"
            p:proxyTargetClass="true"
            p:interceptorNames="LoginMethodAdvisor"
    />

    <!-- 定义被proxy bean -->
    <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
    <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />

</beans>

上面注释上已经写的很清楚了,接下来我们使用main方法进行测试一下 然后再贴出一下输出的Log

public static void main(String[] args){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) context.getBean("userServiceProxy");
    AdminService adminService = (AdminService) context.getBean("adminServiceProxy");
    userService.login("TONY","PWD");
    userService.register("TONY","PWD");

    adminService.login("TONY","PWD");
    adminService.register("TONY","PWD");
}

输出可以看到只有userService.login才有被成功增强:

>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD


但是你会问 既然我们写定义proxy bean的时候就已经定义好那个类进行代理了为什么还有在advisor定义classFilter的方法呢,因为是为了做自动代理,这个留最后说~  接下来我们来做一个更加简单的静态方法切面,通过正则表达式进行限定方法名匹配,这里用到RegexpMethodPointcutAdvisor类,什么都不用改变就该spring配置文件即可:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!-- 未代理的类 -->
    <bean id="userService" class="com.maxfunner.service.impl.UserService" />
    <bean id="adminService" class="com.maxfunner.service.impl.AdminService" />

    <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />


    <!-- pattern是正则表达式,现在我们只针对service包下的所有类的log开头的方法进行织入 -->
    <bean id="LoginMethodAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="patterns">
            <list>
                <value>.*\.service\..*\.log.*</value>
            </list>
        </property>
        <property name="advice">
            <bean class="com.maxfunner.advice.UserAdvice" />
        </property>
    </bean>

    <!-- 注意使用了abstract主要是作为一个配置模板去使用,由于我们没有接口实现方式去写Service所以这里使用proxyTargetClass -->
    <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"  abstract="true"
            p:proxyTargetClass="true"
            p:interceptorNames="LoginMethodAdvisor"
    />

    <!-- 定义被proxy bean -->
    <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
    <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />

</beans>

然后我们的main方法不用改变然后查看输出结果,可以发现按照我们的正则表达式的意思,已经成功织入了service包中的所有方法名为log开头的方法:

>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
>>>>>>>>>>login invoked !!!
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD


动态切面,根据方法中的不同参数进行选择性织入,这个是已经非常耗资源的织入方法,后面打印完所有的LOG 我们就清楚为什么是耗资源的织入方法了,首先我们用到了DynamicMethodMatcherPointcut,我们会在这个方法中限定类,限定方法,限定方法的参数进行织入:

/**
 * Created by yanzhichao on 27/04/2017.
 */
public class DynamicLoginMethodPointcut extends DynamicMethodMatcherPointcut {

    @Override
    public ClassFilter getClassFilter() {

        return new ClassFilter() {
            public boolean matches(Class<?> aClass) {
                System.err.println("getClassFilter invoked !!! aClass : " + aClass.getName());
                return UserService.class.isAssignableFrom(aClass);
            }
        };
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        System.err.println("matches(static) invoked !!! method : " + method.getName());
        return "login".equals(method.getName());
    }

    public boolean matches(Method method, Class<?> aClass, Object... objects) {
        //如果用户名为TONY 就织入
        System.err.println("matches(dynamic) invoked !!! objects[0] : " + objects[0]);
        return "TONY".equals(objects[0]);
    }
}

注意 一会我们会打印一堆日志,你会发现一个问题,Spring采用了一种非常好的机制,静态检查第一次调用的时候发现匹配和不匹配后 之后就不对这些连接点跑静态检查的方法了,而动态检查matches的就会每次都调用都会跑,所以我们必须在静态检查的方法中先过滤一批,静态检查非常高效,而动态检查每次调用都会检查所以只检查参数部分~

然后我们在配置文件使用了DefaultPointcutAdvisor 引用我们实现的 DynamicLoginMethodPointcut:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!-- 未代理的类 -->
    <bean id="userService" class="com.maxfunner.service.impl.UserService" />
    <bean id="adminService" class="com.maxfunner.service.impl.AdminService" />

    <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />


    <!-- DefaultPointcutAdvisor是一个默认的advisor然后使用它来应用我们的DynamicLoginMethodPointcut -->
    <bean id="LoginMethodAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut">
            <bean class="com.maxfunner.advisor.DynamicLoginMethodPointcut" />
        </property>
        <property name="advice">
            <bean class="com.maxfunner.advice.UserAdvice" />
        </property>
    </bean>

    <!-- 注意使用了abstract主要是作为一个配置模板去使用,由于我们没有接口实现方式去写Service所以这里使用proxyTargetClass -->
    <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"  abstract="true"
            p:proxyTargetClass="true"
            p:interceptorNames="LoginMethodAdvisor"
    />

    <!-- 定义被proxy bean -->
    <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
    <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />

</beans>

最后我们 跟以后不一样的是 我们在main方法连续调用一样的方法两次:

public static void main(String[] args){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) context.getBean("userServiceProxy");
    AdminService adminService = (AdminService) context.getBean("adminServiceProxy");
    userService.login("TONY","PWD");
    userService.register("TONY","PWD");

    adminService.login("TONY","PWD");
    adminService.register("TONY","PWD");

    userService.login("TONY","PWD");
    userService.register("TONY","PWD");

    adminService.login("TONY","PWD");
    adminService.register("TONY","PWD");
}

最后运行的时候会发现 我们刚刚所说的检查机制,然而跟我们预计的一样 只织入UserService中的login方法 而且只织入 username参数为TONY的情况:

getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : register
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : login
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : toString
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : clone
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
>>>>>>>>>>login invoked !!!
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : login
matches(dynamic) invoked !!! objects[0] : TONY
Login : TONY, pwd : PWD
register : TONY , pwd PWD
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : register
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
matches(dynamic) invoked !!! objects[0] : TONY


还有一种比较少用的pointcut 中文叫 流程切面 (ControlFlowPointcut) 我也不知道怎么解释他的主要功能,还是直接看代码吧,我们会定义一个TestUserService的类,里面有个Test的方法,会跑UserService中的Login 和 Register方法,然后我希望是Test方法中跑中Login和Register被织入,但是在其他地方跑Login 和 Register不会被织入,这个我完全想不到使用场景,可能我入世未深,但是我还是把他写下来 希望以后用到的时候可以看看这份东西,说实话当我这段时间复习AOP的时候,我完全对这个切面完全不记得了,所以写下希望下次用的时候可以让我回忆道有这样一个切面~

首先定义TestUserService 类

/**
 * Created by yanzhichao on 27/04/2017.
 */
public class TestUserService {

    public void test(UserService service){
        service.login("TONY","PWD");
        service.register("TONY","PWD");
    }

    public void test(AdminService service){
        service.login("TONY","PWD");
        service.register("TONY","PWD");
    }

}

然后就是配置文件:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!-- 未代理的类 -->
    <bean id="userService" class="com.maxfunner.service.impl.UserService" />
    <bean id="adminService" class="com.maxfunner.service.impl.AdminService" />

    <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />


    <!-- DefaultPointcutAdvisor是一个默认的advisor然后使用它来应用我们的DynamicLoginMethodPointcut -->
    <bean id="LoginMethodAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut">
            <!-- 使用了ControlFlowPoint 在它的构造函数中 第一个参数是TestUserSerivce的类 然后就是织入的方法 -->
            <bean class="org.springframework.aop.support.ControlFlowPointcut">
                <constructor-arg type="java.lang.Class" value="com.maxfunner.service.impl.TestUserService" />
                <constructor-arg type="java.lang.String" value="test" />
            </bean>
        </property>
        <property name="advice">
            <bean class="com.maxfunner.advice.UserAdvice" />
        </property>
    </bean>

    <!-- 注意使用了abstract主要是作为一个配置模板去使用,由于我们没有接口实现方式去写Service所以这里使用proxyTargetClass -->
    <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"  abstract="true"
            p:proxyTargetClass="true"
            p:interceptorNames="LoginMethodAdvisor"
    />

    <!-- 定义被proxy bean -->
    <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
    <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />

</beans>

然后重点在调用和输入,你会发现正常执行两个service 的时候不会被织入,而是在TestUserService中调用test方法的时候 adminService 和 userService 中的两个方法都会被织入:

public static void main(String[] args){

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) context.getBean("userServiceProxy");
    AdminService adminService = (AdminService) context.getBean("adminServiceProxy");
    userService.login("TONY","PWD");
    userService.register("TONY","PWD");

    adminService.login("TONY","PWD");
    adminService.register("TONY","PWD");

    TestUserService testUserService = new TestUserService();
    testUserService.test(userService);
    testUserService.test(adminService);

}

最后看看输出可以看到在TestUserService中test方法执行的方法都有织入:

Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
>>>>>>>>>>register invoked !!!
register : TONY , pwd PWD
>>>>>>>>>>login invoked !!!
Admin Login : TONY, pwd : PWD
>>>>>>>>>>register invoked !!!
Admin register : TONY , pwd PWD


最后我们想是否能够指定test方法中执行那个类中的那个方法会才会被织入呢,答案是肯定的。这时候我们使用复合切点切面ComposablePointcut,因为这个切面有两种特性第一种是当两个切面,一个指定类的,第二个切面指定方法的,可以取两者的交际,就是说 又要是这个类的 还要是这个 方法的 连接点才会被织入, 另外一种是 有两个切面,一个指定类的,第二个切面指定方法的,取两者的并集,就是说如果是指定的类的所有方法都织入,还有某一个没有指定的类中的被指定的方法名的方法都会被织入。

当然 ComposablePointcut 本质上它也是切面,所以在构造方法上也提供了基础切面

ComposablePointcut() 没有定义基础的切面

ComposablePointcut(ClassFilter classFilter) 类过滤

ComposablePointcut(MethodMatcher methodMatcher) 方法过过滤

ComposablePointcut(ClassFilter classFilter,MethodMatcher methodMatcher) 基础复合过滤

交集复合的方法是:

ComposablePointcut intersection(ClassFilter classFilter) 

ComposablePointcut intersection(MethodMatcher methodMatcher) 

ComposablePointcut intersection(Pointcut pointcut) 

并集复合的方法是:

ComposablePointcut union(ClassFilter classFilter) 

ComposablePointcut union(MethodMatcher methodMatcher) 


你会发现一个坑爹的现象是没有提供一个参数为Pointcut的union方法,可以使用org.springframework.aop.support.Pointcuts工具类,有两个静态方法可以返回一个Pointcut这个Pointcut 正是ComposablePointcut (绝对坑爹)

Pointcut intersection(Pointcut a,Pointcut b);

Pointcut union(Pointcut a,Pointcut b);


不说别的实现我们的上面说的情况“最后我们想是否能够指定test方法中执行那个类中的那个方法会才会被织入呢,答案是肯定的!”看看怎么做~ 

首先我们创建一个工具类去把多个切面组装成一个ComposablePointcut ,然而我们这次使用了之前实现的两个pointcut 第一个是刚刚实现的ControlFlowPointcut 然后通过交集复合的方式 在复合一个 DynamicLoginMethodPointcut 这样就可以实现 在UserService 中的Login方法 实参为TONY 在 TestUserService中的test方法中调用的织入,非常复杂的关系,我觉得看输出会比较直接点,而且我真的想不出使用场景:

/**
 * Created by yanzhichao on 27/04/2017.
 */
public class ComposablePointcutService {

    public Pointcut getComposablePointcut(){
        ComposablePointcut composablePointcut = new ComposablePointcut();

        //ControlFlowPointcut实现TestUserService.test中的方法织入
        Pointcut p1 = new ControlFlowPointcut(TestUserService.class,"test");

        //我们一开始实现的动态切面,只有在UserService login方法中的参数为TONY 才会被织入
        Pointcut p2 = new DynamicLoginMethodPointcut();

        return composablePointcut.intersection(p1).intersection(p2);
    }
}

然后我们通过在Spring容器注册一个ComposablePointcutService,然后继续使用DefaultPointcutAdivsor ,然后这次我们装配的pointcut不是ControlFlowPointcut而是通过ComposablePointcutService中getComposablePointcut方法获得的复合pointcut对象:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!-- 未代理的类 -->
    <bean id="userService" class="com.maxfunner.service.impl.UserService" />
    <bean id="adminService" class="com.maxfunner.service.impl.AdminService" />

    <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />


    <!-- 刚刚实现的一个复合切面的工具类 有个getComposablePointcut 获得切面 -->
    <bean id="composablePointcutService" class="com.maxfunner.advisor.ComposablePointcutService" />


    <!-- DefaultPointcutAdvisor是一个默认的advisor然后使用它来应用我们的composablePointcutService的复合切面 -->
    <bean id="LoginMethodAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="pointcut" value="#{composablePointcutService.composablePointcut}" />
        <property name="advice">
            <bean class="com.maxfunner.advice.UserAdvice" />
        </property>
    </bean>

    <!-- 注意使用了abstract主要是作为一个配置模板去使用,由于我们没有接口实现方式去写Service所以这里使用proxyTargetClass -->
    <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"  abstract="true"
            p:proxyTargetClass="true"
            p:interceptorNames="LoginMethodAdvisor"
    />

    <!-- 定义被proxy bean -->
    <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
    <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />

</beans>

最后调用然后输出结果:

public static void main(String[] args){

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) context.getBean("userServiceProxy");
    AdminService adminService = (AdminService) context.getBean("adminServiceProxy");
    userService.login("TONY","PWD");
    userService.register("TONY","PWD");

    adminService.login("TONY","PWD");
    adminService.register("TONY","PWD");

    TestUserService testUserService = new TestUserService();
    testUserService.test(userService);
    testUserService.test(adminService);

}

输出结果如下:

getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : register
matches(static) invoked !!! method : register
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : login
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : toString
matches(static) invoked !!! method : toString
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : clone
matches(static) invoked !!! method : clone
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
matches(static) invoked !!! method : login
matches(dynamic) invoked !!! objects[0] : TONY
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.UserService
Login : TONY, pwd : PWD
matches(static) invoked !!! method : register
register : TONY , pwd PWD
matches(static) invoked !!! method : register
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
getClassFilter invoked !!! aClass : com.maxfunner.service.impl.AdminService
matches(dynamic) invoked !!! objects[0] : TONY
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD
>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD


最后一个切面是引介切面(IntroductionAdvisor)  由于引介切面只能代理类级别的,所以无法实现引介切面定位代理到某一个方法,使用IntroductionAdvisor 的效果其实和IntroductionAdvice差不多,但是最大的好处是他提供了ClassFilter 也就是说可以实现自动代理,当然我们可以使用我上一篇博客的引介advice,但是由于业务不太一样,以免混淆写一些和业务相关的吧,首先我们会对类进行一个日志的输出,我们定义一个接口,可以让代理对象自己去控制是否打印当前登录的密码,先定义一个接口:

/**
 * Created by yanzhichao on 27/04/2017.
 */
public interface HiddenPasswordController {

    public void hidden(boolean isHidden);

}

然后我们通过DelegatingIntroductionInterceptor实现一个advice:

/**
 * Created by yanzhichao on 27/04/2017.
 */
public class HiddenPasswordAdvice extends DelegatingIntroductionInterceptor implements HiddenPasswordController {

    ThreadLocal<Boolean> threadLocal = new ThreadLocal<Boolean>();

    public void hidden(boolean isHidden) {
        threadLocal.set(isHidden);
    }

    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {

        System.out.print( "advice ->>>>>> " + mi.getMethod().getName() + " method  Username : "
                + mi.getArguments()[0] + " Password : ");
        if(threadLocal.get() == null || threadLocal.get()){
            System.out.println("*********");
        }else{
            System.out.println(mi.getArguments()[1]);
        }
        return super.invoke(mi);
    }
}

然后我们不直接使用DefaultIntroductionAdvisor,而是自己继承DefaultIntroductionAdvisor 然后重写 getClassFilter 因为DefaultIntroductionAdvisor的getClassFilter永远返回true,所以我这里会过滤屌UserService 只有 AdminService 才会被增强,而且我这里用一个List<Class>的熟悉去定义,意味着在配置文件可以对ClassFilter对那些类产生增强进行定义:

/**
 * Created by yanzhichao on 27/04/2017.
 */
public class HiddenPasswordAdvisor extends DefaultIntroductionAdvisor implements ClassFilter {

    private List<Class> targetClasses = new ArrayList<Class>();

    public List<Class> getTargetClasses() {
        return targetClasses;
    }

    public void setTargetClasses(List<Class> targetClasses) {
        this.targetClasses = targetClasses;
    }

    //直接调用我实现的HiddenPasswordAdvice,并指明目标类的实现接口HiddenPasswordController
    public HiddenPasswordAdvisor() {
        super(new HiddenPasswordAdvice(), HiddenPasswordController.class);
    }

    //传入一个引介增强,继承于DefaultIntroductionAdvisor
    public HiddenPasswordAdvisor(Advice advice) {
        super(advice);
    }

    //第二个参数主要描述主要实现目标类实现的新接口,继承于DefaultIntroductionAdvisor
    public HiddenPasswordAdvisor(Advice advice, IntroductionInfo introductionInfo) {
        super(advice, introductionInfo);
    }

    //第二个参数直接指定新增的接口,继承于DefaultIntroductionAdvisor
    public HiddenPasswordAdvisor(DynamicIntroductionAdvice advice, Class<?> intf) {
        super(advice, intf);
    }


    //重写了getClassFilter 由于DefaultIntroductionAdvisor没有定义过滤那些Class
    @Override
    public ClassFilter getClassFilter() {
        return this;
    }

    //如果想不过滤任何类 可以直接使用DefaultIntroductionAdvisor
    @Override
    public boolean matches(Class<?> clazz) {
        for (Class clz: targetClasses) {
            if(clz.isAssignableFrom(clazz))
                return true;
        }
        return false;
    }
}

上面的构造函数尤其重要,注意看上面各个构造函数的解释,然后我们在看看配置文件:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!-- 未代理的类 -->
    <bean id="userService" class="com.maxfunner.service.impl.UserService" />
    <bean id="adminService" class="com.maxfunner.service.impl.AdminService" />

    <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />


    <!-- 刚刚实现的一个复合切面的工具类 有个getComposablePointcut 获得切面 -->
    <bean id="hiddenPasswordAdvice" class="com.maxfunner.advice.HiddenPasswordAdvice" />


    <!-- 使用我们自定义的HiddenPasswordAdvisor 继承自 DefaultIntroductionAdvisor  -->
    <!-- 虽然我们定义了一个默认的构造方法,但是为了演示我这里也使用DefaultIntroductionAdvisor原来的构造方法  -->
    <bean id="LoginIntroductionAdvisor" class="com.maxfunner.advisor.HiddenPasswordAdvisor">
        <constructor-arg ref="hiddenPasswordAdvice" />
        <constructor-arg type="java.lang.Class" value="com.maxfunner.service.HiddenPasswordController" />
        <property name="targetClasses">
            <list>
                <value>com.maxfunner.service.impl.AdminService</value>
            </list>
        </property>
    </bean>

    <!-- 注意使用了abstract主要是作为一个配置模板去使用,由于我们没有接口实现方式去写Service所以这里使用proxyTargetClass -->
    <bean id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"  abstract="true"
            p:proxyTargetClass="true"
            p:interceptorNames="LoginIntroductionAdvisor"
    />

    <!-- 定义被proxy bean -->
    <bean id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
    <bean id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />

</beans>

然后开始调用看结果:

  public static void main(String[] args){

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userServiceProxy");
        AdminService adminService = (AdminService) context.getBean("adminServiceProxy");

        userService.login("TONY","PWD");
        userService.register("TONY","PWD");

        adminService.login("TONY","PWD");
        adminService.register("TONY","PWD");

          //由于我们重写了classfilter所以我们将UserService进行类型转换的时候就会报错
//        HiddenPasswordController controller = (HiddenPasswordController) userService;
//        controller.hidden(false);

        HiddenPasswordController controller = (HiddenPasswordController) adminService;
        controller.hidden(false);

        userService.login("TONY","PWD");
        userService.register("TONY","PWD");

        adminService.login("TONY","PWD");
        adminService.register("TONY","PWD");

    }

可以看到如果没有在getClassFilter中定义的类进行类型转换会出错的,以下是输出:

Login : TONY, pwd : PWD
register : TONY , pwd PWD
advice ->>>>>> login method  Username : TONY Password : *********
Admin Login : TONY, pwd : PWD
advice ->>>>>> register method  Username : TONY Password : *********
Admin register : TONY , pwd PWD
advice ->>>>>> hidden method  Username : false Password : *********
Login : TONY, pwd : PWD
register : TONY , pwd PWD
advice ->>>>>> login method  Username : TONY Password : PWD
Admin Login : TONY, pwd : PWD
advice ->>>>>> register method  Username : TONY Password : PWD
Admin register : TONY , pwd PWD


然后最后我们来看看自动代理,有两种方案实现自动代理,第一种是通过BeanNameAutoProxyCreator,另外一种是DefaultAdvisorAutoProxyCreator 

其实都非常简单BeanNameAutoProxyCreator 就是通过名字进行自动代理,而DefaultAdvisorAutoProxyCreator是通过advisor中的classFilter进行自动代理,没有什么非常多的概念,然后我们现在先使用一下DefaultAdvisorAutoProxyCreator,这次我们使用最为原始的StaticMethodMatcherPointcutAdvice,然后继续使用回我们一开头实现的StaticLoginMethodAdvisor 忘记了上拖上去看看,然后把bean名称为p开头都自动创建代理:

<!-- 自动被代理 -->
<bean id="userService" class="com.maxfunner.service.impl.UserService" />
<bean id="adminService" class="com.maxfunner.service.impl.AdminService" />

<bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />

<bean id="staticLoginMethodAdvisor" class="com.maxfunner.advisor.StaticLoginMethodAdvisor">
    <property name="advice" ref="UserAdvice" />
</bean>

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
    p:beanNames="userService"    p:interceptorNames="staticLoginMethodAdvisor" p:optimize="true"
/>

其中beanNames熟悉支持通配符*,同时如果想匹配多个用,号进行分割。

测试一下:

public static void main(String[] args){

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) context.getBean("userService");
    AdminService adminService = (AdminService) context.getBean("adminService");
    userService.login("TONY","PWD");
    userService.register("TONY","PWD");

    adminService.login("TONY","PWD");
    adminService.register("TONY","PWD");

}
>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD


没毛病,继续~ 最后一个DefaultAdvisorAutoProxyCreator,只要看Advisor中定义的ClassFilter,比上面更加简单的配置(由于我们使用的StaticLoginMethodAdvisor 已经定义了classFilter了 只所以只自定创建userService的代理):

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">


    <!-- 自动被代理 -->
    <bean id="userService" class="com.maxfunner.service.impl.UserService" />
    <bean id="adminService" class="com.maxfunner.service.impl.AdminService" />

    <bean id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />

    <bean id="staticLoginMethodAdvisor" class="com.maxfunner.advisor.StaticLoginMethodAdvisor">
        <property name="advice" ref="UserAdvice" />
    </bean>

    <!-- 就一行没有什么配置,简单到不敢相信 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />


</beans>

测试一下:

public static void main(String[] args){

    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService) context.getBean("userService");
    AdminService adminService = (AdminService) context.getBean("adminService");
    userService.login("TONY","PWD");
    userService.register("TONY","PWD");

    adminService.login("TONY","PWD");
    adminService.register("TONY","PWD");

}
>>>>>>>>>>login invoked !!!
Login : TONY, pwd : PWD
register : TONY , pwd PWD
Admin Login : TONY, pwd : PWD
Admin register : TONY , pwd PWD






发布了53 篇原创文章 · 获赞 65 · 访问量 16万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章