Spring MVC 集成 AOP,自定义注解,在切面获得方法参数,以及自定义注解的参数。

本文实现了,自定义个注解,用来标注切入点,就是说,你想让哪些个方法执行切面的方法,只需要在这些方法上面,添加自定义注解,然后,就可以执行切面的advice啦。

我们在切面可以拿到:

1,当前执行方法的参数。

2,自定义注解上定义的参数。

3,顺便获得当前session里面的用户吧。

要在spring mvc里面集成aop,那么就得先看如何完善配置文件。

这有个前提。

就是你的项目已经是spring mvc啦,我这就不逼逼spring mvc需要的配置啦,下面只说aop相关的配置

首先是:applicationContext.xml

引入命名空间,aop相关的就是下面的2行,为了方便观众,我就把整个的命名空间给贴在下面。

xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
命名空间完了之后,就是aop的注解,我这顺便把这个spring 的扫描也给写这,是因为,声明切面的时候,这个切面也是要声明成一个组件,交给spring 容器帮忙处理一下的。

要是有些老铁,不了解原理的话,直接就只扫描controller或者service文件夹,那不就扫描不到这个切面类了吗,那就运行不起来啦。那就尴尬啦。后面,我会上我的整个测试项目的文件目录概览图,仅供参考吧。

    <!-- 开启spring的扫描注入,使用如下注解 -->
    <!-- @Component,@Repository,@Service,@Controller-->
    <context:component-scan base-package="com.lxk"/>

    <!-- aop 注解实现 -->
    <aop:aspectj-autoproxy/>

然后是:pom.xml引入AOP的jar依赖

        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.10</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.7.2</version>
        </dependency>
这地方,可能有老铁,会只是单独引入上面那个,不过,你跟着我的教程来,那肯定不会出下面这个错啦。

aspectj 使用spring AOP切面编程的时候报错:ReflectionWorld$ReflectionWorldException NoClassDefFoundError 的处理

看看在项目里面,这个jar包的依赖关系图


可以看到,引入的2个jar包是直接被这个项目依赖的,后面没有出现复杂的嵌套依赖,这依赖关系,一目了然。清晰。。。

想知道,这个图怎么来吗?看下面链接。

Intellij IDEA 中如何查看maven项目中所有jar包的依赖关系图


自定义注解:MethodLog

package com.lxk.annotation;

import java.lang.annotation.*;

/**
 * 记录数据库相关的操作如:新建,更新,删除。
 *
 * @author lxk on 2017/12/7
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface MethodLog {
    /**
     * 记录操作描述
     */
    String description() default "";

    /**
     * 增删改的数据的类型
     */
    Class<?> clazz();
}

这个注解,是使用在方法上的。对于注解的使用,还是白板的小伙伴,麻烦移步到下面这个链接,简单瞅瞅,这个注解是怎么声明,怎么使用的

注解之概念的理解

好,注解定义OK之后,就是该使用了,咱先看切面的代码吧。

切面类:CalendarAspect.java

package com.lxk.aop;

import com.lxk.annotation.MethodLog;
import com.lxk.httpModel.SessionInfo;
import com.lxk.model.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
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.lang.reflect.Method;

/**
 * 日历的日志切面操作
 *
 * @author lxk on 2017/12/7
 */
@Component
@Aspect
public class CalendarAspect {

    //声明个切面,切哪呢?切到 com.lxk.service.StudentService 这个目录下,以save开头的方法,方法参数(..)和返回类型(*)不限
    @Pointcut("execution(* com.lxk.service.StudentService.save*(..))")
    private void aa() {
    }//切入点签名

    /**
     * 前置通知
     */
    @Before("aa()")
    public void beforeMethod(JoinPoint joinPoint) {
        System.out.println("before method   start ...");

        System.out.println("before method   end ...");
    }

    /**
     * 环绕通知 around advice
     * 这个切的是 com.lxk.service 包下面以及子包下所有,后面又 && 同时满足带有注解 MethodLog
     */
    @Around(value = "(execution(* com.lxk.service..*(..))) && @annotation(methodLog)", argNames = "joinPoint, methodLog")
    public Object methodAround(ProceedingJoinPoint joinPoint, MethodLog methodLog) throws Throwable {
        System.out.println("Around method    start.......................");
        User user = getUserFromSession();
        if (user != null) {
            System.out.println("Around method                " + user.toString());
        }
        //获得自定义注解的参数
        System.out.println("Around method  methodLog 的参数,remark:" + methodLog.description() + " clazz:" + methodLog.clazz());
        //执行目标方法,并获得对应方法的返回值
        Object result = joinPoint.proceed();
        System.out.println("Around method     返回结果:" + result);
        System.out.println("Around method          end.......................");
        return result;
    }

