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>

    后置返回通知after-returning只有在目标方法成功执行后才会生效,而后置最终通知after无论目标方法成功执行还是抛出异常都会生效。


    以下是测试方法:

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>

五、切面对象的作用范围

    1. singleton。单例模式,即每个切面只有一个实例,是Spring AOP默认的作用域。

    2. perthis。每个切入点表达式匹配的连接点对应的AOP对象都会创建一个新的切面实例。使用@Aspect(perthis(“切入点表达式”))指定切入点。

    3. pertarget。每个表切入点达式匹配的连接点对应的目标对象都会创建一个新的切面实例。使用@Aspect(pertarget("切入点表达式"))指定切入点。

    注意:Schema风格的配置只支持默认的singleton作用域。在进行切面定义的时候还必须将切面bean的作用域(scope)定义为“prototype",否则将不能为每个匹配连接点表达式的AOP代理对象或目标对象创建一个切面实例。


六、Spring AOP代理机制

    1. JDK动态代理。java.long.reflect.Proxy动态代理实现。即提取目标对象的接口然后对接口创建AOP代理对象。

    2. CGLIB代理。CGLIB不仅能进行接口代理,也能进行类代理。但是CGLIB不能代理final方法,因为CGLIB通过生成子类创建代理,而final方法不能被覆盖。另外CGLIB会调用两次构造器,一次是目标类的构造器,一次是代理累的构造器,如果使用该方式,请确保两次构造器调用不会影响应用功能。

    默认情况下Spring使用JDK动态代理,如果目标对象没有实现任何接口将使用CGLIB代理。如果要强制使用CGLIB代理,可以采用如下方法指定:

    ①schema风格配置

<aop:config proxy-target-class="true">...</aop:config>

    ②@Aspect风格使用如下方式指定采用CGLIB代理方式

<aop:aspectj-autoproxy proxy-target-class="true"/>



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值