Spring中AOP实现

1.什么是SpringAOP

什么是aop:Aspect Oriented Programming的缩写,面向切面编程,通过预编译和动态代理实现程序功能的

统一维护的一种技术

主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等

2.SpringAOP框架的用途

提供了声明的企业服务,特别是EJB的替代服务的声明

允许用户控制自己的方面,以完成OOP和AOP的互补使用

OOP:模拟真实的世界,一切皆是对象

3.AOP的实现方式

下边这两种Spring都是支持的

3.1预编译

-AspectJ  完整的面向切面编程解决方案--》spring不是完整的解决方案,不过spring提供比较好的实现方式,当然spring是同时也是支持这种方式的,这也是一种常用的方式

3.2运行期间动态代理(JDK动态代理,CGLib动态代理)

-SpringAop,JbossAop

Spring的AOP使用纯java实现,无需特殊的编译过程,不需要控制类的加载器层次,目前只支持方法的执行的连接点(通知Spring Bean某个方法执行)

不是为了提供完整AOP实现;而是侧重于一种AOP于IOC容器之间的整合,SpringAOP不会AspectJ(完整的AOP解决方案)竞争

Spring没有使用AspectJ的时候,也可以通过如下方式实现AOP

Spring AOP 默认使用标准的JavaSE动态代理作为AOP代理,这使得任何接口(或者集合)都可以被代理

Spring AOP 中也可以使用CGLIB代理(如果一个业务对象没有实现一个接口)

有接口的:使用JDK的动态里

无接口的:使用CGLIB代理

4.SpringAOP中的一些概念

在实际使用SpringAOP之前,了解他的概念是必不可少的一个过程,

SpringAOP主要围绕以下概念展开:

切面(Aspect)一个关注点的模块化,这个关注点可能会横切多个对象

连接点(Joinpoint)程序执行过程中某个特定的连接点

通知(Advice) 在切面的某个特的连接点上执行的动作

切入点(Pointcut)匹配连接的断言,在Aop中通知和一个切入点表达式关联

引入(Intruduction) 在不修改类代码的前提下,为类添加新的方法和属性

目标对象(Target Object) 被一个或者多个切面所通知的对象

Aop代理(AOP Proxy) AOP框架创建的对象,用来实现切面契约(aspect contract)(包括方法执行等)

织入(Weaving)把切面连接到其他的应用程序类型或者对象上,并创建一个被通知的对象,氛围:编译时织入,类加载时织入,执行时织入

通知类型Advice:

前置通知(before advice) 在某个连接点(jion point)之前执行的通知,但不能阻止连接点前的执行(除非抛出一个异常)

返回后通知(after returning advice)在某个连接点(jion point)正常执行完后执行通知

抛出异常通知(after throwing advice) 在方法异常退出时执行的通知

后通知(after(finally) advice)在方法抛出异常退出时候的执行通知(不管正常返回还是异常退出)

环绕通知(around advice) 包围一个连接点(jion point)的通知

切入点Pointcut:SpringAOP占时仅仅支持方法的连接点

execution(public * *(..)) 切入点为执行所有的public方式时

execution(* set*(..)) 切入点执行所有的set开始的方法时

execution(* com.xyz.service.Account.*(..)) 切入点执行Account类的所有方法时

execution(* com.xyz.service..(..))切入点执行com.xyz.service包下的所有方法时

execution(* com.xyz.service...(..)) 切入点执行com.xyz.service包以及其子包的所有的方法时

上边这种方式aspectj和springaop通用的,其他方式可以自己查找资料

5.SpringAOP实现

上边的一些概念,看过后可能还是不懂,可以自行查阅资料,下边我也会说明

我们在Java项目开发中,AOP常见的配置有如下3种:

a.基于SpringAOP容器的实现,使用AspectJ(Spring封装了AspectJ的使用),这是一种比较常用的方式,可以很容易看清代码结构

b.运行期间的动态代理实现,这是一种比较老的实现方式,比较繁琐

c.注解实现,这种方式开发起来简单,但是不利于后期维护,比如说很难找出你所有使用了SpringAOP注解的地方

这里我主要介绍第一种,和第二种的使用

5.1SpringAOP中的容器实现

5.1.1 前置通知 before

某个需要切入的方法之前执行切面中的方法

Spring所有的切面通知都必须放在一个<aop:config>内(可以配置多个<aop:config>元素),每一个<aop:config>可以包含pointcut,adviso

和aspect元素(注意这些元素的出现是由顺序的)

配置文件:spring-aop-schema-advice.xml

声明了切面类,当切入点中匹配到了类名包含BIZ字符串的类时,选取面类中的一个方法,在选取某种Adivice通知,切入该方法的执行过程

其中:aop:aspect 中id的值任意(表意性强)  ref的值为切面类的id值

aop:pointcut 中expression写切入点表达式,说明那个方法可能需要做切入点

aop:before 中method的值为切面中的方法名说明切入那个方法,pointcut-ref的值为切入点的id值