    /**
     * 最终通知 after advice
     * 使用的是在上面声明的切面,并且带上个注解,意思是除了满足上面aa()方法的条件还得带上注解才OK
     */
    @After(value = "aa() && @annotation(methodLog)", argNames = "joinPoint, methodLog")
    public void methodAfter(JoinPoint joinPoint, MethodLog methodLog) throws Throwable {
        System.out.println("After method     start.......................");
        //获得自定义注解的参数
        System.out.println("After method   methodLog 的参数,remark:" + methodLog.description() + " clazz:" + methodLog.clazz());
        MethodLog remark = getMethodRemark(joinPoint);
        System.out.println("After method        end.......................");
    }

    /**
     * 后置通知
     *
     */
    @AfterReturning(value = "(execution(* com.lxk.service..*(..))) && @annotation(methodLog)", argNames = "joinPoint, methodLog, result",
            returning = "result")
    public void methodAfterReturning(JoinPoint joinPoint, MethodLog methodLog, Object result) throws Throwable {
        System.out.println("AfterReturning method    start.......................");
        System.out.println("AfterReturning method   返回的结果:" + result);
        User user = getUserFromSession();
        if (user != null) {
            System.out.println("AfterReturning  " + user.toString());
        }
        System.out.println("AfterReturning method       end.......................");
    }

    /**
     * 从session里面获得user对象
     */
    private User getUserFromSession() {
        //获取到当前线程绑定的请求对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //已经拿到session,就可以拿到session中保存的用户信息了。
        User user = null;
        try {
            SessionInfo sessionInfo = (SessionInfo) request.getSession().getAttribute("sessionInfo");
            user = sessionInfo.getUser();
        } catch (Exception ignored) {

        }
        return user;
    }

    /**
     * 获取方法的中文备注____用于记录用户的操作日志描述
     */
    private MethodLog getMethodRemark(JoinPoint joinPoint) throws Exception {
        //返回目标对象
        Object target = joinPoint.getTarget();
        String targetName = target.getClass().getName();
        //返回当前连接点签名
        String methodName = joinPoint.getSignature().getName();
        //获得参数列表
        Object[] arguments = joinPoint.getArgs();

        Class targetClass = Class.forName(targetName);
        Method[] method = targetClass.getMethods();
        //这个怎么这么low呢。
        for (Method m : method) {
            if (m.getName().equals(methodName)) {
                Class[] tmpCs = m.getParameterTypes();
                if (tmpCs.length == arguments.length) {
                    MethodLog methodCache = m.getAnnotation(MethodLog.class);
                    if (methodCache != null && !("").equals(methodCache.description())) {
                        return methodCache;
                    }
                    break;
                }
            }
        }
        return null;
    }
}
这个是切面的代码,在aop里面切面的代码,英文对应advice,通知。大概有有如上,前置,后置,最终,环绕,还有个异常,我这个切面没体现,

上面这个代码很多,没使用过的,肯定都不知道啥是啥,当然我也是这么个状态。

分享点东西。酌情看看,你是否需要看一下。

1,execution(* com.lxk.service.StudentService.save*(..))

这个叫切入点表达式,具体表达什么意思,什么语法,可参考下面的链接文章中,xml配置部分,顺便看看最原始的aop的样子。

spring AOP 之 xml 配置实现(附 Java 代码实例)

2,关于JoinPoint的方法和简单注释。

package org.aspectj.lang;

import org.aspectj.lang.reflect.SourceLocation;

public interface JoinPoint {
    String toString();         //连接点所在位置的相关信息
    String toShortString();     //连接点所在位置的简短相关信息
    String toLongString();     //连接点所在位置的全部相关信息
    Object getThis();         //返回AOP代理对象
    Object getTarget();       //返回目标对象
    Object[] getArgs();       //返回被通知方法参数列表
    Signature getSignature();  //返回当前连接点签名
    SourceLocation getSourceLocation();//返回连接点方法所在类文件中的位置
    String getKind();        //连接点类型
    StaticPart getStaticPart(); //返回连接点静态部分
}

3,自定义注解的时候,自己的理解。

spring aop 中@annotation()的使用,绝壁原创的文章


然后是调用的地方,如何调用。

