概述
文章目录
- 前言
- 介绍AOP
- 一、实现AOP
- 1.1、全注解形式实现AOP
- 前提准备(引入jar包)
- 实现AOP(五种通知)
- 二、认识JoinPont与ProceedingJoinPoint
- 2.1、初识两个接口
- 2.2、JoinPoint使用
- 2.3、ProceedingJoinPoint使用(配合@Around)
- 三、对注解进行切面进行权限校验
- 实际业务
- 1、对一条请求的请求接收到响应做日志处理
- 总结
- 参考文章
- 参考文章
前言
本篇博客主要是介绍Spring中AOP的实际应用,若文章中出现相关问题,请指出!
所有博客文件目录索引:博客目录索引(持续更新)
介绍AOP
前言:在Spring中AOP至关重要,通过AOP能为程序方法添加统一功能,集中的解决一些公共问题。
应用场景包含:日志记录,Deubgging调试,tracing,分析与解控记录进行跟踪优化,Authentication 权限、Caching 缓存、Context passing 内容传递、Error handling 错误处理、Lazy loading 懒加载。
- P2021.4.23 目前仅接触到使用AOP进行日志记录,鉴权。
一、实现AOP
1.1、全注解形式实现AOP
前提准备(引入jar包)
引入坐标:
<!-- spring、aspect -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.2</version>
</dependency>
-
- context包:实现类如
ApplicationContext
,AnnotationConfigApplicationContext
,注解如@ComponentScan
,@Configuration
,@EnableAspectJAutoProxy
。 - aop包:当前暂时没有使用到。
- core包:如注解
@Component
- ascetj包:如实现类(或接口)
JoinPoint
,ProceedingJoinPoint
,注解不用多说如:@Aspect
、@Pointcut
、@Before
、@Around
等多个通知。
- context包:实现类如
实现AOP(五种通知)
TargetClass
(无接口实现类):
import org.springframework.stereotype.Component;
@Component //注入到bean中(方便AOP能够进行切面)
public class TargetClass {
public String executeMethod(String arg1,String arg2){
System.out.println("TargetClass类的executeMethod方法执行了!"+"arg1="+arg1+",arg2="+arg2);
return "changluyaya"; //后来补充
}
}
AOPConfig
:AOP配置类用于开启切面以及扫描包(扫描如@Component
这类注解,来进行之后的切面操作)
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration //提供生成bean定义以及服务请求
@ComponentScan("com.aopexer") //对于指定程序包进行扫描
@EnableAspectJAutoProxy //开启切面自动代理类
public class AOPConfig {
}
MyAspect
:自定义切面,即AOP代理类,包含五个通知方法
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* @ClassName LogAscept
* @Author ChangLu
* @Date 2021/4/23 19:45
* @Description 自定义AOP切面
*/
@Component //表明该类是组件
@Aspect //当前类标识为一个切面供容器读取
public class MyAspect {
@Pointcut("execution(* com.aopexer.TargetClass.*(..))")//设置切入点,方便下面直接调用
public void point(){}
//前置通知
@Before("point()")
public void before(JoinPoint joinPoint){
System.out.println(2+"==>@Before");
}
//环绕通知
@Around("point()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println(1+"==>@Around 方法执行前");
proceedingJoinPoint.proceed();//执行方法
System.out.println(5+"==>@Around 方法执行后");
}
//后置通知:若是想要接收返回值来对返回值进行一些额外操作则需要配置returning属性来获取
@AfterReturning(value = "point()",returning = "obj") //这里obj对应属性参数obj
public void afterReturning(Object obj){
System.out.println(3+"==>@AfterReturning");
System.out.println("3后置通知中拿到返回值:"+obj);
}
//最终通知
@After("point()")
public void after(){
System.out.println(4+"==>@After");
}
//异常通知
@AfterThrowing("point()")
public void afterThrowing(){
System.out.println("抛出异常后"+"==>@AfterThrowing");
}
}
测试类Test
:用于测试AOP的几个通知
public class Test {
public static void main(String[] args) {
//获取一个中央接口:从给定的组件类派生bean定义并自动刷新上下文
ApplicationContext context = new AnnotationConfigApplicationContext(AOPConfig.class);
TargetClass bean = context.getBean("targetClass", TargetClass.class);//或者context.getBean(TargetClass.class);
bean.executeMethod("参数1","参数2");
}
}
无异常测试
被代理类中的方法无异常时测试程序:
结论:在无异常情况下,通知顺序为:@Around
(方法前)->@Before
->目标方法->@AfterReturing
->@After
->@Around
(方法后)。
有异常测试
在执行方法中添加一个异常程序段:
说明:程序中出现异常时,@AfterReturning
与@Around
(方法后)没有执行。对于@After
最终通知依旧执行,通常可以使用该注解来执行一些资源关闭操作,类似于finally()
。
二、认识JoinPont与ProceedingJoinPoint
2.1、初识两个接口
JoinPoint
:封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象。
ProceedingJoinPoint
:与JoinPoint相同就是新增两个方法,一般用于在环绕通知方法中。
JoinPooint
与ProceedingJoinPoint
都是接口,并且后者继承前者:
JoinPont
接口提供的方法如下:
Signature getSignature()
:获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息。Object[] getArgs();
:获取被代理方法的参数对象。Object getTarget();
:获取被代理的对象。Object getThis();
:获取代理对象。
ProceedingJoinPoint
接口:在JoinPont接口基础上多增加了两个方法
Object proceed() throws Throwable
:执行目标方法。Object proceed(Object[] var1) throws Throwable
:传入的新的参数去执行目标方法
2.2、JoinPoint使用
其常用方法如下:
下面切面都是基于该类的方法进行测试:
//指定被代理类方法
package com.aopexer;
@Component //用于注入到bean中
public class TargetClass {
public void executeMethod(String arg1,String arg2){
System.out.println("TargetClass类的executeMethod方法执行了!"+"arg1="+arg1+",arg2="+arg2);
}
}
常见方法测试
//前置通知
@Before("point()")
public void before(JoinPoint joinPoint){
System.out.println(joinPoint.getKind());//连接点类型
System.out.println(joinPoint.getSourceLocation());//①无源位置返回null;②返回默认构造函数的定义类的SourceLocation
System.out.println(joinPoint.getStaticPart());//封装此连接点的静态部分的对象
//获取传入目标的方法参数(重点)
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
System.out.println(arg);
}
System.out.println(joinPoint.getTarget());//获取目标对象(被代理对象)(重点)
System.out.println(joinPoint.getThis());//获取代理对象(代理对象自己)(重点)
System.out.println(joinPoint.toString());//execution表达式(描述信息中等)
System.out.println(joinPoint.toShortString());//execution表达式(描述信息简短)
System.out.println(joinPoint.toLongString());//execution表达式(描述信息详细)
}
- 其中参数值即为传入方法的值!
其中的getStaticPart()
对象方法:
说明:比较重要的就是执行方法参数值,execution表达式的详细信息以及被代理对象和代理对象。
通过
getSignature()
获取对象Signature测试
Signature
:连接点标签名,一般用于跟踪或记录应用程序相关连接点反射信息。简而言之就是获取目标类名、方法名、参数类型。
//切面代理类
//前置通知
@Before("point()")
public void before(JoinPoint joinPoint){
joinPoint.
System.out.println("-----------------");
Signature signature = joinPoint.getSignature();//连接点签名
System.out.println(signature.getDeclaringTypeName());//全限定定类名(重点)
System.out.println(signature.getDeclaringType());//class+全限定类名
System.out.println(signature.getName());//方法名(重点)
System.out.println(signature.getModifiers());//方法权限修饰对应值(1=>PUBLIC)
System.out.println(signature.toString());//返回值+方法全限定类名+参数类型
System.out.println(signature.toLongString());//权限类型+返回值+方法全限定类名+全限定参数类型
System.out.println(signature.toShortString());//execution表达式中的一部分=>类名.方法(..)
System.out.println("-----------------");
}
2.3、ProceedingJoinPoint使用(配合@Around)
介绍:通常ProceedingJoinPoint
与@Around
环绕通知进行使用。
在2.1中介绍了ProceedingJoinPoint
除了包含有JoinPont
的方法还包含两个执行方法,其执行的即为被代理类方法,并且其中的一个执行方法能够重新传参再次调用方法。
实例演示
被代理方法:
@Component //用于注入到bean中
public class TargetClass {
public int executeMethod(String arg1,String arg2){
System.out.println("TargetClass类的executeMethod方法执行了!"+"arg1="+arg1+",arg2="+arg2);
return 1;
}
}
环绕通知:作用于上面被代理方法
- 仅提供环绕通知代码,其他通知与1.1章节中一致。
- 在该环绕通知中,为了演示效果,再次传值并调用了被代理类方法
//环绕通知
@Around("point()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {//该环绕方法应当有返回值
System.out.println(1+"==>@Around 方法执行前");
//执行被代理类方法(根据原有参数)
Object proceedReturn = proceedingJoinPoint.proceed();
//通过自己传参再次执行被代理类方法
proceedingJoinPoint.proceed(new Object[]{"hello","world"});
System.out.println(5+"==>@Around 方法执行后");
return proceedReturn;
}
注意:该环绕方法应当有返回值,并且应该将执行方法得到的返回值进行返回。
测试程序:
public class Test {
public static void main(String[] args) {
//获取一个中央接口:从给定的组件类派生bean定义并自动刷新上下文
ApplicationContext context = new AnnotationConfigApplicationContext(AOPConfig.class);
TargetClass bean = context.getBean("targetClass", TargetClass.class);//或者context.getBean(TargetClass.class);
int i = bean.executeMethod("参数1", "参数2");
System.out.println(i);
}
}
说明:若是在环绕通知中多次调用被代理类方法,那么就会出现上面程序的现象,2、3、4通知再次执行!环绕通知中的方法是否有返回值应该决定于被代理类方法,仅举一个极端例子:被代理类方法有返回值,环绕方法无返回值,若是在程序中显示获取被代理类方法的返回值就会报出异常,其他情况均无异常。
三、对注解进行切面进行权限校验
参考文章:AOP中获取自定义注解的参数值
开发环境:spring-aspects 5.3.2、spring-boot-starter-web 2.5.2
ValidateAnnotation.java
:自定义注解
/**
* @author changlu
* @date 2021/07/30 13:36
**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidateAnnotation {
String value();
}
UserController.java
@RestController
@RequestMapping("/api/v1/test")
@Slf4j //在类上添加该接口
public class UserController {
//添加自定义注解
@ValidateAnnotation(value = "user")
@GetMapping("/{id}")
public ResultBody test(@PathVariable(value = "id")Integer myid){
users.add(new User((long)111, "changlu", 11, null,null));
log.info("msg");
return ResultBody.success(users);
}
}
AOPConfig.java
/**
* @author changlu
* @date 2021/07/30 13:32
**/
@Component
@Aspect
public class AOPConfig {
@Pointcut("@annotation(com.changlu.Annotations.ValidateAnnotation)")
public void test() {}
//对于@Before需要通过该种形式来对注解进行切面控制 @annotation(annoation)中annoation与方法参数应该一致!
@Before("test() && @annotation(annoation)")
@ResponseBody
public void before(ValidateAnnotation annoation){
System.out.println(annoation);
//一旦注解中的value与校验的相符,就抛出异常向前端响应未通过校验
if("user".equals(annoation.value())){
throw new MsgException(CommonEnum.PERMISSION_ERROR);
}
}
}
上面实现的案例仅仅只是简单表达通过APO来进行拦截校验的功能实现!
实际业务
1、对一条请求的请求接收到响应做日志处理
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* @ClassName WebLogAspect
* @Author ChangLu
* @Date 2021/8/16 0:04
* @Description TODO
*/
@Aspect
@Component
@Slf4j
public class WebLogAspect {
@Pointcut("execution(public * com.changlu.springbootdemo.controller.*.*(..)))")
public void webLog() {
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) {
//收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
log.info("URL : " + request.getRequestURL().toString());
log.info("HTTP_METHOD :" + request.getMethod());
log.info("IP : " + request.getRemoteAddr());
log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
+ joinPoint.getSignature().getName());
log.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));
}
// 通过设置returning = "res"来得到指定返回的响应结果
@AfterReturning(returning = "res", pointcut = "webLog()")
public void doAfterReturning(Object res) throws JsonProcessingException {
//处理完请求,返回内容(借助jackson工具类来对对象进行序列化操作)
log.info("RESPONSE : " + new ObjectMapper().writeValueAsString(res));
}
}
执行一条请求后的日志记录:
总结
1、JoinPont
与ProceedingJoinPoint
都是接口,后者继承了前者并且多了两个执行方法(用于执行被代理类方法)。
2、JoinPoint常用方法有:获取方法名(joinPoint.getSignature().getName()
)、获取简单类名(joinPoint.getSignature().getDeclaringTypeName()
)、获取被代理类方法参数值(joinPoint.getArgs()
)、获取被代理对象(joinPoint.getTarget()
)、获取代理对象(joinPoint.getThis()
)。
3、ProceedingJoinPoint
使用于@Around
环绕通知方法中,提供了执行被代理方法,该调用执行方法前后可进行业务操作,如获取方法执行时间等等。
4、@Around
环绕通知方法对于是否设置返回参数应该取决于被代理类方法,建议是带有返回值,因为AOP往往是针对于多个方法执行的,所有一般都是设置返回值为Object。
参考文章
[1]. Spring AOP中JoinPoint的用法
[2]. Java反射机制getModifiers()方法的作用 在joinpoint
的getModifiers()
返回值
[3]. AOP中获取自定义注解的参数值:使用APO来对注解进行切面包含详细的描述
.getThis()`)。
3、ProceedingJoinPoint
使用于@Around
环绕通知方法中,提供了执行被代理方法,该调用执行方法前后可进行业务操作,如获取方法执行时间等等。
4、@Around
环绕通知方法对于是否设置返回参数应该取决于被代理类方法,建议是带有返回值,因为AOP往往是针对于多个方法执行的,所有一般都是设置返回值为Object。
参考文章
[1]. Spring AOP中JoinPoint的用法
[2]. Java反射机制getModifiers()方法的作用 在joinpoint
的getModifiers()
返回值
[3]. AOP中获取自定义注解的参数值:使用APO来对注解进行切面包含详细的描述
我是长路,感谢你的耐心阅读。如有问题请指出,我会积极采纳!
欢迎关注我的公众号【长路Java】,分享Java学习文章及相关资料
Q群:851968786 我们可以一起探讨学习
注明:转载可,需要附带上文章链接
最后
以上就是典雅乌冬面为你收集整理的Spring面向切面编程-AOP详解前言介绍AOP一、实现AOP二、认识JoinPont与ProceedingJoinPoint三、对注解进行切面进行权限校验实际业务参考文章参考文章的全部内容,希望文章能够帮你解决Spring面向切面编程-AOP详解前言介绍AOP一、实现AOP二、认识JoinPont与ProceedingJoinPoint三、对注解进行切面进行权限校验实际业务参考文章参考文章所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复