Spring AOP学习笔记

一、通过一个最基础的例子记录基于Schema方式的spring AOP实现。

    以下模拟一个业务处理类:

public class HelloWorldServiceImpl implements HelloWorldService {
	@Override
	public String sayHello(String param,People people) {
		System.out.println("People's name is " + people.getName());
		return "success";
	}
}


    以下模拟切面通知实现类,其他诸如环绕通知、后置异常通知就不一一列出了:

public class HelloWorldAspect {
	public void beforeAdvice(String pa,People arg) {
		System.out.println("=================前置通知!" + pa);
	}
	
	public void afterFinallyAdvice(String pa, People arg) {
		System.out.println("=================后置最终通知!" + arg.getName());
	}
	
	public void afterReturn(String pa,People arg,String value) {
		System.out.println("=================value = " + value);
	}
}


    以下是applicationContext.xml文件配置:

<bean id="helloWorldService" class="cn.com.enorth.service.impl.HelloWorldServiceImpl"/>
<bean id="aspect" class="cn.com.enorth.aop.HelloWorldAspect"/>

<aop:config>
	<aop:pointcut expression="execution(* cn.com.enorth..*.*(..)) and args(pa,arg)" id="pointcut"/>
	<aop:aspect ref="aspect">
		<aop:before pointcut-ref="pointcut" method="beforeAdvice"/>
		<aop:after pointcut-ref="pointcut" method="afterFinallyAdvice"/>
		<aop:after-returning pointcut-ref="pointcut" method="afterReturn" returning="value"/>
	</aop:aspect>
</aop:config>


    以下是测试方法:

public class Test {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		HelloWorldService service = ctx.getBean("helloWorldService",HelloWorldService.class);
		People people = new People();
		people.setName("Super Man");
		service.sayHello("Hello", people);
	}
}

    按照Spring的正常调用方式调用业务类的相应方法,如果该方法匹配AOP的切入点模式,就会自动调用切面实现类中的相应方法。

    如果通知方法中需要使用被匹配方法的参数,则要在匹配模式中加入"and args(参数列表)",参数名要与通知方法中的参数名一一对应。如果需要使用被匹配方法的返回值,则要在后置返回通知中定义return属性,其值与通知方法中的参数名一致。如上述配置中return=”value“与HelloWorldAspect类中afterReturn方法的第三个参数名”value“相一致。


    关于环绕通知:

    环绕通知十分强大,可以决定目标方法(被匹配到的方法)是否执行,什么时候执行,执行的时候是否替换参数,执行完毕是否替换返回值。环绕通知通过<aop:around>标签及其属性进行配置。

    环绕通知的第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型,在通知方法内部使用PorceedingJoinPoint的proceed()方法是目标方法执行。proceed()方法可选择是否传入Object[]数组替换目标方法执行时的参数。目标方法的返回值,将由通知方法的返回值取代。如果目标方法返回值类型为void,则在通知方法内执行proceed()方法返回null。而如果目标方法有返回值,通知方法为void类型,调用目标方法的程序也将获得null。


二、基于@AspectJ的AOP

1、Spring默认并不支持@AspectJ风格的切面声明,为了启用支持需要做如下配置:

<aop:aspectj-autoproxy />

2、声明切面

    使用@Aspect注解进行声明

@Aspect
public class NewAspect {}


    然后将该切面在配置文件中声明为bean,Spring就能自动识别并进行AOP方面的配置:

<bean id="aspect" class="....NewAspect"/>


    切入点以及各类通知都可以在该类中定义,见下文。

3、一个@AspectJ风格的AOP实现如下:

    以下模拟业务处理类:

public class NewServiceImpl implements NewService {
	@Override
	public void newSay(People people) {
		System.out.println(people.getName());
	}
}


    以下模拟一个切面处理类:

public class NewAspect {
	@Pointcut(value="execution(* cn.com.enorth..*.new*(..)) && args(people)")
	public void pointCut(People people){}
	
