spring中AOP机制学习简单Demo

什么是AOP

Aspect Oriented Programming,面向切面/方面编程

即针对一批组件的通用业务逻辑进行编程,将通用的功能业务模块分离,通用的功能就是可以复用的代码段

AOP中的一些术语

  • 切面:封装通用业务逻辑的组件,作用到其他组件上,要求:
    • 必须是被Spring管理的bean组件中的方法
    • 无返回值(void)
    • 参数类型与通知类型一致
  • 切入点:指定哪些组件的哪些方法使用切面程序,spring提供表达式来实现指定,要求:
    • 必须是spring 管理的bean组件的方法
    • 不限定返回值
    • 不限定参数类型
  • 通知:切面程序插入到切入点程序的某个位置,根据位置的不同,分为:
    • 前置通知 : 切面程序在目标方法执行前执行
    • 后置通知 : 目标组件(切入点)正常执行并返回参数后执行
    • 异常通知 : 目标组件抛出异常后执行
    • 最终通知 : 目标组件正常执行后、或者异常通知前 执行,和finally 中的内容相似,最终通知中切面中的代码不管异常否,均会执行
    • 环绕通知 : 可以取代之前的集中通知类型,具体见下面代码

切入点表达式

  1. 按函数匹配 ,形如:execution(* com.lc.test.Boy.aspectTest(..)),注意的几点:
    1. 第一个 * 表示任意修饰符及任意返回类型,不能省略;   如:写成 execution(public * com.lc.test.Boy.*(..))表示Boy类中用public修饰的方法、任意返回类型、任意参数的方法为切入点
    2. * 表示匹配一个,如果要两个参数,就要这样写:execution(* com.lc.test.Boy.aspectTest(*,*))
    3. 两个点 .. 表示匹配任意参数(0个或多个): execution(* com.lc.test.Boy.aspectTest(..))
    4. com..*.*(..)) : 指定com下的所有类的任意参数的任意方法为切入点
  2. 按类匹配, 形如:within(com.lc.test.Boy) :指定这个类中所有方法 均为切入点
  3. 按bean组件的id匹配:bean(boy) :bean的id为boy的类 中所有方法 均为切入点,可用通配符 ,形如:bean(b*)

环境搭建

  1. spring 核心jar : core
  2. spring-aop, spring-aspects (spring-aop..., spring-aspects..., aopalliance..., aspectjweaver...,)

Demo部分代码及过程中遇到的问题:

前置通知:

// 切入点程序
public class Boy {
    public void aspectTest(String param1, String param2){
	    System.out.println("测试AOP");
    }
}

// 切面程序
// JoinPoint jp 参数并不是必须的,这个参数可以获取目标组件的细节,如类名,方法名等
public class AspectDemo{
	public void doBefore(JoinPoint jp){
		System.out.println("前置通知");
		System.out.println(jp.getTarget().getClass().getName());
	}
}


// applicationComtext.xml配置文件
<!-- 将切入点和切面程序声明为bean组件 -->
<bean id="boy" class="com.lc.test.Boy"></bean>
<bean id="beforeAspect" class="com.lc.aspect.AspectDemo"></bean>
<!-- 配置通知 -->
<aop:config>
	<!-- 配置切入点 expression="within(com.lc.test.Boy) 或者expression=bean(boy)"" " -->
	<aop:pointcut id="targetPointcut" expression="execution(* com.lc.test.Boy.aspectTest(..))" />
	<!-- 配置切面,ref:切面对象 -->
	<aop:aspect ref="beforeAspect">
		<!-- 前置通知 method:切面方法, pointcut-ref:指定切面方法作用的切入点 -->
		<aop:before method="doBefore" pointcut-ref="targetPointcut"/>
	</aop:aspect>
</aop:config>

后置通知:

// 切入点程序和前置通知一样

// 切面程序
public void doAfterReturning(JoinPoint jp, Object result){
	System.out.println("目标组件的返回值:" + result);
	System.out.println("后置通知");
}

// 配置
<!-- 将切入点和切面程序声明为bean组件 -->
<bean id="boy" class="com.lc.test.Boy"></bean>
<bean id="beforeAspect" class="com.lc.aspect.AspectDemo"></bean>
<!-- 配置通知 -->
<aop:config>
	<!-- 配置切入点 -->
	<aop:pointcut id="targetPointcut" expression="bean(boy)" />
	<!-- 配置切面,ref:切面对象 -->
	<aop:aspect ref="beforeAspect">
		<!--前置通知  method:切面方法, pointcut-ref:指定切面方法作用的切入点 -->
		<aop:before method="doBefore" pointcut-ref="targetPointcut"/>
		<!-- 后置通知 returnint:切入点程序执行完成的返回值,void为null -->
		<aop:after-returning method="doAfterReturning" pointcut-ref="targetPointcut" returning="result"/>
	</aop:aspect>
