如何使用spring boot Aop拦截到封装实体的getXxx方法

问题遇到的现象和发生背景

我封装了个实体

@Data
public UserDto{
private String userId;
private String userName
}
我想要达到的结果

在设定中 userId是肯定有值
我想拦截 getUserName()方法
判断如果userName 为空
我根据userId去处理一个 userName值作为返回
但是 测试AOP 貌似拦截不到get方法

你是不是可以重写一下getUserName()方法,在方法里面增加userName为空的判断和处理逻辑。没必要非去拦截get方法啊

别想了,没交给容器管理,你就是个普通对象而非代理对象!只能将new一个对象,改成生成代理对象,这样才能执行aop操作!

写一个自定义注解,将该注解打在你的get方法上,然后aop切面该注解,这样也是清晰明了,后期无论多少个类,都可以使用该注解进行切面,但是切面的前提是该类必须是bean,你这个实体放进去没意义,建议做一个接口,接口定义一个方法,所有需要操作的实体都实现该接口,重写方法,方法中获取username 如果为空那么返回id

要用spring的aop切面,首先对象得是spring管理的bean,你这实体类不可能生成bean去让spring管理的;
如果只是单纯的想让name为空时根据id去设置name,实体类可以直接对name设置默认值 = id;
或者用反射去根据对象获取值再进行判断填充

Spring AOP 是在运行时动态为所定义的 bean 创建代理,拦截 bean 方法的执行,你定义的 DTO 不是 bean,因此拦截不到 DTO 方法的执行。

只能使用 AspectJ 在编译时生成字节码拦截 DTO 方法的执行。

这种的应该是将aop做在controller层面吧.可以在自定义个一个注解,然后将aop作用到这个注解上,然后在需要调用的地方加上自定义注解。dto再去处理感觉很不合理,要么在controller切面去处理,要么在service处理。切面实现类似如下

@RequestMapping()
@BeforeXxx
public void getXxx()

@before(@annotation(BeforeXxx))
public  void beforeXxx(final JointPoint joinPoint){
//根据id设置name
}

如果是controller层的话定义一个注解,做restcontrolleradvice在读取body前拦截把实体转成map去get你要get的属性然后判断

建议换一个思路处理问题:
增加统一得BaseDto 把 xxxxId 和 xxxxName放到BaseDto里面,然后你其他的Dto继承BaseDto就行了

因为你的Bean不是交给spring托管的,想直接拦截你的实体是不可能的.可以拦截controller,在返回的时候拦截进行统一处理。下面的是我们进来和返回分别处理表情转码的例子

img

你可以在那个get属性的时候进行判断 在这个方法里写相关的操作

插个天眼

首先,这个想法是不对的,aop的前提是这个对象是容器创建的,很明显dto并不是容器创建的;
如果你想实现描述的功能,只能从侧面间接做到切面效果,比如在dao层做切面,检测返回结果中字段满足你的要求的情况下对返回结果做修订,这个相信你可以自己实现;
其他方式多少都需要对你的dto做修改,然而这恰恰不是你想要的。。。

突然想起一个东东貌似可以满足你的需求,不过有点麻烦,AspectJ你可以了解一下

感觉反射就能做

提供一下思路参考:

  1. 将你这两个属性抽离成一个父类实体,后续有这样业务操作的继承这个类。
  • 后续可以写一个工具类去操作。
  1. AOP拦截
  • 如果你这个实体类是方法的参数,那么从拦截器里面判断参数。

    切记不要在方法内部出现new XXXDTO() 的操作,会失效。如果是这种方式参考1解决。 如果是从数据库查询出来的,也是一样。必须得从方法拦截里面去处理这个DTO。

  1. 反射

    和1有差不多的效果,编码式判断。不用继承。

  2. 非侵入的方式

    稍微比较复杂,参考arthas产品。

创建注解

package com.zhao.annotationaop.annotation;

import org.springframework.core.annotation.Order;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
//最高优先级
@Order(1)
public @interface InterceptGetName {
    String value() default "getName";
}

通过AOP实现注解功能

package com.zhao.annotationaop.accomplish;

import com.zhao.annotationaop.annotation.InterceptGetName;
import com.zhao.annotationaop.entity.User;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Aspect
@Scope
@Component
public class accomplishGetNameAnnotation {

    @Before("within(@org.springframework.web.bind.annotation.RestController *) && @annotation(getName)")
    public void requestLimit(final JoinPoint joinPoint, InterceptGetName getName) throws IOException {
        Object[] args = joinPoint.getArgs();
        User user = (User) args[0];
        String value = getName.value();
        switch (value) {
            case "getName": {
                String name = user.getName();
                if (name == null) {
                    user.setName("123");
                }
            }
            ;
            break;
            case "getId": {
                String id = user.getId();
                if (id == null) {
                    user.setId("456");
                }
            }
            ;
            break;
            default:
                break;
        }
    }
}

编写测试Controller

package com.zhao.annotationaop.controller;

import com.zhao.annotationaop.annotation.InterceptGetName;
import com.zhao.annotationaop.annotation.RequestLimit;
import com.zhao.annotationaop.entity.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
public class TestController {

    


    @InterceptGetName(value = "getId")
    @RequestMapping("/test2")
    public String test2(User user){
        System.out.println(user);
        return "123";
    }
}

启动访问测试

img

返回结果

img

采用反射来直接获取吧,自己写的话可能有冲突

写一个自定义注解,将pointcut定义为该注解,然后把注解写在getxx上

封装一个方法,入参是UserDto的接口类型,然后在里面调用getUserName,根据返回结果统一做处理。


 /**
     *
     * @param obj
     * @param mapFiled userName ,userId 如果userName获取不到值 使用 userId 经过function进行处理,且userId 不为空
     * @param function
     * @param <T>
     */
    public static <T> void doNameDeal(Object obj, Map<String,String> mapFiled, Function<String,T> function){
        Class<?> aClass = obj.getClass();
        if (mapFiled==null || mapFiled.isEmpty()|| function == null){
            return;
        }
        for (Map.Entry<String, String> entry : mapFiled.entrySet()) {
            String key = entry.getKey();
            String val = entry.getValue();
            try {
                Field nameField = aClass.getDeclaredField(key);
                nameField.setAccessible(true);
                if (nameField.get(obj) == null){
                    Field idField = aClass.getDeclaredField(val);
                    idField.setAccessible(true);
// 题目意思是该值不为空,直接toString了
                    String id = idField.get(obj).toString();
                    T applyV = function.apply(id);
                    nameField.set(obj,applyV);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    @Data
    public static class UserDto{
        private String userId;
        private String userName;
    }
    public static String getNameById(String id){
        //模拟按照id获取名字
        return id+"---";
    }

    public static void main(String[] args) {
        UserDto dto = new UserDto();
        dto.setUserId("1");
        dto.setUserName("name");
        Map<String,String> map = new HashedMap();
        map.put("userName","userId");
        doNameDeal(dto,map,AlipayFactory::getNameById);

        System.out.println(dto);

        dto.setUserName(null);

        doNameDeal(dto,map,AlipayFactory::getNameById);
        System.out.println(dto);
    }

AlipayFactory.UserDto(userId=1, userName=name)
AlipayFactory.UserDto(userId=1, userName=1---)
如果所有的xxxID和xxxName 所有的dto处理的逻辑都一样,那么久这么写很方便

自定义注解不难解决

现代的开发集成工具IDE都有批量替换功能,我觉得你就不应该试图批量拦截实体类的getName方法,如果需要改,就做批量替换。

@After("execution(* com.test.entity..get(..))")
@After("execution(
com.test.entity.
.*set(..))")