java web013——Spring AOP面向切面编程

Posted 江州益彤

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java web013——Spring AOP面向切面编程相关的知识,希望对你有一定的参考价值。

一、Spring AOP简介

AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。

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

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

类与切面的关系

AOP的使用,使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多的关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性。

二、AOP术语


Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

三、JDK动态代理(基于接口实现)

JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。

3.1、先抛出问题



3.1、使用JDK动态代理解决问题


代理的作用:附加功能由动态代理来做,核心功能的业务处理由service来实现



四、AspectJ开发

4.1、AspectJ简介

AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring 2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。新版本的Spring框架,也建议使用AspectJ来开发AOP。
使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ。

4.2、AspectJ开发准备

下载AspectJ的jar包https://mvnrepository.com/artifact/org.aspectj/aspectjweaver
需要导入的jar包

4.3、通知的类型

按照通知在目标类方法的连接点位置,主要可以分为以下类型:

1、环绕通知:在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
2、前置通知:在目标方法执行前实施增强,可以应用于权限管理等功能。
3、返回通知:在目标方法成功执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
4、后置(最终)通知:在目标方法执行后实施增强,不论是否发生异常,该通知都要执行,该类通知可用于释放资源。
5、异常抛出通知:在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。

4.4、基于注解的AspectJ

AspectJ框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。AspectJ的注解及其描述如下所示:

4.5、切入点表达式

execution(modifiers-pattern  ret-type-pattern  declaring-type-pattern name-pattern(param-pattern) throws-pattern)

4.6、基于注解的AspectJ

获取方法的方法名
获取方法的参数
获取方法的返回结果
处理特定异常
环绕通知用法
重用切入点表达式
切面的优先级

4.7、实例(实现接口情况)



MyAspect .java

