spring 注解 之 AOP基于@Aspect的AOP配置
Spring AOP面向切面编程,可以用来配置事务、做日志、权限验证、在用户请求时做一些处理等等。用@Aspect做一个切面,就可以直接实现。
1.首先定义一个切面类,加上@Component @Aspect这两个注解
@Component
@Aspect
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
}
2.定义切点
private final String POINT_CUT = "execution(public * com.xhx.springboot.controller.*.*(..))";
@Pointcut(POINT_CUT)
public void pointCut(){}
切点表达式中,..两个点表明多个,*代表一个, 上面表达式代表切入com.xhx.springboot.controller包下的所有类的所有方法,方法参数不限,返回类型不限。 其中访问修饰符可以不写,不能用*,,第一个*代表返回类型不限,第二个*表示所有类,第三个*表示所有方法,..两个点表示方法里的参数不限。 然后用@Pointcut切点注解,想在一个空方法上面,一会儿在Advice通知中,直接调用这个空方法就行了,也可以把切点表达式卸载Advice通知中的,单独定义出来主要是为了好管理。
3.Advice,通知增强,主要包括五个注解Before,After,AfterReturning,AfterThrowing,Around,下面代码中关键地方都有注释,我都列出来了。
@Before 在切点方法之前执行
@After 在切点方法之后执行
@AfterReturning 切点方法返回后执行
@AfterThrowing 切点方法抛异常执行
@Around 属于环绕增强,能控制切点执行前,执行后,,用这个注解后,程序抛异常,会影响@AfterThrowing这个注解
package com.xhx.springboot.config;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.SourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
@Component
@Aspect
public class LogAspect {
private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final String POINT_CUT = "execution(public * com.xhx.springboot.controller.*.*(..))";
@Pointcut(POINT_CUT)
public void pointCut(){}
@Before(value = "pointCut()")
public void before(JoinPoint joinPoint){
logger.info("@Before通知执行");
//获取目标方法参数信息
Object[] args = joinPoint.getArgs();
Arrays.stream(args).forEach(arg->{ // 大大
try {
logger.info(OBJECT_MAPPER.writeValueAsString(arg));
} catch (JsonProcessingException e) {
logger.info(arg.toString());
}
});
//aop代理对象
Object aThis = joinPoint.getThis();
logger.info(aThis.toString()); //com.xhx.springboot.controller.HelloController@69fbbcdd
//被代理对象
Object target = joinPoint.getTarget();
logger.info(target.toString()); //com.xhx.springboot.controller.HelloController@69fbbcdd
//获取连接点的方法签名对象
Signature signature = joinPoint.getSignature();
logger.info(signature.toLongString()); //public java.lang.String com.xhx.springboot.controller.HelloController.getName(java.lang.String)
logger.info(signature.toShortString()); //HelloController.getName(..)
logger.info(signature.toString()); //String com.xhx.springboot.controller.HelloController.getName(String)
//获取方法名
logger.info(signature.getName()); //getName
//获取声明类型名
logger.info(signature.getDeclaringTypeName()); //com.xhx.springboot.controller.HelloController
//获取声明类型 方法所在类的class对象
logger.info(signature.getDeclaringType().toString()); //class com.xhx.springboot.controller.HelloController
//和getDeclaringTypeName()一样
logger.info(signature.getDeclaringType().getName());//com.xhx.springboot.controller.HelloController
//连接点类型
String kind = joinPoint.getKind();
logger.info(kind);//method-execution
//返回连接点方法所在类文件中的位置 打印报异常
SourceLocation sourceLocation = joinPoint.getSourceLocation();
logger.info(sourceLocation.toString());
//logger.info(sourceLocation.getFileName());
//logger.info(sourceLocation.getLine()+"");
//logger.info(sourceLocation.getWithinType().toString()); //class com.xhx.springboot.controller.HelloController
///返回连接点静态部分
JoinPoint.StaticPart staticPart = joinPoint.getStaticPart();
logger.info(staticPart.toLongString()); //execution(public java.lang.String com.xhx.springboot.controller.HelloController.getName(java.lang.String))
//attributes可以获取request信息 session信息等
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
logger.info(request.getRequestURL().toString()); //http://127.0.0.1:8080/hello/getName
logger.info(request.getRemoteAddr()); //127.0.0.1
logger.info(request.getMethod()); //GET
logger.info("before通知执行结束");
}
/**
* 后置返回
* 如果第一个参数为JoinPoint,则第二个参数为返回值的信息
* 如果第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
* returning:限定了只有目标方法返回值与通知方法参数类型匹配时才能执行后置返回通知,否则不执行,
* 参数为Object类型将匹配任何目标返回值
*/
@AfterReturning(value = POINT_CUT,returning = "result")
public void doAfterReturningAdvice1(JoinPoint joinPoint,Object result){
logger.info("第一个后置返回通知的返回值:"+result);
}
@AfterReturning(value = POINT_CUT,returning = "result",argNames = "result")
public void doAfterReturningAdvice2(String result){
logger.info("第二个后置返回通知的返回值:"+result);
}
//第一个后置返回通知的返回值:姓名是大大
//第二个后置返回通知的返回值:姓名是大大
//第一个后置返回通知的返回值:{name=小小, id=1}
/**
* 后置异常通知
* 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
* throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
* 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
* @param joinPoint
* @param exception
*/
@AfterThrowing(value = POINT_CUT,throwing = "exception")
public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){
logger.info(joinPoint.getSignature().getName());
if(exception instanceof NullPointerException){
logger.info("发生了空指针异常!!!!!");
}
}
@After(value = POINT_CUT)
public void doAfterAdvice(JoinPoint joinPoint){
logger.info("后置通知执行了!");
}
/**
* 环绕通知:
* 注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用
*
* 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(value = POINT_CUT)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
logger.info("@Around环绕通知:"+proceedingJoinPoint.getSignature().toString());
Object obj = null;
try {
obj = proceedingJoinPoint.proceed(); //可以加参数
logger.info(obj.toString());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
logger.info("@Around环绕通知执行结束");
return obj;
}
}
执行前后顺序是这样
: @Around环绕通知
: @Before通知执行
: @Before通知执行结束
: @Around环绕通知执行结束
: @After后置通知执行了!
: @AfterReturning第一个后置返回通知的返回值:18
org.aspectj.lang.JoinPoint : 方法中的参数JoinPoint为连接点对象,它可以获取当前切入的方法的参数、代理类等信息,因此可以记录一些信息,验证一些信息等。
org.aspectj.lang.ProceedingJoinPoint: 为JoinPoint的子类,多了两个方法
public Object proceed() throws Throwable; //调用下一个advice或者执行目标方法,返回值为目标方法返回值,因此可以通过更改返回值,修改方法的返回值
public Object proceed(Object[] args) throws Throwable; //参数为目标方法的参数 因此可以通过修改参数改变方法入参
可以用下面的方式获取request、session等对象
//attributes可以获取request信息 session信息等
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
下面介绍一下切点表达式:
1.execution(方法修饰符 返回类型 方法全限定名(参数)) 主要用来匹配整个方法签名和返回值的
"execution(public * com.xhx.springboot.controller.*.*(..))"
*只能匹配一级路径
..可以匹配多级,可以是包路径,也可以匹配多个参数
+ 只能放在类后面,表明本类及所有子类
还可以按下面这么玩,所有get开头的,第一个参数是Long类型的
@Pointcut("execution(* *..get*(Long,..))")
2. within(类路径) 用来限定类,同样可以使用匹配符
下面用来表示com.xhx.springboot包及其子包下的所有类方法
"within(com.xhx.springboot..*)"
3. this与target
this与target在用法上有些重合,理解上有对比性。
this表示当前切入点表达式所指代的方法的对象的实例,即代理对象是否满足this类型
target表示当前切入点表达式所指代的方法的目标对象的实例 即是否是为target类做的代理
如果当前对象生成的代理对象符合this指定的类型,则进行切面,target是匹配业务对象为指定类型的类,则进行切面。
生成代理对象时会有两种方法,一个是CGLIB一个是jdk动态代理。
用下面三个例子进行说明:
this(SomeInterface)或target(SomeInterface):这种情况下,无论是对于Jdk代理还是Cglib代理,其目标对象和代理对象都是实现SomeInterface接口的(Cglib生成的目标对象的子类也是实现了SomeInterface接口的),因而this和target语义都是符合的,此时这两个表达式的效果一样;
this(SomeObject)或target(SomeObject),这里SomeObject没实现任何接口:这种情况下,Spring会使用Cglib代理生成SomeObject的代理类对象,由于代理类是SomeObject的子类,子类的对象也是符合SomeObject类型的,因而this将会被匹配,而对于target,由于目标对象本身就是SomeObject类型,因而这两个表达式的效果一样;
this(SomeObject)或target(SomeObject),这里SomeObject实现了某个接口:对于这种情况,虽然表达式中指定的是一种具体的对象类型,但由于其实现了某个接口,因而Spring默认会使用Jdk代理为其生成代理对象,Jdk代理生成的代理对象与目标对象实现的是同一个接口,但代理对象与目标对象还是不同的对象,由于代理对象不是SomeObject类型的,因而此时是不符合this语义的,而由于目标对象就是SomeObject类型,因而target语义是符合的,此时this和target的效果就产生了区别;这里如果强制Spring使用Cglib代理,因而生成的代理对象都是SomeObject子类的对象,其是SomeObject类型的,因而this和target的语义都符合,其效果就是一致的。
4.args(paramType)
args无论其类路径或者是方法名是什么,表达式的作用是匹配指定参数类型和指定参数数量的方法,类型用全路径
args(java.lang.String,..,java.lang.Integer)
5.@within(annotationType) 匹配带有指定注解的类,,within为配置指定类型的类实例
下面匹配含有 @Component注解的类
"@within(org.springframework.stereotype.Component)"
6.@annotation(annotationType) 匹配带有指定注解的方法
7.@args(annotationType)
@args表示使用指定注解标注的类作为某个方法的参数时该方法将会被匹配
可以使用&&、||、!、三种运算符来组合切点表达式,表示与或非的关系。
@Around(value = "pointcut1() || pointcut2()")
github代码地址 路径:\springboot\SpringBoot基础\springboot29
参考:Springboot(二十一)@Aspect 切面注解使用
spring 注解 之 AOP基于@Aspect的AOP配置的更多相关文章
- spring aop 基于schema的aop
AOP的基本概念: 连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化.方法执行.方法调用.字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP ...
- 【Spring】基于@Aspect的AOP配置
Spring AOP面向切面编程,可以用来配置事务.做日志.权限验证.在用户请求时做一些处理等等.用@Aspect做一个切面,就可以直接实现. · 本例演示一个基于@Aspect的小demo 1. ...
- 【AOP】基于@Aspect的AOP配置
基于spring cloud的aop配置 1,启动类MemberAppliaction增加注解 @Import({SwaggerConfiguraion.class, WebMvcAutoConfig ...
- 基于@Aspect的AOP配置
1. Spring 除了支持Schema 方式配置 AOP,还支持注解方式:使用 @Aspect 来配置 2. Spring 默认不支持 @Aspect 风格的切面声明,通过如下配置开启@Aspect ...
- Spring框架学习09——基于AspectJ的AOP开发
1.基于注解开发AspectJ (1)AspectJ注解 基于注解开发AspectJ要比基于XML配置开发AspectJ便捷许多,所以在实际开发中推荐使用注解方式.关于注解的相关内容如下: @Aspe ...
- 第三章 AOP 基于Schema的AOP
基于Schema定义的切面和前现两种方式定义的切面,内容上都差不多,只是表现形式不一样而已. 3.7.1一般增强的使用 a.目标类 public class Target { public void ...
- 基于aspect实现AOP——xml配置的其他操作
将上方配置中的前置通知,可换成环绕通知
- 第三章 AOP 基于@AspectJ的AOP
在前面,我们分别使用Pointcut.Advice.Advisor接口来描述切点.增强.切面.而现在我们使用@AdpectJ注解来描述. 在下面的例子中,我们是使用Spring自动扫描和管理Bean. ...
- Spring注解开发系列Ⅵ --- AOP&事务
注解开发 --- AOP AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,横向重复,纵向抽取.详细的AO ...
随机推荐
- LINUX 查看当前系统的内存使用情况 free
每天学习一点点 编程PDF电子书.视频教程免费下载:http://www.shitanlife.com/code # free 显示结果如下: Mem:表示物理内存统计 total 内存总数 8057 ...
- 二、Oracle 数据库基本操作
一.oracle常用数据类型数字:number(p,s) p表示数字的长度包括小数点后的位数,s表示小数点后的位数固定长度字符:char(n):n表示最大长度,n即是最大也是固定的长度,当数据不满长度 ...
- SpringMVC配置多视图-内容协商原理
SpringMVC配置多视图-内容协商原理 2014年03月06日 16:46:59 日积月累_滴水石穿 阅读数:10964更多 个人分类: SpringMVC Spring Framework ...
- Python 包内的导入问题(绝对导入和相对导入)
基本概念 Python 中的包,即包含 __init__.py 文件的文件夹. 对于 Python 的包内导入,即包内模块导入包内模块,存在绝对导入和相对导入问题. 普通 Python 模块的搜索路径 ...
- Python脱产8期 Day08 2019/4/22
一.三种字符串 1.普通字符串:u'以字符作为输出单位‘ #print(u‘abc’)#用于显示 2.二进制字符串:b'以字节作为输出单位’#用于传输 3.原义字符串:r‘以字符作为输出单位,原样输 ...
- Java IO(四)——字符流
一.字符流 字节流提供了处理任何类型输入/输出操作的功能(因为对于计算机而言,一切都是0和1,只需把数据以字节形式表示就够了),但它们不可以直接操作Unicode字符,因为一个Unicode字符占用2 ...
- logistic回归和最大熵
回顾发现,李航的<统计学习方法>有些章节还没看完,为了记录,特意再水一文. 0 - logistic分布 如<统计学习方法>书上,设X是连续随机变量,X服从logistic分布 ...
- 关于Spring Data JPA更新部分字段的问题
1.问题背景 个人比较喜欢Spring data JPA,这次的问题是在实体类中使用List类型作为字段,JPA也提供了操作的方法,即使用@ElementCollection注解,网上对于JPA的知识 ...
- 深入理解Java中的反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制. ...
- 【C#复习总结】细说 Lambda表达式
1 前言 本系列会将[委托] [匿名方法][Lambda表达式] [泛型委托] [表达式树] [事件]等基础知识总结一下.(本人小白一枚,有错误的地方希望大佬指正) 系类1:细说委托 系类2:细说匿名 ...