<?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"
 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">

	<!-- 切面类 -->
	<bean id="myAspect" class="com.briup.spring.aop.bean.annotation.aop.MyAspect"></bean>

	<!-- 业务类 -->
	<bean id="aspectBiz" class="com.briup.spring.aop.bean.annotation.aop.AspectBiz"></bean>
	<!-- aop配置 可以配置多个-->
	<aop:config>
	<!-- 切面类 -->
		<aop:aspect id="aspectTest" ref="myAspect">
		<!-- 切入点  标识切入点 aop包下类名包含Biz的类的所有方法-->
			<aop:pointcut expression="execution(* com.briup.spring.aop.bean.annotation.aop.*Biz.*(..))" id="myPointcut"/>
			<!-- 通知 ,通过切入点切入切入切面类中的before方法-->
			<aop:before method="before" pointcut-ref="myPointcut"/>
		</aop:aspect>
	</aop:config>
</beans>

切面类:

切面类中的某个方法,一般是用于切入业务类中的某个方法在某种状态时切入

/*
 * 声明一个切面类
 * */
public class MyAspect {

	public void before(){
		System.out.println("aspect before");
	}

}

业务类:

//业务类
public class AspectBiz {

	public void biz(){
		System.out.println("Aspect biz");
	}

}

测试:

	@Test
	public void aspectBefore(){
		ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("com/briup/spring/chap4/spring-aop-schema-advice.xml");
		AspectBiz aspectBiz = (AspectBiz) ac.getBean("aspectBiz");
		aspectBiz.biz();
	}

结果:

aspect before
Aspect biz

通过结果可以看出,前置通知在业务类AspectBiz的方法biz执行之前执行了before方法

5.1.2后置通知after-returning

某个需要切入的方法执行完成之后执行切面中指定的方法

配置文件:spring-aop-schema-advice.xml

<aop:config>
	<!-- 切面类 -->
		<aop:aspect id="aspectTest" ref="myAspect">
		<!-- 切入点  标识切入点 aop包下类名以Biz结尾的类的所有方法-->
			<aop:pointcut expression="execution(* com.briup.spring.aop.bean.annotation.aop.*Biz.*(..))" id="myPointcut"/>
			<!-- 通知 ,通过切入点切入切入切面类中的before方法 在执行切入点指定的方法之前执行 -->
			<aop:before method="before" pointcut-ref="myPointcut"/>
			<!-- 返回之后的通知 -->
			<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
		</aop:aspect>
</aop:config>

切面类:

添加afterReturning方法

/*
 * 声明一个切面类
 * */
public class MyAspect {

	public void before(){
		System.out.println("aspect before");
	}

	public void afterReturning(){
		System.out.println("aspect afterReturning");
	}

}

结果:

aspect before
Aspect biz
aspect afterReturning

结果可以看出,后置通知会在业务方法的执行之后

5.1.3异常通知after-throwing

在方法抛出异常后的通知

配置文件:spring-aop-schema-advice.xml

	<aop:config>
	<!-- 切面类 -->
		<aop:aspect id="aspectTest" ref="myAspect">
		<!-- 切入点  标识切入点 aop包下类名以Biz结尾的类的所有方法-->
			<aop:pointcut expression="execution(* com.briup.spring.aop.bean.annotation.aop.*Biz.*(..))" id="myPointcut"/>
			<!-- 通知 ,通过切入点切入切入切面类中的before方法 在执行切入点指定的方法之前执行 -->
			<aop:before method="before" pointcut-ref="myPointcut"/>
			<!-- 返回之后的通知 -->
			<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
			<!--  -->
			<aop:after-throwing method="afteThrowing" pointcut-ref="myPointcut"/>
		</aop:aspect>
	</aop:config>

切面类:

/*
 * 声明一个切面类
 * */
public class MyAspect {

	public void before(){
		System.out.println("aspect before");
	}

	public void afterReturning(){
		System.out.println("aspect afterReturning");
	}

	public void afteThrowing(){
		System.out.println("aspect afteThrowing");
	}
}

业务类:

修改为如下:

	public void biz(){
		System.out.println("Aspect biz");
		throw new RuntimeException(); //出现异常的时候afteThrowing才会执行
	}

测试和结果:

aspect before
Aspect biz
aspect afteThrowing
上边的结果由于抛出异常后,不会正常的返回所有没有aspect afterReturning输出

5.1.4最终通知after(finally) advice

不管方法是否会抛出异常都会通知,就像try...catch..finallly

配置文件:spring-aop-schema-advice.xml

	<aop:config>
	<!-- 切面类 -->
		<aop:aspect id="aspectTest" ref="myAspect">
		<!-- 切入点  标识切入点 aop包下类名以Biz结尾的类的所有方法-->
			<aop:pointcut expression="execution(* com.briup.spring.aop.bean.annotation.aop.*Biz.*(..))" id="myPointcut"/>
			<!-- 通知 ,通过切入点切入切入切面类中的before方法 在执行切入点指定的方法之前执行 -->
			<aop:before method="before" pointcut-ref="myPointcut"/>
			<!-- 返回之后的通知 -->
			<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
			<!--  -->
			<aop:after-throwing method="afteThrowing" pointcut-ref="myPointcut"/>
			<!-- after(finally) advice 不管是否抛出异常,最后都会执行的方法 -->
			<aop:after method="after" pointcut-ref="myPointcut"/>
		</aop:aspect>
	</aop:config>

