动态代理在Spring AOP中的应用

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 采访...
接受记者采访...
感谢 操场 采访...
在操场,开始训练,剧烈运动...
 
 
解释:<1>我们使用ProxyFactory将advice增强织入到了player类中,但是,ProxyFactory内部实际上是使用的JDK代理或者CGLIB代理。从运行结果我们可以看出,上面我们使用的是CGLIB代理。那么我们如何决定使用哪个代理呢?事实上,我们通过ProxyFactory的setInterfaces(arg0)指定了针对接口进行处理,那么ProxyFactory就会使用JDK代理,如果是针对类的代理就会使用CGLIB代理。另外,我们还可以通过setOptimize(true)方法,让proxyfactory启动优化处理方式,这样的话,针对接口的代理也会使用CGLIB。
		/*
		 * 指定接口,从而使用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方法,不再进行检查

在操场,进行锻炼






 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP动态代理是实现AOP的一种方式。Spring AOP使用了Java的动态代理机制来创建代理对象,并将切面逻辑织入到目标对象的方法调用动态代理是在运行时生成代理对象的一种机制。它不需要在编写代码时就确定要代理的类和方法,而是在运行时根据需要创建代理对象。在Spring AOP,主要有两种类型的动态代理:基于接口的代理和基于类的代理。 1. 基于接口的代理(JDK动态代理):当目标对象实现了至少一个接口时,Spring AOP会使用JDK动态代理来生成代理对象。JDK动态代理通过实现目标对象所实现的接口来生成代理对象,在调用代理对象的方法时,会通过InvocationHandler接口将方法调用转发给实际的目标对象。 2. 基于类的代理(CGLIB动态代理):当目标对象没有实现任何接口时,Spring AOP会使用CGLIB动态代理来生成代理对象。CGLIB动态代理通过继承目标对象生成一个子类,并覆盖其的方法来实现代理。调用代理对象的方法时,会先进入子类的方法,然后再调用目标对象的方法。 使用动态代理可以实现对目标对象的拦截和增强,而不需要修改目标对象的源代码。代理对象可以在目标对象的方法执行前、执行后或执行过程插入额外的逻辑,例如日志记录、性能统计、事务管理等。 需要注意的是,动态代理只能对公共方法进行拦截,对私有方法、静态方法或final方法无法进行拦截。同时,动态代理也只能拦截通过代理对象调用的方法,直接通过目标对象调用方法时无法实现拦截和增强。 总结来说,Spring AOP使用动态代理来实现切面逻辑的织入,可以通过JDK动态代理或CGLIB动态代理来生成代理对象,并将切面逻辑应用到目标对象的方法调用

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值