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()); //[email protected]

//被代理对象
Object target = joinPoint.getTarget();
logger.info(target.toString()); //[email protected]

//获取连接点的方法签名对象
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)
  [email protected](annotationType) 匹配带有指定注解的类,,within为配置指定类型的类实例

下面匹配含有 @Component注解的类

"@within(org.springframework.stereotype.Component)"
  [email protected](annotationType) 匹配带有指定注解的方法

[email protected](annotationType)

@args表示使用指定注解标注的类作为某个方法的参数时该方法将会被匹配

可以使用&&、||、!、三种运算符来组合切点表达式,表示与或非的关系。

@Around(value = "pointcut1() || pointcut2()")

github代码地址    路径:\springboot\SpringBoot基础\springboot29

参考:Springboot(二十一)@Aspect 切面注解使用

参考:    Spring AOP切点表达式用法总结

原文地址:https://www.cnblogs.com/aspirant/p/10288903.html

时间: 2024-10-27 04:59:22

spring 注解 之 AOP基于@Aspect的AOP配置的相关文章

spring aop 基于schema的aop

AOP的基本概念: 连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化.方法执行.方法调用.字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP中表示为"在哪里干": 切入点(Pointcut):选择一组相关连接点的模式,即可以认为连接点的集合,Spring支持perl5正则表达式和AspectJ切入点模式,Spring默认使用AspectJ语法,在AOP中表示为"在哪里干的集合":(选取我们所需要的连接点的集

【Spring】基于@Aspect的AOP配置

Spring AOP面向切面编程,可以用来配置事务.做日志.权限验证.在用户请求时做一些处理等等.用@Aspect做一个切面,就可以直接实现. ·   本例演示一个基于@Aspect的小demo 1.新建一个Maven工程 2.引入相关maven依赖 1 <project xmlns="http://maven.apache.org/POM/4.0.0" 2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quo

【Spring】IOC之基于Java类的配置Bean

林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 基于Java配置选项,可以编写大多数的Spring不用配置XML,但有几个基于Java的注释的帮助下解释.从Spring3.0开始支持使用java代码来代替XML来配置Spring,基于Java配置Spring依靠Spring的JavaConfig项目提供的很多优点.通过使用@Configuration, @Bean ,@Importand,@DependsOnannotations来实现J

(五)使用注解开发,基于java类进行配置

1.使用注解代替bean 配置扫描哪些包下的注解 <!--指定注解扫描包--> <context:component-scan base-package="com.alan.pojo"/> 在指定包下编写类,增加注解 @Component("user") // 相当于配置文件中 <bean id="user" class="当前注解的类"/> public class User { publ

基于aspect实现AOP——xml配置的其他操作

将上方配置中的前置通知,可换成环绕通知 原文地址:https://www.cnblogs.com/cn-chy-com/p/9260031.html

开涛spring3(6.3) - AOP 之 6.3 基于Schema的AOP

6.3  基于Schema的AOP 基于Schema的AOP从Spring2.0之后通过“aop”命名空间来定义切面.切入点及声明通知. 在Spring配置文件中,所以AOP相关定义必须放在<aop:config>标签下,该标签下可以 有<aop:pointcut>.<aop:advisor>.<aop:aspect>标签,配置顺序不可变. <aop:pointcut>:用来定义切入点,该切入点可以重用: <aop:advisor>:

Spring AOP基于注解的“零配置”方式

Spring AOP基于注解的“零配置”方式: Spring的beans.xml中 <!-- 指定自动搜索Bean组件.自动搜索切面类 --> <context:component-scan base-package="org.crazyit.app.service,org.crazyit.app.aspect"> <context:include-filter type="annotation" expression="or

spring学习5:基于注解实现spring的aop

目录 spring学习5:基于注解实现spring的aop 一.基于注解+xml实现 1.1 在配置文件中开启spring对注解aop的支持 1.2 把通知类用注解配置到容器中,并用注解声明为切面 1.3 定义切入点表达式 1.4 定义通知 二.基于纯注解实现 三.多个aop的执行顺序 1.xml配置 2.注解配置 3.注意 spring学习5:基于注解实现spring的aop 上一节学习了spring aop的基本概念和如何基于xml配置来实现aop功能.这一节来学习下如何用注解实现aop 一

Spring的AOP基于AspectJ的注解方式开发3

上上偏博客介绍了@Aspect,@Before 上篇博客介绍了spring的AOP开发的注解通知类型:@Before,@AfterThrowing,@After,@AfterReturning,@Around 也介绍了JoinPoint和ProceedingJoinPoint的区别 这篇博客讲@PointCut的使用,切入点注解----转载自https://www.cnblogs.com/ltfxy/p/9885742.html 这种方法便于维护管理 /** * 切面类:注解的切面类 */ @A