切面类:

/*
 * 声明一个切面类
 * */
public class MyAspect {

	public void before(){
		System.out.println("aspect before");
	}

	public void afterReturning(){
		System.out.println("aspect afterReturning");
	}

	public void afteThrowing(){
		System.out.println("aspect afteThrowing");
	}

	public void after(){
		System.out.println("aspect after(finally)");
	}
}

业务类:

//业务类
public class AspectBiz {

	public void biz(){
		System.out.println("Aspect biz");
		throw new RuntimeException();
	}
}

测试结果:

aspect before
Aspect biz
aspect afteThrowing
aspect after(finally)

从结果中可以看出,抛出异常后,切面类中的after方法还是可以执行

如果业务类如下:没有异常,
public class AspectBiz {
	public void biz(){
		System.out.println("Aspect biz");

	}

}

结果:

aspect before
Aspect biz
aspect after(finally)

由以上结果可以看到,不管怎么样after方法还是会执行,很像try...catch..finally

5.1.5环绕通知around

在方法的执行前后执行通知

配置文件:spring-aop-schema-advice.xml

	<aop:config>
	<!-- 切面类 -->
		<aop:aspect id="aspectTest" ref="myAspect">
		<!-- 切入点  标识切入点 aop包下类名以Biz结尾的类的所有方法-->
			<aop:pointcut expression="execution(* com.briup.spring.aop.bean.annotation.aop.*Biz.*(..))" id="myPointcut"/>
			<!-- 通知 ,通过切入点切入切入切面类中的before方法 在执行切入点指定的方法之前执行 -->
			<aop:before method="before" pointcut-ref="myPointcut"/>
			<!-- 返回之后的通知 -->
			<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
			<!--  -->
			<aop:after-throwing method="afteThrowing" pointcut-ref="myPointcut"/>
			<!-- after(finally) advice 不管是否抛出异常,最后都会执行的方法 -->
			<aop:after method="after" pointcut-ref="myPointcut"/>
			<!-- adroun advice 环绕通知,方法的第一参数必须是ProceedingJoinPoint类型 -->
			<aop:around method="around" pointcut-ref="myPointcut"/>
		</aop:aspect>
	</aop:config>

切面类:

可以看出,环绕方法的第一参数必须四ProceedingJoinPoint类型

/*
 * 声明一个切面类
 * */
public class MyAspect {

	public void before(){
		System.out.println("aspect before");
	}

	public void afterReturning(){
		System.out.println("aspect afterReturning");
	}

	public void afteThrowing(){
		System.out.println("aspect afteThrowing");
	}

	public void after(){
		System.out.println("aspect after(finally)");
	}

	public void around(ProceedingJoinPoint joinPoint){
		Object object = null;
		try{
			System.out.println("aspect around 1"); //方法执行前
			object = joinPoint.proceed();  //代表业务方法的执行
			System.out.println("aspect around 1"); //方法执行后
		}catch(Throwable e){
			e.printStackTrace();
		}
	}
}

测试结果:

aspect around 1
Aspect biz
aspect around 1

从结果可以看出,joinPoint.proceed其实就是执行业务方法,我们可以在其之前做和之后做一些操作

5.1.6通知中的参数传递

这里我们列举环绕通知中的参数参数传递,其他通知也是同样的方式。

配置文件:spring-aop-schema-advice.xml

<?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"
 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">

	<!-- 切面类 -->
	<bean id="myAspect" class="com.briup.spring.aop.bean.annotation.aop.MyAspect"></bean>

	<!-- 业务类 -->
	<bean id="aspectBiz" class="com.briup.spring.aop.bean.annotation.aop.AspectBiz"></bean>
	<!-- aop配置 可以配置多个-->
	<aop:config>
	<!-- 切面类 -->
		<aop:aspect id="aspectTest" ref="myAspect">
		<!-- 切入点  标识切入点 aop包下类名以Biz结尾的类的所有方法-->
			<aop:pointcut expression="execution(* com.briup.spring.aop.bean.annotation.aop.*Biz.*(..))" id="myPointcut"/>
			<!-- 通知 ,通过切入点切入切入切面类中的before方法 在执行切入点指定的方法之前执行 -->
			<aop:before method="before" pointcut-ref="myPointcut"/>
			<!-- 返回之后的通知 -->
			<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
			<!--  -->
			<aop:after-throwing method="afteThrowing" pointcut-ref="myPointcut"/>
			<!-- after(finally) advice 不管是否抛出异常,最后都会执行的方法 -->
			<aop:after method="after" pointcut-ref="myPointcut"/>
			<!-- adroun advice 环绕通知,方法的第一参数必须是ProceedingJjoinPoint类型 -->
			<!-- <aop:around method="around" pointcut-ref="myPointcut"/> -->

			<!-- 参数传递  -->
			<aop:around method="aroundInit" pointcut="execution(* com.briup.spring.aop.bean.annotation.aop.AspectBiz.init(String,int))
			and args(bizName,times)"/>
		</aop:aspect>
	</aop:config>
</beans>