package com.dgut.spring.aop.aspectj;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//创建一个切面
@Component
@Aspect
@Order(2) // 第二个切面,后使用
public class MyAspect 

	// 重用切入点表达式
	@Pointcut("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.*(..))")
	public void myPointCut() 

	

	// 前置通知
	// 切CalculatorImpl类的add方法参数任意..,返回值任意*
	@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
	public void beforeAnnouncement(JoinPoint jp) 
		String methodName = jp.getSignature().getName();
		Object[] args = jp.getArgs();
		System.out.println("Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
	

	// 返回通知
	// 切CalculatorImpl类的所有方法参数任意..,返回值为res和参数Object res同名,表示两个是一致的
	@AfterReturning(value = "myPointCut()", returning = "res")
	public void afterReturningAnnouncement(JoinPoint jp, Object res) 
		String methodName = jp.getSignature().getName();
		System.out.println("AfterReturning Announcement===> " + "要切入的方法名:" + methodName + "() ,返回结果:" + res);
	

	// 后置(最终)通知
	// 切CalculatorImpl类的所有方法参数任意..,返回值任意*
	@After("myPointCut()")
	public void afterAnnouncement(JoinPoint jp) 
		String methodName = jp.getSignature().getName();
		System.out.println("After Announcement===> " + "要切入的方法名:" + methodName + "()");
	

	// 异常抛出通知
	// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
	@AfterThrowing(value = "myPointCut()", throwing = "ex")
	public void exceptionAnnouncement(JoinPoint jp, Exception ex) 
		String methodName = jp.getSignature().getName();
		System.out.println("Exception Announcement===> " + "要切入的方法名:" + methodName + "()" + " throwing Exception:" + ex);
	

	// 环绕通知
	// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
	@Around("myPointCut()")
	public Object aroundAnnouncement(ProceedingJoinPoint pjp) 
		Object res = null;

		try 
			// 前置通知
			System.out.println("前置通知");
			res = pjp.proceed();
			// 返回通知
			System.out.println("返回通知");
		 catch (Throwable e) 
			// 异常抛出通知
			System.out.println("异常抛出通知");
			e.printStackTrace();
		 finally 
			// 后置(最终)通知
			System.out.println("后置(最终)通知");
		
		return res;
	



OtherAspect .java

package com.dgut.spring.aop.aspectj;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//创建一个切面
@Component
@Aspect
@Order(1) // 第一个切面,先使用
public class OtherAspect 
	// 前置通知
	// 切CalculatorImpl类的add方法参数任意..,返回值任意*
	@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
	public void beforeAnnouncement(JoinPoint jp) 
		String methodName = jp.getSignature().getName();
		Object[] args = jp.getArgs();
		System.out.println("<===Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
	



4.8、实例(没有实现接口情况)



MyAspect .java

package com.dgut.spring.aop.aspectj;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//创建一个切面
@Component
@Aspect
@Order(2) // 第二个切面,后使用
public class MyAspect 

	// 重用切入点表达式
	@Pointcut("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.*(..))")
	public void myPointCut() 

	

	// 前置通知
	// 切CalculatorImpl类的add方法参数任意..,返回值任意*
	@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
	public void beforeAnnouncement(JoinPoint jp) 
		String methodName = jp.getSignature().getName();
		Object[] args = jp.getArgs();
		System.out.println("Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
	

	// 返回通知
	// 切CalculatorImpl类的所有方法参数任意..,返回值为res和参数Object res同名,表示两个是一致的
	@AfterReturning(value = "myPointCut()", returning = "res")
	public void afterReturningAnnouncement(JoinPoint jp, Object res) 
		String methodName = jp.getSignature().getName();
		System.out.println("AfterReturning Announcement===> " + "要切入的方法名:" + methodName + "() ,返回结果:" + res);
	

	// 后置(最终)通知
	// 切CalculatorImpl类的所有方法参数任意..,返回值任意*
	@After("myPointCut()")
	public void afterAnnouncement(JoinPoint jp) 
		String methodName = jp.getSignature().getName();
		System.out.println("After Announcement===> " + "要切入的方法名:" + methodName + "()");
	

	// 异常抛出通知
	// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
	@AfterThrowing(value = "myPointCut()", throwing = "ex")
	public void exceptionAnnouncement(JoinPoint jp, Exception ex) 
		String methodName = jp.getSignature().getName();
		System.out
				.println("Exception Announcement===> " + "要切入的方法名:" + methodName + "()" + " throwing Exception:" + ex);
	

	// 环绕通知
	// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
	@Around("myPointCut()")
	public Object aroundAnnouncement(ProceedingJoinPoint pjp) 
		Object res = null;

		try 
			// 前置通知
			System.out.println("前置通知");
			res = pjp.proceed();
			// 返回通知
			System.out.println("返回通知");
		 catch (Throwable e) 
			// 异常抛出通知
			System.out.println("异常抛出通知");
			e.printStackTrace();
		 finally 
			// 后置(最终)通知
			System.out.println("后置(最终)通知");
		
		return res;
	



OtherAspect .java

package com.dgut.spring.aop.aspectj;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//创建一个切面
@Component
@Aspect
@Order(1) // 第一个切面,先使用
public class OtherAspect 
	// 前置通知
	// 切CalculatorImpl类的add方法参数任意..,返回值任意*
	@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
	public void beforeAnnouncement(JoinPoint jp) 
		String methodName = jp.getSignature().getName();
		Object[] args = jp.getArgs();
		System.out
				.println("<===Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
	



4.9、基于XML的AspectJ

基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在aop:config元素内。

4.9.1、XML文件中常用元素的配置方式如下:

以上是关于java web013——Spring AOP面向切面编程的主要内容,如果未能解决你的问题,请参考以下文章

Java——面向切面编程,Spring中的AOP编程

Java实战之03Spring-03Spring的核心之AOP(Aspect Oriented Programming 面向切面编程)

Java Spring 切面 aop 超时?

推荐学java——Spring之AOP

Java--Spring之AOP面向切面编程

java - spring - AOP