	/*
	 * value取值可以是命名切入点“pointCut(people)”
	 * 也可以是新的切入点表达式“execution(* cn.com.enorth..*.*(..)) && args(people)”
	 */
	@Before(value="pointCut(people)")
	public void befort(People people){
		System.out.println("===============" + people.getName());
	}
	
	@AfterReturning(value="pointCut(people)",returning="value")
	public void afertReturning(People people,Object value){
		System.out.println("===============" + value);
	}
}


    以下是applicationContext.xml中需要注册的bean:

<bean id="newService" class="cn.com.enorth.service.impl.NewServiceImpl"/>
<bean id="newAspect" class="cn.com.enorth.aop.NewAspect"/>


    以下是测试代码:

public class Test {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		NewService service = ctx.getBean("newService",NewService.class);
		People people = new People();
		people.setName("Super Man");
		service.newSay(people);
	}
}


    对照Schema风格的AOP实现,@AspectJ风格也就很好理解了。差异比较大的一点是,@AspectJ风格中可引用的切入点是通过单独定义的一个方法实现的,在上下文中通过方法名以及参数列表引用,即:

@Pointcut(value="execution(* cn.com.enorth..*.new*(..)) && args(people)")
public void pointCut(People people){}


    而在Schema风格中,这是通过<aop:pointcut>标签实现的,在上下文中通过切入点的id引用,即:

<aop:pointcut expression="execution(* cn.com.enorth..*.*(..)) and args(pa,arg)" id="pointcut"/>

    另外,@AspectJ风格中的&&、||、!在Schema风格中分别用and、or、not表示。


三、参数传递

    上面的介绍中已经通过在定义切入点的时候,使用“args(参数列表)”匹配模式的方式传递被匹配方法的参数到通知方法中。下面介绍在通知方法中使用JoinPoint接口类型获取更多信息。

    任何通知方法的第一个参数都可以是JoinPoint类型,例外的是环绕方法的第一个参数必须是ProceedingJoinPoint,它是JoinPoint的子类。第一个参数也可以是JoinPoint.StaticPart,它只包含连接点的静态信息。

1、JoinPoint接口方法说明

String toString();//连接点所在位置的相关信息

String toShortString();//简短相关信息

String toLongString();//详细相关信息

Object getThis();//返回AOP代理对象

Object getTarget();//返回目标对象

Object[] getArgs();//返回被通知方法的参数列表

Signature getSignature();//返回当前连接点签名

SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置

String getKind();//返回连接点类型

StaticPart getStaticPart();//返回连接点静态部分

2、ProceedingJoinPoint接口定义:用于环绕通知,使用proceed方法执行目标方法。

public interface ProceedingJoinPoint extends JoinPoint {
    public Object proceed() throws Throwable;
    public Object proceed(Object[] args) throws Throwable;
}

3、JoinPoint.StaticPart:提供连接点的静态信息部分

public interface StaticPart{
    Signature getSignature(); // 返回当前连接点签名
    String getKind(); //返回连接点类型
    int getId(); //返回连接点的唯一标识
    String toString(); //同上
    String toShortString(); //同上
    String toLongString(); //同上
}


四、通知执行顺序

1、同一个切面中通知的执行顺序

①前置通知 / 环绕通知proceed方法执行之前的部分//这两者的先后顺序不确定

②被通知方法

③后置通知 / 环绕通知proceed方法执行之后的部分//这两者的执行顺序不确定

2、不同切面中通知的执行顺序

    当定义在不同切面的同类型的通知(比如都是前置通知),需要在用一个连接点执行时,如果没有指定切面的执行顺序,那么这两个通知的执行顺序是未知的。

    如果有必要确定执行顺序,需要执行切面执行的优先级。可以通过实现org.springframework.core.Ordered接口或者使用注解@Order指定切面优先级。Order.getValue()返回值越小优先级越高。不推荐使用接口方法指定优先级,两种风格下配置优先级的方式如下:

@Aspect
@Order(2)
public class OrderAspect2{}
<aop:aspect ref="..." order="1">...</aop:aspect>


原文地址:https://www.cnblogs.com/keanuyaoo/p/3257972.html