</aop:config>

异常通知:

// 切入点程序
public void aspectTest(){
	System.out.println("测试AOP");
	// 创造一个抛出异常的点
        System.out.println(3/0);
}

// 切面程序
public void doThrowable(JoinPoint jp, Throwable t){
	System.out.println("异常信息:"+ t.getMessage());
	System.out.println("异常通知");
}

//配置
<!-- 将切入点和切面程序声明为bean组件 -->
<bean id="boy" class="com.lc.test.Boy"></bean>
<bean id="beforeAspect" class="com.lc.aspect.AspectDemo"></bean>
<!-- 配置通知 -->
<aop:config>
	<!-- 配置切入点 -->
	<aop:pointcut id="targetPointcut" expression="bean(boy)" />
	<!-- 配置切面,ref:切面对象 -->
	<aop:aspect ref="beforeAspect">
		<!--前置通知  method:切面方法, pointcut-ref:指定切面方法作用的切入点 -->
		<aop:before method="doBefore" pointcut-ref="targetPointcut"/>
		<!-- 后置通知 returnint:切入点程序执行完成的返回值,void为null -->
		<aop:after-returning method="doAfterReturning" pointcut-ref="targetPointcut" returning="result"/>
		<!-- 异常通知 throwing:切入点程序抛出的异常信息  -->
		<aop:after-throwing method="doThrowable" pointcut-ref="targetPointcut" throwing="t" />
	</aop:aspect>
</aop:config>

最终通知(暂时没搞清和后置通知有啥区别,但是有一点,后置通知可以获取到目标组件的返回值):

// 切面程序
public void doAfter(JoinPoint jp){
	System.out.println("最终通知");
}

// 配置
<!-- 将切入点和切面程序声明为bean组件 -->
<bean id="boy" class="com.lc.test.Boy"></bean>
<bean id="beforeAspect" class="com.lc.aspect.AspectDemo"></bean>
<!-- 配置通知 -->
<aop:config>
	<!-- 配置切入点 -->
	<aop:pointcut id="targetPointcut" expression="bean(boy)" />
	<!-- 配置切面,ref:切面对象 -->
	<aop:aspect ref="beforeAspect">
		<!--前置通知  method:切面方法, pointcut-ref:指定切面方法作用的切入点 -->
		<aop:before method="doBefore" pointcut-ref="targetPointcut"/>
		<!-- 后置通知 returnint:切入点程序执行完成的返回值,void为null -->
		<aop:after-returning method="doAfterReturning" pointcut-ref="targetPointcut" returning="result"/>
		<!-- 异常通知 throwing:切入点程序抛出的异常信息  -->
		<aop:after-throwing method="doThrowable" pointcut-ref="targetPointcut" throwing="t" />
		<!-- 最终通知 -->
		<aop:after method="doAfter" pointcut-ref="targetPointcut"/>
	</aop:aspect>
</aop:config>

 环绕通知:参数为org.aspectj.lang.ProceedingJoinPoin(pjp), 是JoinPoint的子类,提供proceed()方法;

    执行过程:proceed方法调用目标组件(切入点)方法,并返回其返回值,Object result = pjp.proceed();

    取代前几种通知类型的方式:

proceed方法之前的代码前置通知
proceed方法之后的代码后置通知
异常中的代码异常通知
finally中的代码最终通知
// 切面程序
public void doArround(ProceedingJoinPoint pjp){
	try {
		System.out.println("前置通知");
		Object result = pjp.proceed();
		System.out.println("后置通知:" + result );
	} catch (Throwable t) {
		System.out.println("异常通知:" + t.getMessage());
	}finally{
		System.out.println("最终通知");
	}
}

// 切入点程序
public String aspectTest(){
	System.out.println("测试AOP");
	//System.out.println(3/0);
	return "success";
}

// 配置
<!-- 将切入点和切面程序声明为bean组件 -->
<bean id="boy" class="com.lc.test.Boy"></bean>
<bean id="beforeAspect" class="com.lc.aspect.AspectDemo"></bean>
<!-- 配置通知 -->
<aop:config>
	<!-- 配置切入点 -->
	<aop:pointcut id="targetPointcut" expression="bean(boy)" />
	<!-- 配置切面,ref:切面对象 -->
	<aop:aspect ref="beforeAspect">
		<!-- 环绕通知  -->
		<aop:around method="doArround" pointcut-ref="targetPointcut"/>
	</aop:aspect>
</aop:config>

 

用配置文件配置,配置比较繁琐,下面用注解配置一下:

前提条件:

  1. 引入了aspectj包,因为要使用包中的@Aspect注解标注组件为切面(这个Demo中是com.lc.aspect.AspectDemo)
  2. 开启自动扫描 :<context:component-scan base-package="com.lc"></context:component-scan>
  3. 开启aspectj的自动代理模式  <aop:aspectj-autoproxy><aop:aspectj-autoproxy/>

前置通知:

// 切入点程序
public String aspectTest(){
	System.out.println("测试AOP");
	//System.out.println(3/0);
	return "success";
}

// 切面程序
@Aspect
public class AspectDemo {
        // 注意这里是execution,不是配置文件中的expression
        // 另外:bean(boy)  , within(com.lc.test.Boy) 也可以
	@Before(value="execution(* com.lc.test.Boy.aspectTest())")
	public void doBefore(JoinPoint jp){
		System.out.println("前置通知");
		System.out.println(jp.getTarget().getClass().getName());
	}
}

// 因为这个没有注解类为bean组件,在配置文件里面配置
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.lc"></context:component-scan>
<!--如果要使用@Aspect注解, 得开启aspectj的自动代理模式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

<!-- 将切入点和切面程序声明为bean组件 -->
<bean id="boy" class="com.lc.test.Boy"></bean>
<bean id="beforeAspect" class="com.lc.aspect.AspectDemo"></bean>

后置通知、异常通知、最终通知、环绕通知:

@Aspect
public class AspectDemo {
	@Before(value="execution(* com.lc.test.Boy.*())")
	// 注意这里是execution,不是配置文件中的expression
        // 另外:bean(boy)  , within(com.lc.test.Boy) 也可以
	public void doBefore(JoinPoint jp){
		System.out.println("前置通知");
		System.out.println(jp.getTarget().getClass().getName());
	}
	
	@AfterReturning(pointcut="execution(* com.lc.test.Boy.*())", returning="result")
	public void doAfterReturning(Object result){
		System.out.println("目标组件的返回值:" + result);
		System.out.println("后置通知");
	}
	
	@AfterThrowing(pointcut="execution(* com.lc.test.Boy.*())",throwing="t")
	public void doThrowable(JoinPoint jp, Throwable t){
		System.out.println("异常信息:"+ t.getMessage());
		System.out.println("异常通知");
	}
	
	@After(value="execution(* com.lc.test.Boy.*())")
	public void doAfter(JoinPoint jp){
		System.out.println("最终通知");
	}
	
	@Around(value="execution(* com.lc.test.Boy.*())")
	public void doArround(ProceedingJoinPoint pjp){
		try {
			System.out.println("前置通知");
			Object result = pjp.proceed();
			System.out.println("后置通知:" + result );
		} catch (Throwable t) {
			System.out.println("异常通知:" + t.getMessage());
		}finally{
			System.out.println("最终通知");
		}
	}
}

像上面的注解配置中,对于切入点的配置,其实不管是

@AfterReturning(pointcut="execution(* com.lc.test.Boy.*())", returning="result") 中的 pointcut 取值, 还是其他通知,比如:

@After(value="execution(* com.lc.test.Boy.*())") 中的 value 取值

它们都是同一个取值,此时,为了简化配置,我们可以这样写(定义一个公共的切入点):

@Aspect
public class AspectDemo {
	// 定义切入点
	@Pointcut(value="execution(* com.lc.test.Boy.aspectTest())")
	public void pointcutName(){}
	
	@Before("pointcutName()")
	// 注意这里是execution,不是配置文件中的expression
        // 另外:bean(boy)  , within(com.lc.test.Boy) 也可以
	public void doBefore(JoinPoint jp){
		System.out.println("前置通知");
		System.out.println(jp.getTarget().getClass().getName());
	}
	
	@AfterReturning(pointcut="pointcutName()", returning="result")
	public void doAfterReturning(Object result){
		System.out.println("目标组件的返回值:" + result);
		System.out.println("后置通知");
	}
	
	@AfterThrowing(pointcut="pointcutName()",throwing="t")
	public void doThrowable(JoinPoint jp, Throwable t){
		System.out.println("异常信息:"+ t.getMessage());
		System.out.println("异常通知");
	}
	
	@After(value="pointcutName()")
	public void doAfter(JoinPoint jp){
		System.out.println("最终通知");
	}
	
	@Around(value="pointcutName()")
	public void doArround(ProceedingJoinPoint pjp){
		try {
			System.out.println("前置通知");
			Object result = pjp.proceed();
			System.out.println("后置通知:" + result );
		} catch (Throwable t) {
			System.out.println("异常通知:" + t.getMessage());
		}finally{
			System.out.println("最终通知");
		}
	}
}

 

总结:aop可以通过通知来实现对一些通用业务逻辑进行编程(代码复用): 日志打印、事务控制、权限控制......

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值