天天看点

Spring AOP 与 AspectJ一、介绍二、 AOP术语三、AOP介绍四、面向切面编程

文章目录

  • 一、介绍
  • 二、 AOP术语
  • 三、AOP介绍
    • 3.1 什么是AOP ?
    • 3.2 Spring通知类型
  • 四、面向切面编程
    • 4.1 面向切面AspectJ
    • 4.2 注解式的Aspect

一、介绍

在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率
AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充

二、 AOP术语

Spring AOP 与 AspectJ一、介绍二、 AOP术语三、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();
		
	}
	
}
           
前置通知: 模拟执行权限检查, 目标类是: [email protected], 被织入的方法: addUser
环绕通知开始执行, 模拟开启事务: 
UserDAO addUser is called... 
最终通知,结束后释放资源... 
环绕通知结束, 模拟关闭事务...
UserDAO deleteUser is called...
后置通知:模拟记录日志...目标类是:[email protected], 被织入的目标方法: 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();
		
	}
	
}

           
前置通知: 模拟执行权限检查, 目标类是: [email protected], 被织入的方法: addUser
UserDAO addUser is called... 
UserDAO deleteUser is called...
后置通知:模拟记录日志...目标类是:com.hl[email protected], 被织入的目标方法: deleteUser