1、情景假设
众所周知运动员在剧烈运
动之前,都会进行一些准备活动,避免受伤。下面我们先定义一个不专业的运动员并没有进行准备活动,然后我们会在增强逻辑中织入准备活动。
除了锻炼之外,运动员还有接受媒体采访,采访之前运动员应该礼貌的表示“感谢媒体采访”。采访结束后,说一声“再见”。这个“感谢”和“再见”分别对应前置增强和后置增强,我们也放在横切逻辑中然后织入。
2、代码实现
2.1 首先看一下将增强织入到类的所有方法中
(1)直接使用java代码,不使用xml文件的方式:
Player.java
public interface Player {
public void exercise(String place);
public void acceptInterview(String interviewer);
}
NaivePlayer.java
public class NaivePlayer implements Player {
public void exercise(String place) {
System.out.println("在"+place+",开始训练,剧烈运动...");
}
public void acceptInterview(String interviewer) {
// TODO Auto-generated method stub
System.out.println("接受记者采访...");
}
}
InterviewBeforeAdvice.java
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
/*
*BeforeAdvice是前置增强的接口,MethodBeforeAdvice是其子接口。spring目前只支持方法调用的前置增强,
*但是spring后来可能会陆续加入其他类型的前置增强,这就是BeforeAdvice接口存在的意义
*
***/
public class InterviewBeforeAdvice implements MethodBeforeAdvice {
//method为业务类方法,arg1为业务方法的入参,target是业务对象实例
//可以发现我们这里并没有指定类的某个特定方法
public void before(Method method, Object[] arg1, Object target)
throws Throwable {
//限制了这个method必须至少有一个参数,否则下面的代码将会出错
String interviewer=(String)arg1[0];
System.out.println("感谢 "+interviewer+" 采访...");
}
}
LearnBeforeAdvice.java
<pre name="code" class="java">public class LearnBeforeAdvice {
public static void main(String[] args) {
Player player=new NaivePlayer();
BeforeAdvice advice=new InterviewBeforeAdvice();
//代理工厂,负责将增强advice织入目标类的目标方法中去
ProxyFactory factory=new ProxyFactory();
//设置代理对象
factory.setTarget(player);
//添加增强,可以多次执行addAdvice函数,织入多个增强,执行顺序和加入顺序相同
factory.addAdvice(advice);
//生成代理实例
Player playerProxy=(Player)factory.getProxy();
System.out.println(playerProxy.getClass().getName());
playerProxy.acceptInterview("LiLei");
playerProxy.exercise("操场");
}
}
运行结果:
<pre name="code" class="java">pkgOne.NaivePlayer$$EnhancerByCGLIB$$95618eeb
感谢 LiLei 采访...
接受记者采访...
感谢 操场 采访...
在操场,开始训练,剧烈运动...
/*
* 指定接口,从而使用JDK代理
*/
Player player=new NaivePlayer();
BeforeAdvice advice=new InterviewBeforeAdvice();
//代理工厂,负责将增强advice织入目标类的目标方法中去
ProxyFactory factory=new ProxyFactory();
//设置代理对象
factory.setTarget(player);
factory.setInterfaces(player.getClass().getInterfaces());
//添加增强,可以多次执行addAdvice函数,织入多个增强,执行顺序和加入顺序相同
factory.addAdvice(advice);
//生成代理实例
Player playerProxy=(Player)factory.getProxy();
System.out.println(playerProxy.getClass().getName());
playerProxy.acceptInterview("LiLei");
playerProxy.exercise("操场");
运行结果:
com.sun.proxy.$Proxy0
感谢 LiLei 采访...
接受记者采访...
感谢 操场 采访...
在操场,开始训练,剧烈运动...
解释: <2>从运行结果我们可以看出,执行所有的方法前,都会织入advice增强。也就是说,这个增强被织入到了类的所有方法中,这并不是我们想要的,不过现在先不用管这个,后面我们会解决这个问题。
(2)虽然使用ProxyFactory比直接使用CGLIB和JDK代理要省事很多,但是,这个并不符合spring的风格,下面我们会以xml文件的形式配置代理,织入增强。
knight.xml <bean id="thanks" class="pkgOne.InterviewBeforeAdvice"/>
<bean id="player" class="pkgOne.NaivePlayer"/>
<bean id="playerProxy" class=" org.springframework.aop.framework.ProxyFactoryBean"
p:proxyInterfaces="pkgOne.Player"
p:interceptorNames="thanks"
p:target-ref="player"
/>
LearnBeforeAdvice2.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class LearnBeforeAdvice2 {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("knight.xml");
Player playerProxy=(Player)context.getBean("playerProxy");
System.out.println(playerProxy.getClass().getName());
playerProxy.acceptInterview("Lilei");
playerProxy.exercise("操场");
}
}
运行结果:
com.sun.proxy.$Proxy4
感谢 Lilei 采访...
接受记者采访...
感谢 操场 采访...
在操场,开始训练,剧烈运动...
解释:ProxyFactoryBean是FactoryBean接口的实现类,它负责为其他bean创建代理实例,它内部使用ProxyFactory来完成这一个工作。
(3)后置增强(业务方法执行结束后执行):
我们使用AfterReturningAdvice来定义后置增强的逻辑:
ByeAfterAdvice.java
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class ByeAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
// TODO Auto-generated method stub
System.out.println("再见...");
}
}
其他的都和前置增强一样,这里不再重复。
(4)环绕增强(业务方法调用前后织入横切逻辑):使用aopalliance定义的MethodInterceptor作为环绕增强的接口
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class RoundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// TODO Auto-generated method stub
//获得目标方法入参
Object[] args=invocation.getArguments();
String interviewer=(String) args[0];
//织入前置逻辑
System.out.println("感谢"+interviewer+"的采访...");
//通过反射调用目标方法
Object object=invocation.proceed();
//织入后置逻辑
System.out.println("再见...");
return object;
}
}
2.2 创建切面,即将增强advice有选择的织入到业务类的某些特定方法中
(1)还是来先看一下不使用xml的形式:
Player2.java
public class Player2 {
public void exercise(String place){
System.out.println("在"+place+",进行锻炼");
}
public void acceptInterview(String interviewer){
System.out.println("接受"+interviewer+"的采访");
}
}
InterviewBeforeAdvice.java
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
/*
*BeforeAdvice是前置增强的接口,MethodBeforeAdvice是其子接口。spring目前只支持方法调用的前置增强,
*但是spring后来可能会陆续加入其他类型的前置增强,这就是BeforeAdvice接口存在的意义
*
***/
public class InterviewBeforeAdvice implements MethodBeforeAdvice {
//method为业务类方法,arg1为业务方法的入参,target是业务对象实例
public void before(Method method, Object[] arg1, Object target)
throws Throwable {
//输出切点
System.out.println(target.getClass().getName()+"."+method.getName());
String interviewer=(String)arg1[0];
System.out.println("感谢 "+interviewer+" 采访...");
}
}
InterviewAdvisor.java
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
//StaticMethodMatcherPointcutAdvisor默认匹配所有类
public class InterviewAdvisor extends StaticMethodMatcherPointcutAdvisor {
//定义切点 方法的匹配规则
public boolean matches(Method arg0, Class<?> arg1) {
// TODO Auto-generated method stub
return "acceptInterview".equals(arg0.getName());
}
//定义切点 类的匹配规则
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class<?> clazz) {
// Player2类或者其子类
return Player2.class.isAssignableFrom(clazz);
}
};
}
}
LearnBeforeAdvice.java
import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
public class LearnBeforeAdvice {
public static void main(String[] args) {
Player2 player2=new Player2();
//代理工厂,负责将增强advice织入目标类的目标方法中去
ProxyFactory factory=new ProxyFactory();
//设置代理对象
factory.setTarget(player2);
//定义切面,为切面织入增强
BeforeAdvice advice=new InterviewBeforeAdvice();
InterviewAdvisor advisor=new InterviewAdvisor();
advisor.setAdvice(advice);
factory.addAdvisor(advisor);
//生成代理实例
Player2 playerProxy=(Player2)factory.getProxy();
System.out.println(playerProxy.getClass().getName());
playerProxy.acceptInterview("LiLei");
System.out.println("---------------");
playerProxy.exercise("操场");
}
}
运行结果:
study.hello.Player2$$EnhancerByCGLIB$$82ca0a79
study.hello.Player2.acceptInterview
感谢 LiLei 采访...
接受LiLei的采访
---------------
在操场,进行锻炼
解释:可以看到,增强被织入到了特定类的特定方法上。
(2)再来看一下更“spring”的方式:
knight.xml
<bean id="player2" class="study.hello.Player2"/>
<bean id="advice" class="study.hello.InterviewBeforeAdvice"/>
<bean id="advisor" class="study.hello.InterviewAdvisor"
p:advice-ref="advice"
/>
<bean id="playerProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="advisor"
p:proxyTargetClass="true"
p:target-ref="player2"
/>
测试代码:
ApplicationContext context=new ClassPathXmlApplicationContext("knight.xml");
Player2 playerProxy=(Player2)context.getBean("playerProxy");
playerProxy.acceptInterview("Lilei");
System.out.println("---------------");
playerProxy.exercise("操场");
运行结果:
study.hello.Player2.acceptInterview
感谢 Lilei 采访...
接受Lilei的采访
---------------
在操场,进行锻炼
(3)正则表达式方法匹配切面:
knight.xml
<bean id="player2" class="study.hello.Player2"/>
<bean id="advice" class="study.hello.InterviewBeforeAdvice"/>
<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"
p:advice-ref="advice">
<property name="patterns">
<list>
<value>.*accept.*</value>
</list>
</property>
</bean>
<bean id="playerProxy2" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="regexpAdvisor"
p:proxyTargetClass="true"
p:target-ref="player2"
/>
测试代码:
ApplicationContext context=new ClassPathXmlApplicationContext("knight.xml");
Player2 playerProxy=(Player2)context.getBean("playerProxy2");
playerProxy.acceptInterview("Lilei");
System.out.println("---------------");
playerProxy.exercise("操场");
运行结果:
study.hello.Player2.acceptInterview
感谢 Lilei 采访...
接受Lilei的采访
---------------
在操场,进行锻炼
(4)动态切面,可以在运行阶段检查参数的值,对性能影响很大。
DynamicPointcut.java
<pre name="code" class="java">public class DynamicPointcut extends DynamicMethodMatcherPointcut {
private static List<String> vips=new ArrayList<String>();
static{
vips.add("llq");
vips.add("tom");
}
//对类进行静态切点检查
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class<?> clazz) {
// TODO Auto-generated method stub
System.out.println("调用getClassFilter,对"+clazz.getName()+"进行静态切点检查");
return Player2.class.isAssignableFrom(clazz);
}
};
}
//对方法进行静态切点检查
public boolean matches(Method method,Class clazz) {
System.out.println("调用matches(method,clazz)方法,对"+clazz.getName()+"."+method.getName()+"进行静态切点检查");
return method.getName().equals("acceptInterview");
}
//如果前两个检查都通过了就会对类,执行动态切点检查。从而节约时间开销。
public boolean matches(Method method, Class<?> targetClass, Object[] args) {
// TODO Auto-generated method stub
//对参数进行动态检查,当且仅当函数名为acceptInterview,并且,第一个参数(即记者)是vip是,才会拦截下这个方法,插入横切逻辑advice进行增强
System.out.println("调用matches(method,clazz,args),对"+targetClass.getName()+"."+method.getName()+"进行动态检查...");
String vip=(String) args[0];
return vips.contains(vip);
}
}
knight.xml
<bean id="player2" class="study.hello.Player2"/>
<bean id="advice" class="study.hello.InterviewBeforeAdvice"/>
<bean id="dynamicPointcut" class="study.hello.DynamicPointcut"/>
<bean id="dynamicAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
p:advice-ref="advice"
p:pointcut-ref="dynamicPointcut"
/>
<bean id="playerProxy3" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="dynamicAdvisor"
p:proxyTargetClass="true"
p:target-ref="player2"
/>
测试代码:
<pre name="code" class="java"> ApplicationContext context=new ClassPathXmlApplicationContext("knight.xml");
Player2 playerProxy=(Player2)context.getBean("playerProxy3");
playerProxy.acceptInterview("tom");
System.out.println("--------------");
playerProxy.acceptInterview("tom");
playerProxy.acceptInterview("ppp");
System.out.println("---------------");
playerProxy.acceptInterview("ppp");
playerProxy.exercise("操场");
playerProxy.exercise("操场");
运行结果:
在织入切面前,spring对目标类中的所有方法进行静态切点检查:
调用getClassFilter,对study.hello.Player2进行静态切点检查
调用matches(method,clazz)方法,对study.hello.Player2.acceptInterview进行静态切点检查
调用getClassFilter,对study.hello.Player2进行静态切点检查
调用matches(method,clazz)方法,对study.hello.Player2.exercise进行静态切点检查
调用getClassFilter,对study.hello.Player2进行静态切点检查
调用matches(method,clazz)方法,对study.hello.Player2.clone进行静态切点检查
调用getClassFilter,对study.hello.Player2进行静态切点检查
调用matches(method,clazz)方法,对study.hello.Player2.toString进行静态切点检查
第一次调用acceptInterview(),先进行静态切点检查,然后通过之后进行动态切点检查,都通过之后织入横切逻辑
调用getClassFilter,对study.hello.Player2进行静态切点检查
调用matches(method,clazz)方法,对study.hello.Player2.acceptInterview进行静态切点检查
调用matches(method,clazz,args),对study.hello.Player2.acceptInterview进行动态检查...
study.hello.Player2.acceptInterview
感谢 tom 采访...
接受tom的采访
第二次调用acceptInterview(),不再进行动态切点检查,直接进行动态切点检查,通过,就织入横切逻辑
--------------
调用matches(method,clazz,args),对study.hello.Player2.acceptInterview进行动态检查...
study.hello.Player2.acceptInterview
感谢 tom 采访...
接受tom的采访
第三次调用acceptInterview(),不再进行静态切点检查,直接进行动态切点检查,没通过,不织入横切逻辑
调用matches(method,clazz,args),对study.hello.Player2.acceptInterview进行动态检查...
接受ppp的采访
---------------
调用matches(method,clazz,args),对study.hello.Player2.acceptInterview进行动态检查...
接受ppp的采访
第一次调用exercise方法,首先进行静态切点检查,没有通过,不再进行动态切点检查,也不织入横切逻辑
调用getClassFilter,对study.hello.Player2进行静态切点检查
调用matches(method,clazz)方法,对study.hello.Player2.exercise进行静态切点检查
在操场,进行锻炼
第二次调用exercise方法,不再进行检查
在操场,进行锻炼