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实战之03Spring-03Spring的核心之AOP(Aspect Oriented Programming 面向切面编程)