其中,通知中也可以单独指定的pointcut,切入点中,我们单独指定了init方法,且指定了方法的参数类型,和方法参数的名字,其中方法参数名字,可以更实际的方法参数名字不同,比如说init(String biz,int t) 都是可以匹配到的,不过为了维护方便,最好还是都一样。

切面类:

/*
 * 声明一个切面类
 * */
public class MyAspect {

	public void before(){
		System.out.println("aspect before");
	}

	public void afterReturning(){
		System.out.println("aspect afterReturning");
	}

	public void afteThrowing(){
		System.out.println("aspect afteThrowing");
	}

	public void after(){
		System.out.println("aspect after(finally)");
	}

	public void around(ProceedingJoinPoint joinPoint){
		Object object = null;
		try{
			System.out.println("aspect around 1"); //方法执行前
			object = joinPoint.proceed();  //代表业务方法的执行
			System.out.println("aspect around 2"); //方法执行后
		}catch(Throwable e){
			e.printStackTrace();
		}
	}

	//AOP中参数的传递
	public void aroundInit(ProceedingJoinPoint joinPoint,String bizName,int times){
		System.out.println(bizName+"--"+times);
		Object object = null;
		try{
			System.out.println("aspect around 1"); //方法执行前
			object = joinPoint.proceed();  //代表业务方法的执行
			System.out.println("aspect around 1"); //方法执行后
		}catch(Throwable e){
			e.printStackTrace();
		}
	}
}

业务类:

public class AspectBiz {

	public void biz(){
		System.out.println("Aspect biz");
	}

	public void init(String bizName,int times){
		System.out.println("aspectBiz init:"+bizName+"  "+times);
	}

}

测试:

	@Test
	public void aspectAround(){
		ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("com/briup/spring/chap4/spring-aop-schema-advice.xml");
		AspectBiz aspectBiz = (AspectBiz) ac.getBean("aspectBiz");
		aspectBiz.init("init", 3);
	}

测试结果:

aspect before
init--3
aspect around 1
aspectBiz init:init  3
aspect around 1
aspect after(finally)
aspect afterReturning
AfterClass 标注的方法 会最后执行

可以看到,参数比顺利的传送过去

6.Advisor

Advisor就像一个小的自包含,只有一个advice切面通过一个bean标识,并且必须实现一个advice接口,同时advisor也可以很好的利用aspectJ的切入点表达式Spring通过配置文件中 <aop:advisor>元素支持advisor实际使用中,大多数情况下它会和transactional advice配合使用,用于事务控制

配置文件使用案例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	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">

	<bean name="sessionFactory"
		class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="configLocation">
			<!-- 路径位于src下 -->
			<value>classpath:hibernate.cfg.xml</value>
		</property>
	</bean>

	<!-- 事物配置 -->
	<bean id="txManager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory">
			<ref local="sessionFactory" />
		</property>
	</bean>

	<!-- advisor配置 配置事务处理的Bean,定义切面(advice)
		由于getxxx,queryxxx,findxxx不涉及事务,可以设置read-only为true
	-->
	<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="get*" read-only="true" />
			<tx:method name="query*" read-only="true" />
			<tx:method name="find*" read-only="true" />
			<tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
			<tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
			<tx:method name="del*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
			<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
			<tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
		</tx:attributes>
	</tx:advice>

	<!-- aop配置 -->
	<aop:config>
	<!-- 给impl包下的所有的方法配置adivce -->
		<aop:pointcut expression="excution(* com.briup.spring.aop.bean.annotation.service.impl..(..))" id="serviceMethod" />
		<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"  />
	</aop:config>

</beans>

关于spring的事务控制将会在另外一篇文章中介绍。

7.SpringAOP代理实现

这种实现方式,主要使用动态代理技术。 有接口的:使用JDK的动态里,无接口的:使用CGLIB代理

这种方式不是很常用

7.1.1ProxyFactoryBean

1.使用SpringAOP代理最关键的是使用org.springframework.aop.framework.ProxyFactoryBean,可以完全控制切入点和通知advice以及他们的顺序

2.使用ProxyFacotryBean或者其他的IOC相关类来创建AOP代理的最重要的好处就是通知切入点可以由IOC来管理

3.被代理的类没有实现任何接口,使用CGLIB代理,否者使用JDK代理

4.通过设置ProxyTargetClass为true可以强制使用CGLIB,(无论是否实现接口)

5.如果目标类实现了一个或者多个接口,那么创建代理的类型将依赖于ProxyFactoryBean的配置

6.如果ProxyFactoryBean的proxyInterfaces属性被设置为一个或者多个全限定接口名,基于JDK的代理被创建

7.如果ProxyFactoryBean的proxyInterfaces属性没有被设置,但是目标类实现类一个或多个接口,那么ProxyFactoryBean将自动检测到这个目标类已经实现了至少一个接口,创建一个基于JDK的代理

