您的位置:首页 > 编程语言 > Java开发

Spring AOP 初步

2013-05-02 11:26 162 查看

1. 面向切面编程(AOP)的概念

参见如下链接:

http://baike.baidu.com/view/1865230.htm

简单地说,AOP就是在程序的纵向流程上添加横向的切面逻辑,相当于给已有的业务逻辑增加额外的功能,而不改动原有的代码。

2. Spring中的AOP流程

完整的AOP要素包括:

切面(Aspect),逻辑(Advice),连接点(Joinpoint),切入点(Pointcut),指令(introduction)目标(Target),代理(Proxy),插入(Weaving)

但是在Spring框架中,AOP的部分要素已经被实现好了,只有如下的流程需要开发者来补充完整,就像IoC一样:

Advice →Pointcut →Proxy

3. Advice

Spring中提供了四种类型的Advice,分别是:前置、后置、拦截、异常。

实现这四种类型的Advice分别需要实现下列四个接口:

前置:MethodBeforeAdvice,目标方法调用前执行

后置:AfterReturningAdvice,目标方法调用后执行

拦截:MethodInterceptor,目标方法在调用前被拦截

异常:ThrowsAdvice,目标方法抛出异常时执行

上述Advice在配置xml时只需要指定bean的id和class即可,无特殊要求。

4. Pointcut

默认下,Pointcut是不需要设置的。上述四种Advice对它们所作用的目标对象的每个方法都会被调用。但实际应用中,可能不需要每个方法都调用AOP的逻辑,因此可以指定Pointcut来限制Advice作用的方法。

虽然Spring提供了完整接口可供开发者自定义Pointcut,但是也提供更为简便的内置类来简化开发。主要有如下几种:

1) NameMatchMethodPointcut

继承自NameMatchMethodPointcut类的切入点,可以在Advice执行之前自动匹配在切入点中设置好的方法名,目标对象中只有指定的方法才会调用Advice。

继承了NameMatchMethodPointcut的Pointcut类必须覆盖它的matches方法,并且在其中调用它的setMappedName(StringmethodName)来指定要拦截的方法名。methodName字符串还可使用*通配符。

实现后的Pointcut类必须通过DefaultPointcutAdvisor进行装配,以完成Advice的插入(Weaving)。详见示例。

2) RegexpMethodPointcutAdvisor

这种切入点使用Spring中内置的通过正则式来匹配方法名的切入点类,只需要在配置文件中装配RegexpMethodPointcutAdvisor,并在装配的过程在属性名为patterns(或pattern)的property中指定需要匹配的正则式即可。

3) ControlFlowPointcut

与上述两个类相比,ControlFlowPointcut(控制流切入点)的目标更为精细。它也是通过Spring中内置的控制流切入点类实现,并不需要编写额外的切入点类。

在配置的时候,首先需要配置ControlFlowPointcut,并向它的构造方法中传入两个参数,第一个(index=0)为Advice所用的类名,第二个(index=1)为Advice作用的方法名。

之后,再配置DefaultPointcutAdvisor实现Advice的插入。

5. Proxy

可以看出,在Spring中,AOP的要素是逐层插入(Weaving)的,即Advice插入Pointcut,而Pointcut也需要插入Proxy,以实现代理对目标类以及切面逻辑的共同调用。

在Spring,对Proxy的插入只需要配置ProxyFactoryBean即可,在其内部进一步配置Advice列表(list)和目标类(target),以及代理调用目标类的接口。其过程与Servlet中配置拦截器颇为相似。详见示例。

6. 示例

前置Advice
package aop.advice;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

public class MethodBeforeAdviceImpl implements MethodBeforeAdvice {

	public void before(Method method, Object[] args, Object target)
			throws Throwable {
		System.out.println("=====前置开始=====");
		System.out.println("类名:" + target.getClass().getName() 
				+ "\t方法名:" + method.getName());
		System.out.print("参数列表:");
		for (Object arg: args) {
			System.out.print(arg + ", " + arg.getClass().getName() + ";\t");
		}
		args[0] = 123;
		System.out.println();
		System.out.println("将第一个参数改为123");
		System.out.println("=====前置结束=====");
	}

}


后置Advice
package aop.advice;

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;

public class AfterReturningAdviceImpl implements AfterReturningAdvice {

	public void afterReturning(Object returnValue, Method method, 
			Object[] args, Object target) throws Throwable {
		System.out.println("~~~~~后置开始~~~~~");
		System.out.println("返回值为:" + returnValue + ", " 
				+ returnValue.getClass().getName());
		System.out.print("将第二个参数改为345并再调用一次:");
		args[1] = 345;
		System.out.println(method.invoke(target, args));
		System.out.println("~~~~~后置结束~~~~~");
	}

}


拦截Advice
package aop.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MethodInterceptorImpl implements MethodInterceptor {

	public Object invoke(MethodInvocation invocation) throws Throwable {
		System.out.println("*****拦截开始*****");
		if (invocation.getArguments()[1] instanceof Integer) {
			System.out.println("若第二个参数为整型则强制设定结果为-100");
			System.out.println("*****拦截结束*****");
			return -100;
		}
		System.out.println("*****拦截结束*****");
		
		return invocation.proceed();
	}

}


异常Advice
package aop.advice;

import java.lang.reflect.Method;

import org.springframework.aop.ThrowsAdvice;

public class ThrowsAdviceImpl implements ThrowsAdvice {

