Spring AOP小结

本文深入浅出地介绍了Spring AOP的基本概念,包括AOP的核心术语、实现原理及具体使用方法,并通过实例展示了如何利用Spring AOP减少代码冗余。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring作为一种web开发最常用的框架之一,其简化开发,松耦合都是我们选择它的原因,而作为Spring的核心---Spring AOP(Aspect Oriented Porgraming)面向切面编程更是重中之重。因此本文将对AOP以本人的理解重新阐述一遍,以便自己和读者理解。如有差错,万望指出。

一、            AOP到底是什么?作用是什么?

按照软件重构的思想,如果多个类中出现相同的代码,那么应该考虑定义一个抽象类,将相同部分抽象出来。例如Horse,Pig,Dog类都用相同的eat(),run()。此时可以定义一个animal抽象类,将eat和run方法实现,而Horse,Pig,Dog可以继承animal类复用eat(),run()方法,从而减少重复代码。但是事情并不只是这么简单。看如下代码:


	public void get(int position) {
		// TODO Auto-generated method stub
		long startTime = System.currentTimeMillis();
		System.out.println("list get:"+list.get(position));
		long endTime = System.currentTimeMillis();
		System.out.println("耗时:"+(endTime-startTime)+"ms");
	}

	
	public void insert(int position, int value) {
		// TODO Auto-generated method stub
		System.out.println("list insert:");
		long startTime = System.currentTimeMillis();
		list.add(position, value);
		long endTime = System.currentTimeMillis();
		System.out.println("耗时:"+(endTime-startTime)+"ms");
	}

	
	public void remove(int position) {
		// TODO Auto-generated method stub
		
		System.out.println("list remove:");
		long startTime = System.currentTimeMillis();
		list.remove(position);
		long endTime = System.currentTimeMillis();
		System.out.println("耗时:"+(endTime-startTime)+"ms");
	}

业务场景是对arraylist和linkedlist的get,add,remove方法的性能检测。通过方法的执行时间判断方法性能的优劣。

毫无疑问,可以定义一个抽象类。AbstractList,对get,add,remove方法都做如下处理

然后再复用抽象类方法即可。虽然在纵向上减少子类的实现。但是在同一个类中,get,add,remove三个方法都重复了方法执行计时操作。这并不是一种好的代码实现,然后继承并不能解决这种横向的代码冗余。那么能不能用一种方法,将计时操作都提取出来,不需要冗余的实现呢?这就是AOP实现的意义了。

二、            AOP的术语解释

为了学习交流的方便,了解AOP中的术语也是相当有必要的:

1、  连接点(Joinpoint)

程序执行的某个特定位置:如类初始化前,类初始化后,方法调用前,方法调用后,方法抛出异常后。即类与程序代码执行时具有边界性质的特定点。对于Spring来说:仅支持方法调用前,方法调用后,方法抛出异常和方法调用前后。所以,AOP要嵌入额外的代码只能在以上的几个点上。而连接点就是AOP可以嵌入代码的选点。以上例为例,get,add,remove三个方法的调用前,后,抛出异常均是连接点。

连接点位置由两个信息确定:一是执行点,即get或者add方法。二是执行方位,即方法调用前或者方法调用后。这个由增强类型决定。

2、  切点(PointCut)

由上可知,一个类有多个连接点。而切点就是我们所感兴趣的,需要嵌入增强的连接点。在Spring中,使用类和方法的查询条件作为切点。而满足条件查询出来的方法就是执行点,通过配置何种增强共同组成连接点。

3、  增强(Advice)

增强就是织入连接点的额外业务逻辑。在上例中就是方法执行计时的代码。因为切点+执行方位才组成连接点。因此Spring中的增强都是带方位的。如BeforeAdvice,AfterReturningAdvice等。

4、  目标对象(Target)

增强织入的目标类。即连接点所在的类。

5、  引介(Introduction)

引介是一种特殊的增强,为类添加额外的属性和方法。通过引介功能,可以动态为业务类添加接口的实现逻辑。

6、  织入(Weaving)

织入很容易望文生义,将增强逻辑添加到连接点上的过程。

AOP有三中织入方式:

1)  编译期织入,要求特殊的Java编译器。

2)  类装载期织入,要求特殊的类装载器

3)  动态代理织入,在运行期为目标类添加增强生成子类。

Spring采用的是动态代理,而AspectJ采用编译器织入和类装载期织入。

7、  代理(Proxy)

一个类被AOP织入增强后,会生成一个代理类。它融合了目标类和增强逻辑。根据代理方式不同,可能生成同接口的类(JDK动态代理),也可能生成目标类的子类(Cglib动态代理)(有关两种代理方式的不同,可参照本博客关于代理方式的总结)

8、  切面(Adpect)

由切点和增强组成,即包括切点的定义,增强逻辑定义和增强执行方位。

三、            AOP的实现原理

再以上例为例,介绍如何通过JDK动态代理实现AOP:

ps:许多方法没有复用实现是因为JDK动态代理必须依赖于实现接口。用Cglib代理可以解决这个问题。

 

/**
 * 定义接口
 * @author cai.wuxin
 *
 */
public interface ListApi {

	public void get(int position);
	
	public void insert(int position,int value);
	
	public void remove(int position);
	
	public void removeEven();
}

public class ArrayListImpl implements ListApi{

private List<Integer> list = null;
	
	public ArrayListImpl(int initialSize) {
		list = new ArrayList<>();
		for(int i = 0; i<initialSize;i++){
			list.add(i);
		}
	}
	
	@Override
	public void get(int position) {
		// TODO Auto-generated method stub
		System.out.println("array get:"+list.get(position));
	}

	@Override
	public void insert(int position, int value) {
		// TODO Auto-generated method stub
		System.out.println("array insert:");
		list.add(position, value);
	}

	@Override
	public void remove(int position) {
		// TODO Auto-generated method stub
		System.out.println("array remove:");
		list.remove(position);
	}

	@Override
	public void removeEven() {
		// TODO Auto-generated method stub
		System.out.println("array remove even:");
		Iterator<Integer> e = list.iterator();
		while (e.hasNext()) {
			if(e.next()%2==0){
				e.remove();
			}
		}
	}

}

public class LinkedListImpl implements ListApi{

	private List<Integer> list = null;
	
	public LinkedListImpl(int initialSize) {
		list = new LinkedList<>();
		for(int i = 0; i<initialSize;i++){
			list.add(i);
		}
	}
	
	@Override
	public void get(int position) {
		// TODO Auto-generated method stub
		System.out.println("list get:"+list.get(position));
	}

	@Override
	public void insert(int position, int value) {
		// TODO Auto-generated method stub
		System.out.println("list insert:");
		list.add(position, value);
	}

	@Override
	public void remove(int position) {
		// TODO Auto-generated method stub
		System.out.println("list remove:");
		list.remove(position);
	}

	@Override
	public void removeEven() {
		// TODO Auto-generated method stub
		System.out.println("list remove even:");
		Iterator<Integer> e = list.iterator();
		while (e.hasNext()) {
			if(e.next()%2==0){
				e.remove();
			}
		}
	}

}

/**
 * 定义代理实现
 * @author cai.wuxin
 *
 */
public class TimeHandler implements InvocationHandler{

	private ListApi list;
	
	public ListApi getProxy(ListApi list){
		this.list = list;
		ListApi proxy = (ListApi)Proxy.newProxyInstance(
				list.getClass().getClassLoader(), 
				list.getClass().getInterfaces(), 
				this);
		return proxy;
	}
	
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// TODO Auto-generated method stub
		//将方法执行时间计时逻辑移到此处。
		long startTime = System.currentTimeMillis();
		Object result = method.invoke(list, args);
		long endTime = System.currentTimeMillis();
		System.out.println("耗时:"+(endTime-startTime)+"ms");
		return result;
	}

}

public class PerformanceTest {

	ListApi array;
	ListApi list;
	int initialSize = 1000000;
	int insertPostion = 1;
	int insertValue = 2;
	int removePostion = 90000;
	
	int getPostion = 90000;
	
	
	@Before
	public void prepare(){
		ListApi arrayImpl = new ArrayListImpl(initialSize);
		ListApi listImpl = new LinkedListImpl(initialSize);
		
		TimeHandler t1 = new TimeHandler();
		TimeHandler t2 = new TimeHandler();
		
		array = t1.getProxy(arrayImpl);
		list = t2.getProxy(listImpl);
	}
	//可以看到真正执行时,只需要关注业务执行逻辑即可,不需要再增加冗余的性能检测逻辑。
	@Test
	public void test(){
		array.insert(insertPostion, insertValue);
		list.insert(insertPostion, insertValue);
		
		array.remove(removePostion);
		list.remove(removePostion);
		
		array.removeEven();
		list.removeEven();
		
		array.get(getPostion);
		list.get(getPostion);
	}
}

执行结果:

array insert:
耗时:0ms
list insert:
耗时:0ms
array remove:
耗时:1ms
list remove:
耗时:1ms
array remove even:
耗时:52890ms
list remove even:
耗时:17ms
array get:180003
耗时:0ms
list get:180003
耗时:3ms


四、            AOP的使用

通过上文的介绍,很容易可以知道,实现AOP最关键的无非是切点和增强。因此以下只介绍项目中如何使用Spring AOP而不再讨论各种不同的增强类型,切面类型。首先介绍一下最基础的Spring AOP增强实现。

 

public interface Waiter {

	public void greetTo(String clientName);
	public void serveTo(String clientName);
}

/*
 * 目标增强类
 */
public class NaiveWaiter implements Waiter{

	@Override
	public void greetTo(String clientName) {
		System.out.println("greet to "+clientName);
		
	}

	@Override
	public void serveTo(String clientName) {
		System.out.println("serve to "+clientName);	
	}
	

}

//前置增强
public class GreetingBeforeAdvice implements MethodBeforeAdvice{

	@Override
	public void before(Method method, Object[] args, Object obj)
			throws Throwable {
		String clientName = (String)args[0];
		System.out.println("How are you Mr "+clientName);
	}

}


<!-- XML配置-->
<bean id="greetingAdvice" class="com.test.paditang.aop.adivce.GreetingBeforeAdvice"/>
	<bean id="target" class="com.test.paditang.aop.adivce.NaiveWaiter"/>
	<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean"
		p:proxyInterfaces="com.test.paditang.aop.adivce.Waiter"//定义代理接口
		p:interceptorNames="greetingAdvice"//增强方法
		p:target-ref="target"/>//目标对象


public class ApplicationContextTest {

	private ApplicationContext ac;
	
	@Before
	public void prepare(){
		ac = new ClassPathXmlApplicationContext("beans.xml");//自动视为在classpath中
	}
	
	@Test
	public void process(){
		//aop增强测试
		Waiter waiter = (Waiter)ac.getBean("waiter");
		waiter.greetTo("caiwuxin");
	}
	
	
}


How are you Mr.caiwuxin
greet to caiwuxin



在项目使用中,虽然Spring AOP能解决很多应用的切面需要,但是和AspectJ相比,仍有许多不足之处。因此我更推荐以下的AOP实现,Spring联合AspectJ,更简单的配置,更丰富的实现。需要添加AspectJ相关依赖。

//定义为切面
@Aspect
public class PreGreetingAspect {

	@Pointcut("execution(* greetTo(..))")//定义切点
	public void greet(){}
	
	@Before("greet()")//使用定义的切点 并定义执行方位,等同于MethodBeforeAdvice
	public void beforeGreeting(){
		System.out.println("How are you");//定义增强逻辑
	}
}

XML中只需要添加配置

<!-- 自动织入加强,XML配置 -->
   <aop:aspectj-autoproxy/>
    <bean class="com.test.paditang.aspectj.aspectj.PreGreetingAspect"/> 

public class AspectJTest {

	public static void main(String []args){
		Waiter target = new NaiveWaiter();
		AspectJProxyFactory factory = new AspectJProxyFactory();
		factory.setTarget(target);
		factory.addAspect(PreGreetingAspect.class);
		Waiter proxy = factory.getProxy();
		proxy.greetTo("John");
		proxy.serveTo("John");
	}
}

How are you
greet to John
serve to John
很明显可以看出对greetTo方法的增强已经实现。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值