7.1.2使用代理实现环绕通知around

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
	<!-- 配置委托类 -->
    <bean name="target" class="com.xxx.spring.aop.bean.UserDaoImpl"></bean>
    <!-- 配置代理对象将来执行的时候,所要执行的处理程序 -->
    <bean name="advice" class="com.xx.spring.aop.bean.AdviceTest"></bean><!-- advice在方法执行前后添加我们的操作 -->
  	<!-- 配置代理对象 -->
    <bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!--proxyInterfaces 如果ProxyFactoryBean的proxyInterfaces属性没有被设置,但是目标类实现类一个或多个接口,
    	那么ProxyFactoryBean将自动检测到这个目标类已经实现了至少一个接口,创建一个基于JDK的代理 -->
    	<property name="proxyInterfaces" value="com.xxx.spring.aop.bean.UserDao"></property>
    	<!-- 注入目标对象(注入被代理的对象) -->
    	<property name="target" ref="target"></property>
    	<!-- 注入代理对象所要执行的处理程序,通知配置 -->
    	<property name="interceptorNames">
		<list>
			<value>advice</value>
		</list>
	</property>
    </bean>
</beans>

注意:

上边的<property name="proxyInterfaces" value="com.xxx.spring.aop.bean.UserDao">可以不用写,Spring的ProxyFactoryBean将会自动检测到

如果被代理的对象target实现了多个接口可以按照如下配置:

<property name="interfaces">
    	<array>
    		<value>com.xxx.spring.aop.bean.UserDao</value>
    	</array>
</property>

value中可以写多个接口

同样:通知interceptorNames如果只用一个通知可以写成<property name="interceptorNames" name="advice">

如果有多个通知可以写成:

	<property name="interceptorNames">
    		<list>
    			<value>advice</value>
    		</list>
    	</property>

但是 通知是不可以省略的,同时推荐结合的写法

上边的委托类即使普通的实现类:即ProxyFactoryBean被代理的对象

<bean name="target" class="com.xxx.spring.aop.bean.UserDaoImpl"></bean>

上边的通知配置

<bean name="advice" class="com.briup.spring.aop.bean.AdviceTest"></bean>

ProxyFactoryBean的配置

需要配置被代理的目标对象,通知,目标类实现的接口(可以省略,被ProxyFactoryBean自动识别)

切面通知实现类

实现MethodInterceptor就可以实现环绕通知,我们可以在方法的目标对象类的方法执行前后加入处理逻辑

import java.util.Date;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class AdviceTest implements MethodInterceptor{

	@Override
	public Object invoke(MethodInvocation method) throws Throwable {
		System.out.println("方法开始执行"+new Date());
		method.proceed();
		System.out.println("方法执行完毕"+new Date());
		return null;
	}
}

目标类:

//委托类
public class UserDaoImpl implements UserDao {

	@Override
	public void saveUser() {
		System.out.println("保存用户");
	}

	@Override
	public void deleteUser() {
		System.out.println("删除用户");
	}

	@Override
	public void updateUser() {
		System.out.println("更新用户");
	}

}

接口:

public interface UserDao {
	public abstract void saveUser();
	public abstract void deleteUser();
	public abstract void updateUser();
}

测试:

	@Test
	public void advice(){
		BeanFactory factory = new ClassPathXmlApplicationContext("com/xxx/spring/chap2/advice.xml");
		UserDao userDao = factory.getBean("proxy", UserDao.class);
		userDao.saveUser();
		userDao.deleteUser();
		userDao.updateUser();
	}

测试结果:

方法开始执行Sun Sep 11 21:02:12 CST 2016
保存用户
方法执行完毕Sun Sep 11 21:02:12 CST 2016
方法开始执行Sun Sep 11 21:02:12 CST 2016
删除用户
方法执行完毕Sun Sep 11 21:02:12 CST 2016
方法开始执行Sun Sep 11 21:02:12 CST 2016
更新用户
方法执行完毕Sun Sep 11 21:02:12 CST 2016

7.1.3使用动态代理实现前置通知before

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
	<!-- 配置委托类 -->
    <bean name="target" class="com.xxx.spring.aop.bean.UserDaoImpl"></bean>
    <!-- 配置代理对象将来执行的时候,所要执行的处理程序 -->
    <bean name="before" class="com.xxx.spring.aop.bean.BeforeTest"></bean>
  	<!-- 配置代理对象 -->
    <bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- 注入目标对象(注入被代理的对象) -->
    	<property name="target" ref="target"></property>
    	<!-- 注入代理对象需要实现的所有接口 -->
    	<property name="interfaces">
    		<array>
    			<value>com.xxx.spring.aop.bean.UserDao</value>
    		</array>
    	</property>
    	<!-- 注入代理对象所要执行的处理程序 -->
    	<property name="interceptorNames">
    		<array>
    			<value>before</value>
    		</array>
    	</property>
    </bean>
</beans>

切面前置通知实现类:

实现MethodBeforeAdvice

import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class BeforeTest implements MethodBeforeAdvice{

	@Override
	public void before(Method method, Object[] obj, Object object)
			throws Throwable {
		System.out.println("version 1.0 author tom "+method.getName()+" is execute");
	}

}

测试:

	@Test
	public void before(){
		BeanFactory factory = new ClassPathXmlApplicationContext("com/xxx/spring/chap2/before.xml");
		UserDao userDao = factory.getBean("proxy", UserDao.class);
		userDao.saveUser();
		userDao.deleteUser();
		userDao.updateUser();
	}
	

结果:

version 1.0 author tom saveUser is execute
保存用户
version 1.0 author tom deleteUser is execute
删除用户
version 1.0 author tom updateUser is execute
更新用户

7.1.4后置通知afterReturning

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
	<!-- 配置委托类 -->
    <bean name="target" class="com.xxx.spring.aop.bean.UserDaoImpl"></bean>
    <!-- 配置代理对象将来执行的时候,所要执行的处理程序 -->
    <bean name="after" class="com.xxx.spring.aop.bean.AfterTest"></bean>
  	<!-- 配置代理对象 -->
    <bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- 注入目标对象(注入被代理的对象) -->
    	<property name="target" ref="target"></property>
    	<!-- 注入代理对象需要实现的所有接口 -->
    	<property name="interfaces">
    		<array>
    			<value>com.xxx.spring.aop.bean.UserDao</value>
    		</array>
    	</property>
    	<!-- 注入代理对象所要执行的处理程序 -->
    	<property name="interceptorNames">
    		<array>
    			<value>after</value>
    		</array>
    	</property>
    </bean>
</beans>

切面后置通知:

实现 AfterReturningAdivce接口

import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class AfterTest  implements AfterReturningAdvice {

	@Override
	public void afterReturning(Object arg0, Method arg1, Object[] arg2,
			Object arg3) throws Throwable {
		System.out.println(arg1.getName()+" is over!");
	}
}

测试:

	@Test
	public void after(){
		BeanFactory factory = new ClassPathXmlApplicationContext("com/xxx/spring/chap2/after.xml");
		UserDao userDao = factory.getBean("proxy", UserDao.class);
		userDao.saveUser();
		userDao.deleteUser();
		userDao.updateUser();
	}

测试结果:

保存用户
saveUser is over!
删除用户
deleteUser is over!
更新用户
updateUser is over!

7.1.5异常通知throw

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
	<!-- 配置委托类 -->
    <bean name="target" class="com.briup.spring.aop.bean.UserDaoImpl"></bean>
    <!-- 配置代理对象将来执行的时候,所要执行的处理程序 -->
    <bean name="throws" class="com.briup.spring.aop.bean.ThrowsAdiviceTest"></bean>
  	<!-- 配置代理对象 -->
    <bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- 注入目标对象(注入被代理的对象) -->
    	<property name="target" ref="target"></property>
    	<!-- 注入代理对象需要实现的所有接口 -->
    	<property name="interfaces">
    		<array>
    			<value>com.briup.spring.aop.bean.UserDao</value>
    		</array>
    	</property>
    	<!-- 注入代理对象所要执行的处理程序 -->
    	<property name="interceptorNames">
    		<list>
    			<value>throws</value>
    		</list>
    	</property>
    </bean>
</beans>

异常通知切面:

参数中必须有Throwable的子类,前边的参数afterThrowing([Method, args, target], subclassOfThrowable)[]号中的参数可选

import java.lang.reflect.Method;

import org.springframework.aop.ThrowsAdvice;

public class ThrowsAdiviceTest implements ThrowsAdvice{

	public void afterThrowing(Method method,Object[] args,Object target,Exception ex)throws Throwable{//Throwable subclass
		System.out.println("afterThrowing 2 ...."+method.getName()+"   "+ target.getClass().getName());
	}

}

实现类:

	@Override
	public void saveUser() throws RuntimeException{
		System.out.println("保存用户");
		throw new RuntimeException();
	}

测试:

	@Test
	public void throwTest(){
		BeanFactory factory = new ClassPathXmlApplicationContext("com/xxx/spring/chap2/throw.xml");
		UserDao userDao = factory.getBean("proxy", UserDao.class);
		userDao.saveUser();
		userDao.deleteUser();
		userDao.updateUser();
	}

结果:

保存用户
afterThrowing 2 ....saveUser   com.xxx.spring.aop.bean.UserDaoImpl

7.1.6切入点配置pointcut

使用代理方式也可以配置切入点

NameMatchMethodPointcut,根据方法的名字进行匹配

mappedNames匹配的方法名集合

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
	<!-- 配置委托类 -->
    <bean name="target" class="com.briup.spring.aop.bean.UserDaoImpl"></bean>
    <!-- 配置代理对象将来执行的时候,所要执行的处理程序 -->
    <bean name="advice" class="com.briup.spring.aop.bean.AdviceTest"></bean>
    <bean name="throws" class="com.briup.spring.aop.bean.ThrowsAdiviceTest"></bean>
    <!-- 切入点 -->
    <bean name="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
    	<property name="mappedNames">
    		<list>
    			<value>sa*</value>
    		</list>
    	</property>
    </bean>
    <!-- advisor -->
    <bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    	<property name="advice" ref="throws"></property>
    	<property name="pointcut" ref="pointcutBean"></property>
    </bean>
  	<!-- 配置代理对象 -->
    <bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- 注入目标对象(注入被代理的对象) -->
    	<property name="target" ref="target"></property>
    	<!-- 注入代理对象需要实现的所有接口 -->
    	<property name="interfaces">
    		<array>
    			<value>com.briup.spring.aop.bean.UserDao</value>
    		</array>
    	</property>
    	<!-- 注入代理对象所要执行的处理程序 -->
    	<property name="interceptorNames">
    		<list>
    			<value>defaultAdvisor</value>
    			<value>throws</value>
    		</list>
    	</property>
    </bean>
</beans>

如上边切入点配置:

  <!-- 切入点 -->
    <bean name="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
    	<property name="mappedNames">
    		<list>
    			<value>sa*</value>
    		</list>
    	</property>
    </bean>

切面:

    <!-- advisor -->
    <bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    	<property name="advice" ref="advice"></property>
    	<property name="pointcut" ref="pointcutBean"></property>
    </bean>

7.1.7使用匿名的代理对象

使用匿名的代理对象可以将bean的配置到代理里边,这样就不用为target目标对象配置单独的对象,这样可以直接避免目标对象

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
    <!-- 配置代理对象将来执行的时候,所要执行的处理程序 -->
    <bean name="advice" class="com.briup.spring.aop.bean.AdviceTest"></bean>
    <bean name="throws" class="com.briup.spring.aop.bean.ThrowsAdiviceTest"></bean>
    <!-- 切入点 -->
    <bean name="pointcutBean" class="org.springframework.aop.support.NameMatchMethodPointcut">
    	<property name="mappedNames">
    		<list>
    			<value>sa*</value>
    		</list>
    	</property>
    </bean>
    <!-- advisor -->
    <bean id="defaultAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    	<property name="advice" ref="advice"></property>
    	<property name="pointcut" ref="pointcutBean"></property>
    </bean>
  	<!-- 配置代理对象 -->
    <bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    	<!-- 注入目标对象(注入被代理的对象),使用匿名的方式 -->
    	<property name="target">
    	<span style="white-space:pre">	</span><bean name="target" class="com.xxx.spring.aop.bean.UserDaoImpl"></bean>
    	</property>
    	<!-- 注入代理对象需要实现的所有接口 -->
    	<property name="interfaces">
    		<array>
    			<value>com.xxx.spring.aop.bean.UserDao</value>
    		</array>
    	</property>
    	<!-- 注入代理对象所要执行的处理程序 -->
    	<property name="interceptorNames">
    		<list>
    			<value>defaultAdvisor</value>
    			<value>throws</value>
    		</list>
    	</property>
    </bean>
</beans>

7.1.8IntroductionInterceptor

Introduction是个特别的Advice,可以在不修改代码的基础上添加一些方法,可以参见

http://www.iteedu.com/webtech/j2ee/springdiary/35.php

http://go12345.iteye.com/blog/352745

8.自动代理实现

8.1.BeanNameAutoProxyCreator

Spring允许使用自动代理的bean定义,他可以自动代理选定bean,这样我么就不用为代理对象声明接口,或者没有实现接口的时候,使用CGLIB代理

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
	<!-- 配置委托类 -->
    <bean name="target" class="com.xxx.spring.aop.bean.UserDaoImpl"></bean>
    <!-- 配置代理对象将来执行的时候,所要执行的处理程序 -->
    <bean name="before" class="com.xxx.spring.aop.bean.BeforeTest"></bean>
  	<!-- 配置代理对象 -->
  	 <bean name="proxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    	<!-- 注入需要被代理的对象名字,会代理所有以targ开始的bean -->
   		<property name="beanNames">
   			<list>
   				<value>targ*</value>
   			</list>
   		</property>
   		<!-- 注入advice或者advisor -->
   		<property name="interceptorNames">
   			<list>
   				<value>before</value>
   			</list>
   		</property>
    </bean>
</beans>

通过,自动代理,proxy会自动帮我们代理所有以targ开头的目标委托类

实现类:

//委托类
public class UserDaoImpl implements UserDao {
	@Override
	public void saveUser(){
		System.out.println("保存用户");
	}

	@Override
	public void deleteUser() {
		System.out.println("删除用户");
	}

	@Override
	public void updateUser() {
		System.out.println("更新用户");
	}

}

测试:

	@Test
	public void autoAdvisor(){
		BeanFactory factory = new ClassPathXmlApplicationContext("com/xxx/spring/chap2/autoAdvisor.xml");
		UserDao userDao = factory.getBean("target", UserDao.class);//autoAdvisor只能通过委托类的名字来拿
		userDao.saveUser();
		userDao.deleteUser();
		userDao.updateUser();
	}

结果:

version 1.0 author tom saveUser is execute
保存用户
version 1.0 author tom deleteUser is execute
删除用户
version 1.0 author tom updateUser is execute
更新用户

8.2DefaultAdvisorAutoProxyCreator

使用DefaultAdvisorAutoProxyCreator我们可以不用显示的指定advisor的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
	<!-- 配置委托类 -->
    <bean name="target" class="com.xxx.spring.aop.bean.UserDaoImpl"></bean>
    <!-- 配置代理对象将来执行的时候,所要执行的处理程序 -->
    <bean name="before" class="com.xxx.spring.aop.bean.BeforeTest"></bean>
    <!-- 配置advisor -->
    <!-- 作用:筛选要拦截的方法 -->
    <bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    	<!-- 注入advice -->
    	<property name="advice" ref="before"></property>
    	<!-- 注入需要被拦截的目标对象中的方法 -->
    	<property name="patterns">
    		<list>
    			<value>.*deleteUser</value>
    		</list>
    	</property>
    </bean>

    <!-- 配置代理对象,当前IoC容器中自动应用,不用显示应用advisor的bean定义 -->
  	<bean name="proxy"  class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
</beans>

测试:

	@Test
	public void autoProxyByName(){
		BeanFactory factory = new ClassPathXmlApplicationContext("com/xxx/spring/chap2/autoProxyByName.xml");
		UserDao userDao = factory.getBean("target", UserDao.class);
		userDao.saveUser();
		userDao.deleteUser();
		userDao.updateUser();
	}

结果:

保存用户
version 1.0 author tom deleteUser is execute
删除用户
更新用户

时间: 2024-11-05 23:23:02

Spring中AOP实现的相关文章

Spring中AOP原理,使用笔记

AOP(面向切面编程):通过预编译和运行期动态代理的方式在不改变代码的情况下给程序动态的添加一些功能.利用AOP可以对应用程序的各个部分进行隔离,在Spring中AOP主要用来分离业务逻辑和系统级服务. 系统级服务指的是:事务处理,日志记录,性能统计,安全控制,异常处理等,因为这些功能分散在程序的各个模块中,又是通用的,所以可以将它从业务逻辑中分离出来. 连接点(joinpoint):在连接点可以拦截方法的执行,在连接点前后织入上述的这些系统级服务(织入的就是通知). 切入点(pointcut)

Spring中AOP简介与使用

Spring中AOP简介与使用 什么是AOP? Aspect Oriented Programming(AOP),多译作 "面向切面编程",也就是说,对一段程序,从侧面插入,进行操做.即通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. 为什么要用AOP? 日志记录,性能统计,安全控制,事务处理,异常处理等等.例如日志记录,在程序运行的某些节点上添加记录执行操作状态的一些代码,获取执行情况.而通过切面编程,我们将这些插入的内容分离出来,将它们独立

Spring中AOP实例详解

Spring中AOP实例详解 需要增强的服务 假如有以下service,他的功能很简单,打印输入的参数并返回参数. @Service public class SimpleService { public String getName(String name) { System.out.println(get name is: + name); return name; } } 定义切面和切点 @Component @Aspect public class L ogAspect { // 定义切

6.Spring中AOP术语与XML方式简单实现

1.AOP术语 1. Joinpoint(连接点):所谓连接点是指那些被拦截到的点.在spring中,这些点指的是方法,因为spring只支持方法类型的连接点 2. Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义 3. Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) 4. Introduction(引介):引介是一种特殊的通知在不修改类代

浅谈spring中AOP以及spring中AOP的注解方式

AOP(Aspect Oriented Programming):AOP的专业术语是"面向切面编程" 什么是面向切面编程,我的理解就是:在不修改源代码的情况下增强功能.好了,下面在讲述aop注解方式的情况下顺便会提到这一点. 一.搭建aop注解方式的环境(导入以下的包) 二.实现 环境搭建好了之后,就创建项目. 1.创建接口类(CustomerDao)并添加两个方法 2.接口类创建好了后,自然是要new一个实现类(CustomerDaoImpl)并实现接口中的方法 3.以上基础工作做完

Spring中AOP和IOC深入理解

spring框架 Spring框架是由于软件开发的复杂性而创建的.Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情.然而,Spring的用途不仅仅限于服务器端的开发.从简单性.可测试性和松耦合性的角度而言,绝大部分Java应用都可以从Spring中受益. ◆目的:解决企业应用开发的复杂性 ◆功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能 ◆范围:任何Java应用 Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架. Spr

Spring中AOP的使用

问题:什么是AOP? 答:AOP基本概念:Aspect-Oriented Programming,面向方面编程的简称,Aspect是一种新的模块化机制.用来描写叙述分散在对象.类或方法中的横切关注点(crosscutting concern), 从关注点中分离出横切关注点是面向方面程序设计的核心所在. 分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特 定领域问题代码的调用.业务逻辑同特定领域问题的关系通过方面来封装.维护,这样原本分散在整个应用程序中的变动

Spring中AOP和IOC

什么是DI机制? 依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色 需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例.但在spring中 创建被调用者的工作不再由调用者来完成,因此称为控制反转.创建被调用者的工作由spring来完成,然后注入调用者 因此也称为依赖注入. spring以动态灵活的方式来管理对象 , 注入的两种方式,设置注入和构造注入. 设置注入的优点:直观

《Java从入门到放弃》入门篇:spring中AOP的配置方式

spring中最核心的两个东东,一个IOC,一个AOP. AOP(Aspect-OrientedProgramming)面向方面编程,也可以叫面向切面编程. 从一个新人的角度可以这样来理解:一般软件中的功能,我们可以分为两大类,一类是业务功能,一类是系统功能. 业务功能是指这个软件必须要用到的,没有的话客户就不给钱的.比如淘宝APP,如果你只能在上面浏览商品而不能购物,那就说明业务功能太监了···. 系统功能主要是指与业务无关,没有这块内容也不影响软件使用的.比如日志管理.权限处理等. AOP主