在看这篇文章之前需要首先理解 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"?>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"> id="userService" class="com.maxfunner.service.impl.UserService" /> id="adminService" class="com.maxfunner.service.impl.AdminService" /> id="UserAdvice" class="com.maxfunner.advice.UserAdvice" /> id="LoginMethodAdvisor" class="com.maxfunner.advisor.StaticLoginMethodAdvisor"> name="advice"> class="com.maxfunner.advice.UserAdvice" /> id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true" p:proxyTargetClass="true" p:interceptorNames="LoginMethodAdvisor" /> id="userServiceProxy" parent="factoryBean" p:target-ref="userService" /> id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />
上面注释上已经写的很清楚了,接下来我们使用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配置文件即可:
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">
id="userService" class="com.maxfunner.service.impl.UserService" />
id="adminService" class="com.maxfunner.service.impl.AdminService" />
id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />
id="LoginMethodAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
name="patterns">
.*\.service\..*\.log.*
name="advice">
class="com.maxfunner.advice.UserAdvice" />
id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true"
p:proxyTargetClass="true"
p:interceptorNames="LoginMethodAdvisor"
/>
id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />
然后我们的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:
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">
id="userService" class="com.maxfunner.service.impl.UserService" />
id="adminService" class="com.maxfunner.service.impl.AdminService" />
id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />
id="LoginMethodAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
name="pointcut">
class="com.maxfunner.advisor.DynamicLoginMethodPointcut" />
name="advice">
class="com.maxfunner.advice.UserAdvice" />
id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true"
p:proxyTargetClass="true"
p:interceptorNames="LoginMethodAdvisor"
/>
id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />
最后我们 跟以后不一样的是 我们在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"); } }
然后就是配置文件:
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">
id="userService" class="com.maxfunner.service.impl.UserService" />
id="adminService" class="com.maxfunner.service.impl.AdminService" />
id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />
id="LoginMethodAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
name="pointcut">
class="org.springframework.aop.support.ControlFlowPointcut">
type="java.lang.Class" value="com.maxfunner.service.impl.TestUserService" />
type="java.lang.String" value="test" />
name="advice">
class="com.maxfunner.advice.UserAdvice" />
id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true"
p:proxyTargetClass="true"
p:interceptorNames="LoginMethodAdvisor"
/>
id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />
然后重点在调用和输入,你会发现正常执行两个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对象:
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">
id="userService" class="com.maxfunner.service.impl.UserService" />
id="adminService" class="com.maxfunner.service.impl.AdminService" />
id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />
id="composablePointcutService" class="com.maxfunner.advisor.ComposablePointcutService" />
id="LoginMethodAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
name="pointcut" value="#{composablePointcutService.composablePointcut}" />
name="advice">
class="com.maxfunner.advice.UserAdvice" />
id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true"
p:proxyTargetClass="true"
p:interceptorNames="LoginMethodAdvisor"
/>
id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />
最后调用然后输出结果:
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 { ThreadLocalthreadLocal = new ThreadLocal (); 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
/** * Created by yanzhichao on 27/04/2017. */ public class HiddenPasswordAdvisor extends DefaultIntroductionAdvisor implements ClassFilter { private ListtargetClasses = new ArrayList (); public List getTargetClasses() { return targetClasses; } public void setTargetClasses(List 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; } }
上面的构造函数尤其重要,注意看上面各个构造函数的解释,然后我们在看看配置文件:
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">
id="userService" class="com.maxfunner.service.impl.UserService" />
id="adminService" class="com.maxfunner.service.impl.AdminService" />
id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />
id="hiddenPasswordAdvice" class="com.maxfunner.advice.HiddenPasswordAdvice" />
id="LoginIntroductionAdvisor" class="com.maxfunner.advisor.HiddenPasswordAdvisor">
ref="hiddenPasswordAdvice" />
type="java.lang.Class" value="com.maxfunner.service.HiddenPasswordController" />
name="targetClasses">
com.maxfunner.service.impl.AdminService
id="factoryBean" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true"
p:proxyTargetClass="true"
p:interceptorNames="LoginIntroductionAdvisor"
/>
id="userServiceProxy" parent="factoryBean" p:target-ref="userService" />
id="adminServiceProxy" parent="factoryBean" p:target-ref="adminService" />
然后开始调用看结果:
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开头都自动创建代理:
id="userService" class="com.maxfunner.service.impl.UserService" /> id="adminService" class="com.maxfunner.service.impl.AdminService" /> id="UserAdvice" class="com.maxfunner.advice.UserAdvice" /> id="staticLoginMethodAdvisor" class="com.maxfunner.advisor.StaticLoginMethodAdvisor"> name="advice" ref="UserAdvice" /> 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 !!!
没毛病,继续~ 最后一个DefaultAdvisorAutoProxyCreator,只要看Advisor中定义的ClassFilter,比上面更加简单的配置(由于我们使用的StaticLoginMethodAdvisor 已经定义了classFilter了 只所以只自定创建userService的代理):
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">
id="userService" class="com.maxfunner.service.impl.UserService" />
id="adminService" class="com.maxfunner.service.impl.AdminService" />
id="UserAdvice" class="com.maxfunner.advice.UserAdvice" />
id="staticLoginMethodAdvisor" class="com.maxfunner.advisor.StaticLoginMethodAdvisor">
name="advice" ref="UserAdvice" />
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
测试一下:
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 !!!