Spring AOP 与 AspectJ

一、介绍

在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率

AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充

二、 AOP术语

在这里插入图片描述

  • Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法
  • Target Object(目标对象):指所有被通知的对象,也被称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象
  • Proxy(代理):将通知应用到目标对象之后,被动态创建的对象
  • Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程

三、AOP介绍

3.1 什么是AOP ?

  • AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来
  • 在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方

3.2 Spring通知类型

前置通知、后置通知、环绕通知、异常抛出通知、引介通知

  • org.springframework.aop.MethodBeforeAdvice(前置通知)
    在目标方法执行前实施增强,可以应用于权限管理等功能

  • org.springframework.aop.AfterReturningAdvice(后置通知)
    在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能

  • org.aopalliance.intercept.MethodInterceptor(环绕通知)
    在目标方法执行前后实施增强,可以应用于日志、事务管理等功能

  • org.springframework.aop.ThrowsAdvice(异常抛出通知)
    在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能

  • org.springframework.aop.IntroductionInterceptor(引介通知)
    在目标类中添加一些新的方法和属性,可以应用于修改老版本程序

四、面向切面编程

4.1 面向切面AspectJ

  • 自定义的切面
public class MyAspect {
	
	// 前置通知
	public void beforeProxy(JoinPoint joinPoint) {
		System.out.print("前置通知: 模拟执行权限检查, ");
		System.out.print("目标类是: " + joinPoint.getTarget());
		System.out.println(", 被织入的方法: " + joinPoint.getSignature().getName());
	}
	
	// 后置通知
	public void afterReturningProxy(JoinPoint joinPoint) {
		System.out.print("后置通知:模拟记录日志...");
		System.out.print("目标类是:" + joinPoint.getTarget());
		System.out.println(", 被织入的目标方法: " + joinPoint.getSignature().getName());
	}
	
	// ProceedingJoinPoint是 JoinPoint的子接口, 表示可以执行的目标方法
	// 使用proceedingJoinPoint为参数,必须返回一个对象 Object
	// 必须接收一个参数,类型为 ProceedingJoinPoint
	// 必须抛出异常 throws Throwable
	public Object aroundProxy(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		
		System.out.println("环绕通知开始执行, 模拟开启事务: ");
		
		Object obj = proceedingJoinPoint.proceed();
		
		System.out.println("环绕通知结束, 模拟关闭事务...");
		return obj;
	}
	
	public void afterThrowingProxy(JoinPoint joinPoint, Throwable exception) {
		System.out.println("抛出异常: " + exception.getMessage());
	}
	
	// 最终通知
	public void afterProxy() {
		System.out.println("最终通知,结束后释放资源... ");
	}
	
}
  • 编写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-4.3.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

	<!-- 1 目标类 -->
	<bean id="userDAO" class="com.hlq.AspectJ.UserDAOImpl" />
	<!-- 2 切面 -->
	<bean id="myAspect" class="com.hlq.AspectJ.MyAspect" />
	<!-- 3 aop编程 -->

	<aop:config>
		<!-- 配置切面 -->
		<aop:aspect ref="myAspect">
			<!-- 3.1 配置切入点,通知最后增强哪些方法 -->
			<aop:pointcut
				expression="execution(* com.hlq.AspectJ.*.add*(..))"
				id="addPointCut" />
			
			<!-- 配置另一个切入点 -->
			<aop:pointcut expression="execution(* com.hlq.AspectJ.*.delete*(..))" id="deletePointCut"/>
			<!-- 3.2 关联通知Advice和切入点pointCut -->
			
			<!-- 3.2.1 前置通知 -->
			<aop:before method="beforeProxy" pointcut-ref="addPointCut" />
			<!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值 returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
			<aop:after-returning method="afterReturningProxy"
				pointcut-ref="deletePointCut" returning="returnVal" />
			<!-- 3.2.3 环绕通知 -->
			<aop:around method="aroundProxy" pointcut-ref="addPointCut" />
			<!-- 3.2.4 抛出通知:用于处理程序发生异常-->
			<!-- * 注意:如果程序没有异常,将不会执行增强 -->
			<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
			<aop:after-throwing method="afterThrowingProxy"
				pointcut-ref="addPointCut" throwing="exception" />
			<!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
			<aop:after method="afterProxy" pointcut-ref="addPointCut" />
			
		</aop:aspect>
	</aop:config>
</beans>

  • 测试
public class UserDAOImpl implements UserDAO {
	
	public void addUser() {
		System.out.println("UserDAO addUser is called... ");
	}
	
