V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Vimax
V2EX  ›  Java

Spring AOP 如何将参数传递给需要织入的方法

  •  
  •   Vimax · 2020-07-17 14:36:26 +08:00 · 3228 次点击
    这是一个创建于 1619 天前的主题,其中的信息可能已经有所发展或是发生改变。

    后台管理系统,每个表有对应的创建者,更新者字段。

    当在页面进行操作时,需要先获取将操作者的信息,然后记录到表。

    现在用户的信息是存在 session 中,每次执行方法的时候都要从 session 中获取操作者信息,然后将操作者的信息放入更新的对象中,然后插入 /更新到表中。

    能否借用 AOP 来完成获取登入用户的信息,然后将操作者信息的属性,通过 AOP 传参给方法,然后在方法中直接通过对象来接收。然后再插入 /更新到表中。

    看了 spring AOP 可以通过 args 等方法,向通知传递数据。

    那么反过来,aop 可以给需要织入的方法传递数据吗?

    或者有什么其他方法可以简化获取用户信息,并记录到表中。

    20 条回复    2020-07-20 19:00:14 +08:00
    lemonEssence
        1
    lemonEssence  
       2020-07-17 14:40:14 +08:00
    如果你用的是 JPA 的话可以用 JPA 审计, @CreatedBy @LastModifiedBy
    Vimax
        2
    Vimax  
    OP
       2020-07-17 14:56:15 +08:00
    @lemonEssence 谢谢了。用的是 mybatis 呢。
    avk458
        3
    avk458  
       2020-07-17 15:19:34 +08:00
    提供个获取信息的静态方法,多线程或者异步线程参考 DelegatingSecurityContext
    rockyou12
        4
    rockyou12  
       2020-07-17 15:24:37 +08:00
    使用自定义 spel 非常好,不过稍微有点复杂,我之前写的基于 aop 的 rbac 框架就是这样做的,效果如下。

    @GetMapping("/{id}")
    @Audit(resType = ResType.shop, resId = "#id", resOpt = ResOpt.READ)
    public ShopViewDto getViewById(String id){
    ShopViewDto shopViewDto = shopService.selectDetailById(id);
    return shopViewDto;
    }

    这个 #id 就是读取的方法上的参数,你要读其他的只要反射得出来都可以,甚至是其他 context 里的参数也可以写在 spel 中,但定义 spel 稍微有点复杂
    optional
        5
    optional  
       2020-07-17 15:41:54 +08:00
    其实并没有觉得这样算是干净了,java 里那一套,基本只能用楼上说的 spel 表达式来搞,但是后果就是失去了强类型的加持。
    aop 当然可以给传递数据,但是这样就不干净了, 最好用 threadlocal 这种放在上下文 context 里。
    cedoo22
        6
    cedoo22  
       2020-07-17 16:41:45 +08:00 via iPhone
    之前 用自定义注解 然后 aop 注入到参数里,类似 validation 后加一个验证结果,就是很不面向 Java 编程。
    lewis89
        7
    lewis89  
       2020-07-17 18:04:29 +08:00
    @optional 要是动态了 也轮不到 AOP 了..
    lewis89
        8
    lewis89  
       2020-07-17 18:05:43 +08:00
    @optional AOP 本来就是给静态语言打补丁的 要是语言本身是动态类型的 根本不需要 AOP
    lewis89
        9
    lewis89  
       2020-07-17 18:07:35 +08:00
    @optional 另外切面跟注解本身 并不是为了干净,而是为了可拔插,注解你想加就加 想移除就移除 不会对软件系统造成任何功能性的影响,类似缓存,你移除缓存注解 不会影响软件原本的业务逻辑行为
    optional
        10
    optional  
       2020-07-17 18:08:26 +08:00
    @lewis89 其实我认为问题在 java/jvm 的 annotation 里只能放常量类型,不支持放表达式,否则会好很多。
    lewis89
        11
    lewis89  
       2020-07-17 18:08:39 +08:00
    @optional 切面的逻辑也是类似的,可以通过注解来标识 那些方法需要拦截,这样设计代码模块 非常容易拔插
    optional
        12
    optional  
       2020-07-17 18:09:25 +08:00
    哪怕是编译器常量都不行
    optional
        13
    optional  
       2020-07-17 18:11:27 +08:00
    @lewis89 aop 本身是个 decorate 模式,这个没有问题问题,问题只支持常量类型,然后为了突破这个,搞出 spel 这种,就很脏。
    magicdu
        14
    magicdu  
       2020-07-17 18:14:45 +08:00 via Android
    mybatisplus 有个 metaobjecthandler
    optional
        15
    optional  
       2020-07-17 18:15:42 +08:00
    @lewis89 在 spring 这套体系里,aop 已经不能叫『可插拔』了,去掉注解和增加注解本质上是两个程序了。
    aspect 里很脏的,还有 @Pointcut 这种,专门写个空方法来提供逻辑。。你说他不脏?
    magicdu
        16
    magicdu  
       2020-07-17 18:21:56 +08:00
    @magicdu #14
    ```
    /**
    * 处理新增和更新的基础数据填充,配合 BaseEntity 和 MyBatisPlusConfig 使用
    */
    @Component
    public class MetaHandler implements MetaObjectHandler {


    /**
    * 新增数据执行
    * @param metaObject
    */
    @Override
    public void insertFill(MetaObject metaObject) {

    UserDetails user;
    try {
    user = SecurityUtils.getUserDetails();
    this.setFieldValByName("crtUserName", user.getUsername(), metaObject);
    this.setFieldValByName("crtUserId", SecurityUtils.getUserId(), metaObject);
    this.setFieldValByName("updUserName", user.getUsername(), metaObject);
    this.setFieldValByName("updUserId", SecurityUtils.getUserId(), metaObject);
    } catch (Exception e) {

    }
    this.setFieldValByName("crtTime", new Date(), metaObject);
    this.setFieldValByName("updTime", new Date(), metaObject);


    }

    /**
    * 更新数据执行
    * @param metaObject
    */
    @Override
    public void updateFill(MetaObject metaObject) {
    UserDetails user;
    try {
    user = SecurityUtils.getUserDetails();
    this.setFieldValByName("updUserName", user.getUsername(), metaObject);
    this.setFieldValByName("updUserId", SecurityUtils.getUserId(), metaObject);
    } catch (Exception e) {

    }
    this.setFieldValByName("updTime", new Date(), metaObject);
    }
    }
    ```
    配合 BaseEntity 和 MyBatisPlusConfig 使用
    ```
    @Configuration
    public class MyBatisPlusConfig {

    /**
    * 自动填充功能
    * @return
    */
    @Bean
    public GlobalConfig globalConfig() {
    GlobalConfig globalConfig = new GlobalConfig();
    globalConfig.setMetaObjectHandler(new MetaHandler());
    return globalConfig;
    }

    }
    ```
    totoro52
        17
    totoro52  
       2020-07-17 18:33:16 +08:00
    我采用的是注解拦截 自写了一个注解类 然后标注需要验证 token 的方法 如果这个方法参数需要一个 user 类的话 就注入一个用户实体类 不知道符不符合楼主的需求

    if(method.getAnnotation(TokenAccess.class).userHold()){
    for (int i = 0;i<args.length;i++) {
    // 需要做进一步判断,判断这个方法是否需要这个 user
    if(args[i]!=null && args[i].getClass() == User.class){
    args[i] = user;
    }
    }
    }
    EscYezi
        18
    EscYezi  
       2020-07-18 21:31:00 +08:00 via iPhone
    请求用过滤器把 session 里面的对象保存好写到 ThreadLocal,然后 aop 直接取 ThreadLocal 里的 user 对象,写表的操作在 aop 里面来调用。
    贴中的用 AOP 获取用户对象注入到方法参数里这个不太能理解,写表的操作是由被切的方法执行的?如果是这样的话,参数直接加个 HttpSession 再封一个工具类不就好了
    EscYezi
        19
    EscYezi  
       2020-07-18 21:34:01 +08:00 via iPhone
    抱歉抱歉,没有看到是要把用户信息放在属性里.....
    siweipancc
        20
    siweipancc  
       2020-07-20 19:00:14 +08:00 via iPhone
    :D 你这个只能在 Around 织入,官方有 demo, 深入的话建议看下 EntityListener 和 Spring Cache 的实现源码。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2863 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 14:26 · PVG 22:26 · LAX 06:26 · JFK 09:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.