先是controller层的代码

    @ResponseBody
    @RequestMapping(value = "createNewStudent", method = RequestMethod.POST)
    public JsonResult create(@RequestBody Student student) {
        if (student == null) {
            return new JsonResult(false, "student is null");
        }
        //Student result = studentService.save(student);
        Student result = studentService.saveEmptyData(student);
        return result == null
                ? new JsonResult(false, "查无结果")
                : new JsonResult(true, "查找成功", result);
    }
这地方,没啥,就是让观众知道,我这地方轮换着调用了2个save开头的方法。
正儿八经调用的是在service层里面。

    @MethodLog(description = "保存-方法名称save", clazz = Student.class)
    public Student save(Student student) {
        if (student != null) {
            student.setCreateTime(new Date());
        }
        return dao.save(student);
    }

    @MethodLog(description = "保存-方法名称saveEmptyData", clazz = Student.class)
    public Student saveEmptyData(Student student) {
        return null;
    }
这个时候,启动spring 项目,然后切着保存一下,看看切面代码的执行效果如何
//这个是单独执行:studentService.save(student)方法的时候的运行结果。
Around method                start.......................
Around method                methodLog 的参数,remark:保存-方法名称save clazz:class com.lxk.model.Student
before method                start ...
before method                end ...
Around method                返回结果:Student{id='5a3207d8684fc4586f07f0a4', name='李学凯新建', age=18, sex=true, money=null, floor=null}
Around method                end.......................
After method                start.......................
After method                methodLog 的参数,remark:保存-方法名称save clazz:class com.lxk.model.Student
After method                end.......................
AfterReturning method               start.......................
AfterReturning method               返回的结果:Student{id='5a3207d8684fc4586f07f0a4', name='李学凯新建', age=18, sex=true, money=null, floor=null}
AfterReturning method               end.......................

//这个是单独执行:studentService.save(student)方法的时候的运行结果。
Around method                start.......................
Around method                methodLog 的参数,remark:保存-方法名称saveEmptyData clazz:class com.lxk.model.Student
before method                start ...
before method                end ...
Around method                返回结果:null
Around method                end.......................
After method                start.......................
After method                methodLog 的参数,remark:保存-方法名称saveEmptyData clazz:class com.lxk.model.Student
After method                end.......................
AfterReturning method               start.......................
AfterReturning method               返回的结果:null
AfterReturning method               end.......................

从这个运行结果上,我们在切面,

确实可以拿到,咱自定义注解的各个参数的值。这是一个。

还可以拿到咱切入点,也就是目标方法的参数。joinPoint.getArgs()

方法名称啥的,也是可以拿到的,当前所切的类,所切的方法,所切方法的参数,以及所切方法的返回参数。

我这也拿了session里面的 sessionInfo,这个是我自定义的一个对象,然后把这个东西放到session里面。因为我这没放,所以,没有取到user对象。

所以,你得把你的user先放到session里面才能在这取。这个就先不说啦。

还有,就是可以看到这些个不同的切面通知的执行先后的情况


哦,对啦,我的这个测试项目的文件目录结构概览图,还没奉上呢。

差不多都这个样子吧


AOP,重点就是要知道怎么切,切哪,切完之后,能拿到什么参数,这个包括所切方法的参数,所切方法的返回值,等等。




  • 10
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
方法参数上使用自定义注解的作用是为了自定义参数检查和修改参数内容。可以通过使用aop自定义注解来实现这个功能。具体实现思路如下: 1. 使用aop切面拦截到带有自定义注解A的接口参数,并通过反射获取注解A参数的类字段集合。检查字段集合上的注解,如果被另一个注解B标识,则将其存储起来。 2. 将接口传入的json转换为jsonObject,并按照与步骤1相同的格式组织字段名称。 3. 如果发现字段名称与步骤1中存储的字段名称相同,则说明此字段需要修改值,直接修改jsonObject的对应值。 4. 返回修改后的json。 例如,在示例代码中,可以使用自定义注解MethodLog来标识需要记录操作描述的方法。通过aop拦截到带有MethodLog注解的方法,并获取注解中的参数信息。然后可以根据需求对参数进行修改,例如可以通过另一个注解来指定需要修改的字段及其修改内容。 请注意,使用自定义注解修改方法参数的功能只适用于post请求的接口。需要使用aop来实现参数拦截和修改的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [controller接口参数使用自定义注解修改传入值](https://blog.csdn.net/weixin_44807009/article/details/126686565)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Spring MVC 集成 AOP自定义注解,在切面获得方法参数,以及自定义注解参数。](https://blog.csdn.net/qq_27093465/article/details/78800100)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值