	public void deleteUser() {
		System.out.println("UserDAO deleteUser is called...");
	}
	
	public UserDAOImpl() {
		super();
	}
	
}
public class AspectTest {
	
	public static void main(String[] args) {
		
		String xmlPath = "com/hlq/AspectJ/aspectConf.xml";
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
		
		UserDAO userDAO = (UserDAO)applicationContext.getBean("userDAO");
		
		userDAO.addUser();
		userDAO.deleteUser();
		
	}
	
}
前置通知: 模拟执行权限检查, 目标类是: com.hlq.AspectJ.UserDAOImpl@4686afc2, 被织入的方法: addUser
环绕通知开始执行, 模拟开启事务: 
UserDAO addUser is called... 
最终通知,结束后释放资源... 
环绕通知结束, 模拟关闭事务...
UserDAO deleteUser is called...
后置通知:模拟记录日志...目标类是:com.hlq.AspectJ.UserDAOImpl@4686afc2, 被织入的目标方法: deleteUser

4.2 注解式的Aspect

  • 定义注解切面
@Aspect
@Component
public class AspectJ {

	// 定义切入点表达式
	@Pointcut("execution(* com.hlq.annotationAspect.*.add*(..))")
	// 使用一个返回值为void、方法体为空的方法来命名切入点
	private void addPointCut() {
	}

	@Pointcut("execution(* com.hlq.annotationAspect.*.delete*(..))")
	public void deletePointCut() {
	}

	// 前置通知
	@Before("addPointCut()")
	public void beforeProxy(JoinPoint joinPoint) {
		System.out.print("前置通知: 模拟执行权限检查, ");
		System.out.print("目标类是: " + joinPoint.getTarget());
		System.out.println(", 被织入的方法: " + joinPoint.getSignature().getName());
	}

	// 后置通知
	@AfterReturning("deletePointCut()")
	public void afterReturningProxy(JoinPoint joinPoint) {
		System.out.print("后置通知:模拟记录日志...");
		System.out.print("目标类是:" + joinPoint.getTarget());
		System.out.println(", 被织入的目标方法: " + joinPoint.getSignature().getName());
	}

//	// ProceedingJoinPoint是 JoinPoint的子接口, 表示可以执行的目标方法
//	// 使用proceedingJoinPoint为参数,必须返回一个对象 Object
//	// 必须接收一个参数,类型为 ProceedingJoinPoint
//	// 必须抛出异常 throws Throwable
//	@Around("myPointCut()")
//	public Object aroundProxy(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//
//		System.out.println("环绕通知开始执行, 模拟开启事务: ");
//
//		Object obj = proceedingJoinPoint.proceed();
//
//		System.out.println("环绕通知结束, 模拟关闭事务...");
//		return obj;
//	}
//
//	@AfterThrowing(value="myPointCut()", throwing="exception")
//	public void afterThrowingProxy(JoinPoint joinPoint, Throwable exception) {
//		System.out.println("抛出异常: " + exception.getMessage());
//	}
//
//	// 最终通知
//	@After("myPointCut()")
//	public void afterProxy() {
//		System.out.println("最终通知,结束后释放资源... ");
//	}

}

  • 配置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"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
  	http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
  	http://www.springframework.org/schema/aop 
  	http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
  	http://www.springframework.org/schema/context 
  	http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<!-- 指定需要扫描的包,使注解生效 -->
	<context:component-scan base-package="com.hlq.annotationAspect" />

	<!-- 启动基于注解的声明式AspectJ支持 -->
	<aop:aspectj-autoproxy />

</beans>

  • 编写任意测试类
@Repository("userDAO")
public class UserDAOImpl implements UserDAO {
	
	public void addUser() {
		System.out.println("UserDAO addUser is called... ");
	}
	
	public void deleteUser() {
		System.out.println("UserDAO deleteUser is called...");
	}

	
}
public class AspectTest {
	
	public static void main(String[] args) {
		
		String xmlPath = "com/hlq/annotationAspect/aspectConf.xml";
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
		
		UserDAO userDAO = (UserDAO)applicationContext.getBean("userDAO");
		
		userDAO.addUser();
		userDAO.deleteUser();
		
	}
	
}

前置通知: 模拟执行权限检查, 目标类是: com.hlq.annotationAspect.UserDAOImpl@56dc1551, 被织入的方法: addUser
UserDAO addUser is called... 
UserDAO deleteUser is called...
后置通知:模拟记录日志...目标类是:com.hlq.annotationAspect.UserDAOImpl@56dc1551, 被织入的目标方法: deleteUser


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值