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切点表达式用法总结

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

  1. spring aop 基于schema的aop

    AOP的基本概念: 连接点(Jointpoint):表示需要在程序中插入横切关注点的扩展点,连接点可能是类初始化.方法执行.方法调用.字段调用或处理异常等等,Spring只支持方法执行连接点,在AOP ...

  2. 【Spring】基于@Aspect的AOP配置

    Spring AOP面向切面编程,可以用来配置事务.做日志.权限验证.在用户请求时做一些处理等等.用@Aspect做一个切面,就可以直接实现. ·   本例演示一个基于@Aspect的小demo 1. ...

  3. 【AOP】基于@Aspect的AOP配置

    基于spring cloud的aop配置 1,启动类MemberAppliaction增加注解 @Import({SwaggerConfiguraion.class, WebMvcAutoConfig ...

  4. 基于@Aspect的AOP配置

    1. Spring 除了支持Schema 方式配置 AOP,还支持注解方式:使用 @Aspect 来配置 2. Spring 默认不支持 @Aspect 风格的切面声明,通过如下配置开启@Aspect ...

  5. Spring框架学习09——基于AspectJ的AOP开发

    1.基于注解开发AspectJ (1)AspectJ注解 基于注解开发AspectJ要比基于XML配置开发AspectJ便捷许多,所以在实际开发中推荐使用注解方式.关于注解的相关内容如下: @Aspe ...

  6. 第三章 AOP 基于Schema的AOP

    基于Schema定义的切面和前现两种方式定义的切面,内容上都差不多,只是表现形式不一样而已. 3.7.1一般增强的使用 a.目标类 public class Target { public void ...

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

    将上方配置中的前置通知,可换成环绕通知

  8. 第三章 AOP 基于@AspectJ的AOP

    在前面,我们分别使用Pointcut.Advice.Advisor接口来描述切点.增强.切面.而现在我们使用@AdpectJ注解来描述. 在下面的例子中,我们是使用Spring自动扫描和管理Bean. ...

  9. Spring注解开发系列Ⅵ --- AOP&事务

    注解开发 --- AOP AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,横向重复,纵向抽取.详细的AO ...

随机推荐

  1. HTTP协议中GET和POST的区别(详细描述)

    HTTP协议在现代网络通信中被广泛应用,在HTTP 1.0版本中有7种请求方式,在HTTP 1.1版本中有8种请求方式,而这些请求方式中最常用的就是GET和POST,网上关于GET与POST请求方式的 ...

  2. o.s.b.d.LoggingFailureAnalysisReporter

    1.错误信息 *************************** APPLICATION FAILED TO START *************************** Descripti ...

  3. geth工作运行程序转后台

    今天查看了一下运行程序怎么转后台,然后就发现了之前写的脚本一定要进行console控制台然后在解锁coinbase,然后才手动挖矿的操作真的是太笨了,后面研究了一下,发现是可以在运行语句上进行操作的: ...

  4. hyperledger中文文档学习-2-简介

    参考https://hyperledgercn.github.io/hyperledgerDocs/blockchain_zh/ Hyperledger区块链框架(https://blog.csdn. ...

  5. [Micropython] TPYBoard STM32F407开发板运行第一个脚本

    从这篇教程开始将动手在TPYBoard STM32F407开发板上运行 Python 脚本,下面教大家拿到这个开发板后怎么用!(该款开发板某宝上有售) 1 连接开发板 通过 USB 线连接你的 PC ...

  6. UVA - 10931-Parity

    题意:1.输入一个数,将其转换为二进制.2.记录二进制中出现1的次数. 注意:转换二进制后直接输出,不能转换为十进制后输出 #include<iostream> #include<c ...

  7. Jenkins集成openshift容器中进行代码扫描

    1.Dockerfile sonarDockerfile: (基础slave镜像参考上篇博文) FROM registry.it.com/openshift/jenkins-slave:latest ...

  8. Spring MVC自定义403,404,500状态码返回页面

    代码 HTTP状态码干货:http://tool.oschina.net/commons?type=5 import org.springframework.boot.web.servlet.erro ...

  9. 《React Native 精解与实战》书籍连载「React 与 React Native 简介」

    此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...

  10. 剑指Offer-- 二叉搜索树中和为某一值的路径

    输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径.路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径. 本身题目不是很难,但是因为刚接触pyhon,对一些对象的传 ...