	public void afterThrowing(Throwable e) {
		System.out.println("-----异常开始-----");
		System.out.println(e.getClass().getName() + "\t" + e.getMessage());
		System.out.println("-----异常结束-----");
	}
	
	// ThrowsAdvice只是标识接口,实现类中必须至少实现两个不同签名的afterThrowing方法中的一个
//	public void afterThrowing(Method method, Object[] args, Object target, Throwable e) {
//		
//	}
}


按方法名匹配的Pointcut
package aop.pointcut;

import java.lang.reflect.Method;

import org.springframework.aop.support.NameMatchMethodPointcut;

public class NameMatchMethodPointcutImpl extends NameMatchMethodPointcut {

	private static final long serialVersionUID = -3944947836761348760L;

	public boolean matches(Method method, Class targetClass) {
		this.setMappedName("divide");
//		this.setMappedNames(new String[]{"add", "divide"});
		
		return super.matches(method, targetClass);
	}

}


目标类及其接口
package aop.service;

public interface IMath {
	public int add(int a, int b);
	public double add(double a, double b);
	public double divide(double a, double b);
}


package aop.service;

public class Math implements IMath {
	public int add(int a, int b) {
		return a + b;
	}
	
	public double add(double a, double b) {
		return a + b;
	}

	public double divide(double a, double b) throws IllegalArgumentException {
		if (b == 0) {
			throw new IllegalArgumentException("除数不能为0");
		}
		return a / b;
	}
}


配置文件
<?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-3.0.xsd"> 
	<bean id="math" class="aop.service.Math" />
	
	<bean id="before" class="aop.advice.MethodBeforeAdviceImpl" />
	<bean id="after" class="aop.advice.AfterReturningAdviceImpl" />
	<bean id="exception" class="aop.advice.ThrowsAdviceImpl" />
	<bean id="intercept" class="aop.advice.MethodInterceptorImpl" />
	
	<bean id="methodNamePointcut" class="aop.pointcut.NameMatchMethodPointcutImpl" />
	<bean id="nameAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="pointcut">
			<ref bean="methodNamePointcut" />
		</property>
		<property name="advice">
			<ref bean="before" />
		</property>
	</bean>
	
	<bean id="regexpAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
		<property name="pattern">
			<value>aop.service.IMath.divide</value>
		</property>
		<property name="advice">
			<ref bean="after" />
		</property>
	</bean>
	
	<bean id="cfPointcut" class="org.springframework.aop.support.ControlFlowPointcut">
		<constructor-arg>
			<value>aop.Main</value>
		</constructor-arg>
		<constructor-arg>
			<value>add</value>
		</constructor-arg>
	</bean>
	<bean id="cfAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
		<property name="pointcut">
			<ref bean="cfPointcut" />
		</property>
		<property name="advice">
			<ref bean="intercept" />
		</property>
	</bean>
	
	<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="proxyInterfaces">
			<list>
				<value>aop.service.IMath</value>
			</list>
		</property>
		<property name="interceptorNames">
			<list>
				<!-- <value>after</value> -->
				<value>regexpAdvisor</value>
				<!-- <value>before</value> -->
				<value>nameAdvisor</value>
				<value>exception</value>
				<!-- <value>intercept</value> -->
				<value>cfAdvisor</value>
			</list>
		</property>
		<property name="target">
			<ref bean="math" />
		</property>
	</bean> 
</beans>


执行入口
package aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import aop.service.IMath;

public class Main {

	public static void main(String[] args) {
		ApplicationContext context = new FileSystemXmlApplicationContext(
				"src\\applicationContext.xml");
		IMath m = (IMath)context.getBean("proxy");
		System.out.println(add(m, 1, 2));
		System.out.println("..........................................................................................");
		System.out.println(m.add(4.0, 5.5));
		System.out.println("..........................................................................................");
		System.out.println(m.divide(10, 2));
		System.out.println("..........................................................................................");
		System.out.println(m.divide(5, 0));
	}

	public static int add(IMath m, int a, int b) {
		return m.add(a, b);
	}
}


7. 说明

1) 加入AOP之后,方法的执行顺序是:

前置Advice →拦截Advice →目标方法 → 后置Advice

若目标方法中抛出了异常,则立即跳转执行异常Advice,再跳转回来执行异常处理(try…catch或throws)

2) 前置Advice和拦截Advice都能改动目标方法的参数值,区别在于,执行前置Advice之后一定要指定目标方法,而拦截Advice能够阻止目标方法的执行;只有在拦截Advice中执行了invocation.proceed()方法才会执行目标方法,其返回值就是目标方法的返回值

3) 因为后置Advice是在目标方法返回(return)之后才执行,因此它不能对目标方法产生影响,但是可以通过反射机制的Method得知目标方法的一些信息

4) 异常Advice实现的ThrowsAdvice接口只是一个标识接口,需要手动实现afterThrowing方法两个签名中的其中一个

5) 代理的拦截列表(interfaceNames)可以接口两种类型的逻辑:Advice和Advisor。后者即是前述的自定义Pointcut的Advice;Spring中提供了许多接口可供实现Advisor,这里只用到了较常用的几个内置比较完整的抽象类

6) 拦截列表(interfaceNames)的顺序不影响Advice/Advisor执行的顺序

7) 完成AOP配置之后,在生成bean时需要用代理的id来替代目标类的id,即如:

IMath m = (IMath)context.getBean("proxy"); // 而不是用目标类的id:math

8) 代理的配置中必须在proxyInterfaces属性中指定代理和目标类共用的接口的全名
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: