5.spring aop

        切面,是面向对象编程中一个重要的术语,切面能够模块化系统当中横切关注点,这些横切关注点会横跨系统当中的多个组件,例如安全,事务,日志,缓存,如果在每个组件中都去自行完成这些关注点的代码编写,那么横切关注点的代码将会散步在系统的各个角落,从系统灵活性和维护性来说,造成了巨大的阻碍。所以将横切关注点进行模块化,形成切面,将核心的业务与切面进行分离,能够更加有限的对程序进行管理,使得开发人员只关注于核心业务的编写,并从横切关注点的逻辑中脱离出来。

 

AOP术语:

切面:系统当中的交叉业务,一般指安全,事务,日志,缓存这些横切关注点,切面是对这些横切关注点的模块化。

通知:横切关注点的具体实现,是切面具体实现的代码程序。

连接点:在系统当中能够插入切面的地点。

切入点:应用哪些切面到哪些连接点上。

目标对象:对哪些对象应用切面。

代理对象:将切面应用在目标对象上产生的对象。

织入:将切面应用在目标对象上产生代理对象的过程。织入发生的时期有三种:编译器,类加载期,运行期,编译期和类加载期要用特殊的编译器和特殊的类加载器,AspectJ可以完成,而spring AOP采用运行期的方式完成织入。

引入:向目标对象添加新的属性和方法。

 

目前AOP三足鼎立框架:

AspectJ
JBOSS AOP
Spring AOP

其中AspectJ功能非常强大,spring AOP在此基础上借鉴了许多。

Spring对AOP的支持:

基于代理的经典AOP
@AspectJ注解驱动的切面
纯POJO切面
注入式AspectJ切面

spring AOP只适用于方法的拦截,如果要考虑构造器或者属性的拦截,则应该考虑AspectJ的使用。

 

spring对AspectJ的指示器支持:

Aspect指示器 描述
arg() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点匹配参数由指定注解标注的执行方法
execution() 用于匹配连接点的执行方法
this() 限制连接点匹配AOP代理的bean引用为指定类型的类
target() 限制连接点匹配目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型(当使用spring AOP时,方法定义在由指定注解所标注的类里)
@annotation 限制匹配带有指定注解连接点

如果在spring中尝试使用AspectJ以外的指示器,将会抛出IllegalArgumentException异常。其中execution指示器是最常使用的,用于执行匹配,再配合其他的限制匹配指示器,从而定义切入点。

 

 

execution(* org.robbie..service..*.*(..))

 上述指示器代表,匹配org.robbie包下任意子包(不一定是儿子包)任意类的任意方法(该方法不管参数,也不管返回类型),其中第一个星号是返回类型,第二个星号是类名,第三个星号是方法名,最后括号中的点代表方法参数。

 

配合其他指示器共同定义切入点:

 

execution(* org.robbie..service..*.*(..) and within(org.robbie.test.*))

 代表既要满足前一个的执行匹配,又要满足在org.robbie.test下的包,才执行代理

 

 

使用bean()指示器,bean指示器是spring2.5后引入的:

 

execution(* org.robbie..service..Instrument.play() and bean(flute))

 该例中展示的是匹配指定包下的Instrument类的play方法,并且该bean的名称为flute

 

在XML中声明切面(定义AOP):

 传统的spring配置切面,需要使用proxyFactoryBean,但是配置起来十分的复杂,spring后来引入了命名空间的配置,用于简化切面的声明:

AOP配置元素 描述
<aop:advisor> 定义AOP通知器
<aop:after> 定义AOP后置通知(不管被通知的方法是否执行成功)
<aop:after-returning> 定义AOP 返回之后通知
<aop:after-throwing> 定义AOP 抛出异常之后通知
<aop:around> 定义AOP环绕通知
<aop:aspect> 定义切面
<aop:aspectj-autoproxy> 使用@AspectJ注解驱动的切面
<aop:before> 定义AOP前置通知
<aop:config> 顶层的AOP配置元素,大多数的<aop:*>元素必须包含在<aop:config>元素内
<aop:declare-parents> 为被通知的对象引入额外的接口,并透明的实现
<aop:pointcut> 定义切入点

 

目标对象:

package org.robbie.test.spring.beans;

import java.util.Properties;

public class Band {

	private Properties instruments;

	public Properties getInstruments() {
		return instruments;
	}

	public void setInstruments(Properties instruments) {
		this.instruments = instruments;
	}
	
	public void play(){
		System.out.println("I am playing !!!");
	}

}

 通知:

package org.robbie.test.spring;

import org.aspectj.lang.ProceedingJoinPoint;

public class MyAdvisor{
	
	public void before(){
		System.out.println("before");
	}
	
	public void after(){
		System.out.println("after");
	}
	
	public void afterThrowing(){
		System.out.println("afterThrowing");
	}
	
	public void afterReturning(){
		System.out.println("afterReturning");
	}
	
	public void round(ProceedingJoinPoint joinPoint) throws Throwable{
		System.out.println("round before");
		joinPoint.proceed();
		System.out.println("round after");
	}

}

 配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		   http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.springframework.org/schema/util
           http://www.springframework.org/schema/util/spring-util-3.0.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

	<bean id="myAdvisor" class="org.robbie.test.spring.MyAdvisor"></bean>
	
	<bean id="band" class="org.robbie.test.spring.beans.Band"></bean>
	
	<aop:config>
		<aop:aspect ref="myAdvisor">
			<aop:pointcut expression="execution(* *..*.*(..))" id="performance"/>
			<aop:before method="before" pointcut-ref="performance"/>
			<aop:after method="after" pointcut-ref="performance"/>
			<aop:after-returning method="afterReturning" pointcut-ref="performance"/>
			<aop:after-throwing method="afterThrowing" pointcut-ref="performance"/>
			<aop:around method="round" pointcut-ref="performance"/>
		</aop:aspect>
		
	</aop:config>
	
	
