Spring入门3.AOP编程
代码下载: 链接: http://pan.baidu.com/s/11mYEO 密码: x7wa
前言:
前面学习的知识是Spring在Java项目中的IoC或DJ,这些是Spring中的基础,我们进一步学习Spring的其他知识,IoC是Spring的核心内容之一,另一个同等重要的内容便是AOP,也就是所谓的面向切面编程的知识,也许现在你不知道这是个什么东西,其实我只是之前听说过,没有具体的了解过,所以一起学习AOP的知识。
本章的主要知识:
AOP基本概念、Advice类的编写、切面的不同类型和配置切面的方法、Spring对于事务管理的支持、事物编程式事物管理以及其配置方式。(感觉是不是很高深啊,我也是第一次接触,不用害怕,我们都掌握了很好的Java编程基础,这些都是基于Java语言的,就不会有太大的困难)
1. Spring AOP
AOP(Aspect Oriented Programming),面相切面编程,他作为面相对象编程的一种补充,是一种比较成熟的编程思想。AOP和OOP相辅相成,面相对象编程将程序分为不同层次的对象,面相切面编程将程序运行的过程分解成为各个切面,可以认为,面相对象编程是从静态角度考虑程序的结构,而面相切面编程则是从动态的角度去考虑程序的运行过程。
利用继承的思想,可以实现代码的复用,但是随着软件规模的增加,应用的升级,软件中就会出现很多重复的代码,而且这些事无法通过继承的方式进行重用和管理的,在系统的升级和管理上带来诸多的不便。AOP是被称为一种“横切”技术,剖开封装对象的内部,并将那些影响了多个类的行为封装到一个重用的模块中,被称之为Aspect,也就是切面。通过切面可以将那些和业务无关的缺位业务模块共同调用的逻辑封装起来,从而减少了系统的重复代码,降低了模块之间的耦合度,有利于系统的可维护性和扩展性。
AOP把软件系统分为两个部分:核心关注点和横切关注点。核心关注点是业务处理的主要流程,而横切关注点是于核心关注点无关的部分,常常发生在核心关注点的周围,并且代码类似,比如日志记录,权限检查等等。
横切关注点虽然于核心关注点的实现无关,但确是一种更为通用的业务,横切关注点离散的穿插在核心业务中,导致系统中的每一个模块都会和这些业务有很高的依赖心。OOP提高了代码的重用,AOP将分散在业务逻辑中的相同代码,通过横向切割的方式抽取成一个独立的模块,使得业务逻辑更加简洁
AOP的核心思想:将程序中的商业逻辑和对其提供的通用服务进行分离。
2.AOP术语
连接点(joinpoint):一个类或者一段程序代码拥有一些具有边界性质的特定点,就是连接点,实际上,就是程序执行到某个特定的位置,比如类初始化前,初始化之后,某个方法调用前,调用后,异常处理等等。通常有很多的连接点,比如方法的调用前,调用后,抛出异常等等,Spring框架仅仅支持函数的连接点。
切入点(PointCut):被增强的连接点,AOP通过切入点定位到特定的连接点,当某个连接点满足指定的条件,该连接点将被添加增强(Advice),那么该连接点就成为一个切入点。
增强(Advice):织入到目标类特定连接点的一段程序代码。由于增强既包含了用于添加到目标连接点上的一段执行逻辑,又包含了用于定位连接点的方位信息,所以在Spring中提供的增强接口都是带方位名的,BeforeAdvice, AfterAdvice.
目标对象(Target):增强被组织的目标类。如果没有AOP,目标业务类需要自己实现所有的逻辑,借助AOP框架技术,业务类可以只实现核心业务逻辑,而日志、事务管理等横切关注点可以使用AOP动态组织到特定的连接点上。
引入(Introduction):一种特殊的Advice,他为类添加了一些属性和方法,这样即使一个业务原本没有实现某个接口,通过AOP的引入功能,也可以动态为该业务添加接口的实现逻辑,让业务类成为这个接口的实现类。
织入(Weaving):将Advice添加到目标类具体连接点的过程。他将目标类、Advice整合在一起,根据不同的实现技术,AOP有三种不同的实现方式:编译期织入,通常要求使用特殊的Java编译期;类装载期织入,要求使用特殊的类状态器;动态代理织入,在运行期为目标添加Advice并且创建一个目标类型的子类以及对象。Spring采用的是动态代理的方式。
代理(Proxy):一个类被AOP织入Advice之后,就会产生一个结果类,该结果类就是融合了目标类和Advice逻辑的代理类,根据不同的代理方式,该结果类既可能是和目标类有着相同接口的类,也可能是目标类的子类,所以可以使用调用目标类的方式调用该结果类。在Spring中,AOP可以是JDK的动态代理,也可以是CGLIB代理。当明确指定目标类实现何种接口的时,Spring使用动态代理,否则使用CGLIB进行字节码增强。
切面(Aspect):切面是由切入点和Advice组成。既包括Advice逻辑的定义,也包括了切入点的定义,SpringAOP是负责实施切面的框架,将切面所定义的Advice组织到切面指定的连接点中。
AOP编程中,开发人员需要了解的有三个方面:
定义普通的业务类、
定义切入点(一个切入点可以很且多个业务逻辑)、
定义Advice(Advice就是在AOP框架为普通业务组件织入的处理逻辑)
对于AOP,最关键的就是定义切入点(PointCut)和Advice,定义了合适的PointCut和Advice,Spring会自动生成AOP代理。
3.Advice类型
根据目标类连接点位置的不同,Spring支持五中类型的Advice:
前置Advice:在某个连接点方法执行之前,如果没有异常,则该连接点一定会被执行,在Spring框架中,BeforeAdvice接口表示前置Advice,Spring只支持方法级别的Advice,所以也可以使用MethodBeforeAdvice接口来表示前置Advice,用于在目标方法执行之前实施
后置Advice:指的是连接点方法无论在任何情况下推出时所执行的Advice,也就是无力啊是连接点方法是正常退出,还是抛出异常,都会执行Advice。Spring AOP使用AfterAdvice。
返回后Advice:连接点方法正常执行退出,Spring中使用的AfterReturnAdvice
抛出异常Advice:抛出异常后执行的Advice,Spring中使用的是ThrowsAdvice
环绕Advice:指的是包围连接点的方法的Advice,这一种Advice比较强大,可以代替前面所有的advice. Spring中使用的是MethodInterceptor接口表示。在目标方法执行前后实施的Advice。
SpringAOP配置方式:
基于XML配置文件的管理方式:使用Spring配置文件来定义切入点和Advice
基于Annotation的零配置方式:使用@Aspect @Pointcut的方式定义切入点和Advice
配置Advice的时候主要依赖一下几个元素:
<aop:before../> <aop:after ../> <aop:after-returning/> <aop:after-throwing><aop:around>
这些元素都不支持子元素,所以使用的是属性的方式,一些属性如下:
pointcut: 指定一个切入点表达式,Spring将在匹配表达式的连接点时织入该Advice
pointcut-ref: 指定一个已经存在的切入点的名称。
method:该属性指定一个方法名称,它对应切面中的定义的Advice逻辑方法
throwing:该属性只针对<after-throwing>有效,用于指定一个形参的名字,通过形参可以访问目标方法抛出的异常传递
returning:仅针对<after-reurning 有效,用于指定一个形参名字,AfterReturning的处理方法,可以使用形参访问目标方法的返回值。
4.基于XMl配置文件的Spring AOP
在Spring的配置文件中,所有切面(Aspect)、切入点(Pointcut)和Advice都必须在<aop:config/>元素内部定义。在<beans></<beans>中可以包含多个<aop:config>元素,一个<aop:config> 可以包含多个<pointcut> <advisor><aspect>,并且这三个元素必须是按照这个顺序来定义。
配置切面
通过<aop:aspect../>元素进行声明的,该元素必须放在<aop:config../>中。使用<aop:aspect../>来配置切面的时候,实际上是将一个已有的Spring Bean转换成为切面Bean,所以首先要定义一个普通的SpringBean。由于切面Bean可以当做一个普通的Spring Bean 来配置,所以可以为该切面Bean配置依赖注入,当切面Bean定义完成之后,通过<aop:aspect../>元素中使用ref属性引用该bean,就可以将一个普通的bean转换成为切面bean。
<aop:aspect>需要指定的属性有
id(切面标志名)、
ref 指定该属性引用的普通bean作为切面bean、
order(指定该切面bean的优先级,order值越小,该切面对应的优先级越高)
创建一个切面类:
public class AspectBean {
public void checkAuth(){
System.out.println("AspectBean.checkAuth()");
}
public void release(){
System.out.println("AspectBean.release()");
}
public void log(Object result){
if(result ==null){
System.out.println("database no data you want");
}else{
System.out.println("database has data you want");
}
}
public void processException(Throwable ex){
System.out.println("Exception information:" + ex.getMessage());
}
public void processInTrans(ProceedingJoinPoint joinpoint) throws Throwable {
System.out.println("start transtraction...");
joinpoint.proceed();
System.out.println("end transtraction....");
}
}
在该切面类中,一共有五个方法,最后一个是表示满足条件的时候,目标类方法进行相应的事务处理过程。至于以上方法如何被调用,这些和Advice配置有关。
通过配置<aop:aspect../>把上面创建的AspectBean类转换成为切面bean:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd"
default-lazy-init="true">
<bean id="aspectBean" class="com.yang.aspect.AspectBean"/>
<aop:config>
<aop:aspect id="adviceAspect" ref="aspectBean">
<!-- 还没有添加Advice配置信息 -->
</aop:aspect>
</aop:config>
</bean>
上述配置信息利用applicationContext.xml中的<aop:config></aop:config>用<aop:aspect></aop:aspect>声明一个id是adviceAspect的切面bean,然后利用ref属性引用id为aspectBean的Spring普通的bean。但是还暂时没有配置Aspect的信息,所以暂时是没有切面作用的。
更进一步,学习Advice配置信息
创建一个目标接口UserService,然后分别说明之前介绍的五中Advice配置文件中的配置方法:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"
default-lazy-init="true">
<!—这一句十分重要,如果没有这一句,使用XML配置的AOP,在编译期间,就会因为加载applicationContext配置文件出错,但是如果使用的是注解的方式,虽然不会导致程序崩溃,但是Spring的AOP根本不会骑任何作用,就是不会执行AOP的代码-->
<aop:aspectj-autoproxy/>
<!---->
<bean id="aspectBean" class=" com.yang.aspect.AspectBean"></bean>
<bean id="userService" class="com.yang.service.impl.UserServiceImpl"></bean>
<aop:config proxy-target-class="true">
<aop:aspect id="myadvice" ref="aspectBean">
<aop:before method="checkAuth" pointcut="execution(* com.yang.service.UserService.*(..))"/>
<aop:after-returning method="log" pointcut="execution(* com.yang.service.UserService.select(..))" returning="result"/>
<aop:after-throwing method="processException" pointcut="execution(* com.yang.service.UserService.*(..))" throwing="ex"/>
<aop:around method="proceedInTrans" pointcut="execution(* com.yang.service.UserService.*(..))"/>
<aop:after method="release" pointcut="execution(* com.yang.service.UserService.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
做一下简单的讲解:
在Spring AOP的配置文件中,使用<aop:aspectj-autoproxy/>是十分重要的,不然会报错。同时对于参数传递,在returning 和throwing的参数名称必须是和函数中的参数名称保持一致,否则也会报错。
对于配置切面的时候,pointcut的配置方式中execution(* com..*.*service.*(..))指的是以com开头的以service结尾的包中的所有class中的所有方法,参数可以是任意的,都会调用该Advice这一个配置的方法。
同时对于around,其中方法的执行是在切入点函数执行之前运行前面的代码,执行之后运行后面的代码。
public void processInTrans(ProceedingJoinPoint joinpoint) throws Throwable {
System.out.println("start transtraction...");
joinpoint.proceed();
System.out.println("end transtraction....");
}
}
代理技术:之前介绍的代理技术有两种形式,CGLIB和JDK动态代理机制。在<aop:config>中使用的是属性proxy-target-class表示的,当设置成为true的时候,切面使用的是CGLIB代理的形式;而当设置成为false的时候,使用的是JDK动态代理的机制。对于不通过的<aop:config>可以使用不同的代理技术。
YangTengfei
2013.11.26