</beans>

 

关于通知调用的顺序(无异常),前置通知--->环绕通知的前置操作--->调用目标对象的方法--->后置通知--->方法返回通知--->环绕通知的后置操作

异常情况下:前置通知--->环绕通知的前置操作--->调用目标对象方法--->后置通知--->抛出通知

 

为通知传递参数:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:util="http://www.springframework.org/schema/util"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		   http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.springframework.org/schema/util
           http://www.springframework.org/schema/util/spring-util-3.0.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

	<bean id="myAdvisor" class="org.robbie.test.spring.beans.MyAdvisor"></bean>
	
	<bean id="band" class="org.robbie.test.spring.beans.Band"></bean>
	
	<aop:config>
		<aop:aspect ref="myAdvisor">
			<aop:pointcut expression="execution(* *..*.*(String)) and args(params)" id="performance"/>
			<aop:before method="before" pointcut-ref="performance" arg-names="params"/>
		</aop:aspect>
	</aop:config>
	
	
</beans>

 前置通知写法:

public void before(String params){
        System.out.println("before " + params);
}

 目标方法:

public void play(String params){
        System.out.println("I am playing " + params);
}

 这样配置之后目标方法的参数将会传递到前置通知里

 

为目标对象引入新功能:

因为JAVA不是动态语言,在没有修改类定义的情况下,是不允许新增方法和属性的(动态语言可以实现,例如ruby),一旦编译完成,就很难添加新的类和方法了,但是使用spring aop的引入功能可以实现,spring 引入的核心理念是代理对象既代理了目标对象,实现了目标对象的方法,又实现了新的接口,当调用新的接口方法时,代理对象把调用委托给了实现新接口的实现类,由新的实现类进行调用,实际上通过引入方式产生的代理是多个对象的结合体,既有目标对象,也有新的接口实现类对象。

 

定义新接口:

package org.robbie.test.spring.inf;

public interface NewInterface {
	void newMethod();
}

 定义新接口的实现类:

package org.robbie.test.spring.beans;

import org.robbie.test.spring.inf.NewInterface;

public class NewImpl implements NewInterface{

	@Override
	public void newMethod() {
		System.out.println("new impl");
	}

}

 配置:

<aop:config>
    <aop:aspect ref="myAdvisor">
	<aop:declare-parents types-matching="*" implement-interface="org.robbie.test.spring.inf.NewInterface" default-impl="org.robbie.test.spring.beans.NewImpl"/>
    </aop:aspect>
</aop:config>

 declare-parent元素中types-matching指定要需要进行引入的类,implement-interface指定需要引入的类将要实现的新接口(添加新方法),default-impl指定新接口的实现类。当引入完成后,新完成的类既具有原来的方法同时也具有新接口的方法。default-impl也可以改写成delegate-ref,detegate-ref是引用的spring的bean

 

基于注解的切面:

package org.robbie.test.spring.beans;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAdvisor{
	
	@Pointcut("execution(* *..*.*(..))")
	public void performance(){
		
	}
	
	@Before("performance()")
	public void before(){
		System.out.println("before ");
	}
	
	@After("performance()")
	public void after(){
		System.out.println("after ");
	}
	
	@AfterThrowing("performance()")
	public void afterThrowing(){
		System.out.println("afterThrowing");
	}
	
	@AfterReturning("performance()")
	public void afterReturning(){
		System.out.println("afterReturning");
	}
	
	@Around("performance()")
	public void round(ProceedingJoinPoint joinPoint) throws Throwable{
		System.out.println("round before");
		joinPoint.proceed();
		System.out.println("round after");
	}

}

 增加配置:

<aop:aspectj-autoproxy />

 

基于注解的切面传递参数:

package org.robbie.test.spring.beans;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAdvisor{
	
	@Pointcut("execution(* *..*.*.*(String)) && args(params)")
	public void performance(String params){
		
	}
	
	@Before("performance(params)")
	public void before(String params){
		System.out.println("before " + params);
	}
	
	

}

 

基于注解的引入:

使用@DeclareParents:

package org.robbie.test.spring.beans;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.robbie.test.spring.inf.NewInterface;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAdvisor{
	
	@DeclareParents(value="*", defaultImpl = NewImpl.class)
	private NewInterface newInterface;
	

}

 value属性为所要匹配的类,注解标注在一个接口上,该接口是对目标对象新增的接口,实现由defaultImpl属性提供

 

注入AspectJ切面:

 相对于spring AOP,AspectJ的功能更加强大,不仅支持方法的拦截,还能拦截属性,构造器等,AspectJ是扩展了JAVA语言的AOP框架,详情见AspectJ项目,要把AspectJ的切面描述注入为spring bean需要进行如下配置:

<bean class="org.robbie.acpectj.MyAspect" factory-method="acpectOf"></bean>	

 其中MyAspect为AspectJ编写的切面,要声明成spring的bean,需要用factory-method指定工厂产生方法,aspectJ所有类默认都提供aspectOf方法用于返回实例,这样就完成了AspectJ在spring中的声明,之后就能像普通的spring bean一样进行操作了。

 MyAspect示例:

public aspect MyAspect{
	
	public MyAspect(){}
	
	pointcut performance() : execution(* *..*.*(..))
	
	before() returning() : performance(){
		System.out.println("aspect");
	}
	
}

 

猜你喜欢

转载自dynamicman.iteye.com/blog/2062032
今日推荐