参考Mybatis 撸了个自定义注解框架

517 阅读3分钟

需求

  车辙在平时的工作,经常碰到需要对前端上传数据进行处理的问题,例如元转分,去空,脱敏等。 在GitHub上转了半天,都是自己定义好的,完全没有扩展点。
在车辙的一顿操作下,参考了Mybatis MapperScan的源码,车辙在此基础上写了个小框架,代码侵入性低的同时用户可以自定义注解和实现类满足需求。

如何使用

1. 项目打包,并依赖(地址在最下方)

    <dependency>
        <groupId>cn.dface</groupId>
        <artifactId>anno</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </dependency>

2.启动类添加扫描注解实现类包路径

SpringBoot

@HandleBeanScanAnno(basePackages = "cn.dface.annocustom.web.back.anno.implHandler")

SpringMVC

<context:component-scan base-package="com.xxx.**.cotroller"/> 我也没试过😄

3.定义注解

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TrimBlankAnno {
        String value() default "" ;
        String methodName() default "" ;
        int level() default 1;
    }

4.写实现类在(2的包路径下)

    @Slf4j
    public class TrimBlankAnnoServiceImpl extends AbstractHandleAnno implements HandleBeanParentService {
        @Override
        public void setAnnotation() {
            this.annotation = TrimBlankAnno.class;
        }
        @Override
        public Object doParam(Object param) {
            String str = ((String)param).replaceAll("1","333");
            return str;
        }
    }

5.在Controller上添加包自带注解

@PostMapping("/testTrimAnno")
@HandleBeanAnno
public Object testTrimAnno( @RequestBody TestBean setBankInfoBean) {
    System.out.println(JSON.toJSONString(setBankInfoBean));
    return ResultVO.newSuccess(setBankInfoBean);
}

6.在入参Bean和出参Bean上添加注解

@TrimBlankAnno(level = 2)
@AddSpaceAnno
private String bankCardNo;

@YuanToFenAnno
private Integer amount;

7. 其他功能

  1. 参数业务检查,不符合业务要求直接抛出异常
  2. 不用再参数上写多个注解,一个注解满足多个条件,如先判断非空,在判断格式

8. 效果

实现类

@Slf4j
public class YuanToFenServiceImpl extends AbstractHandleAnno implements HandleBeanParentService {

    @Override
    public void setAnnotation() {
        this.annotation = YuanToFenAnno.class;
    }


    @Override
    public Object doParam(Object param) {
        Integer originParam = (Integer) param;
        return  originParam * 100;
    }
}

入参

{
    "bankCardNo": " 123 4 5 6 ", // 去空格处理  然后加上空格
    "amount": 10         // 返回值元转分
}

出参

{
    "status": 0,
    "errorCode": null,
    "errorMsg": null,
    "object": {
        "bankCardNo": "123456      ",
        "amount": 1000
    }
}

源码分析

定义注册扫描包的注解

/**
 * 处理bean的注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(HandleBeanScanRegister.class)
public @interface HandleBeanScanAnno {
    String basePackages() default "com";
}

扫描包,注册bean, 保存类(参考Mybatis和Dubbo)

public class HandleBeanScanRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        // 通过注解map获取注解
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(HandleBeanScanAnno.class.getName()));
        // 设置register用于set beanDefinition
        HandleBeanParentScanner scanner = new HandleBeanParentScanner(registry,false);

        //设置资源加载器
        if (resourceLoader != null) {
            scanner.setResourceLoader(resourceLoader);
        }

        //获取注解中的包
        List<String> basePackages = new ArrayList<String>();
        for (String pkg : annoAttrs.getStringArray("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        //接受所有,不设置过滤Filter
        scanner.acceptAll();
        //获取创建bean后的BeanDefinitionHolder SET,并设置到map,用于后期通过名称获取bean
        Set<BeanDefinitionHolder> beanDefinitionHolderSet = scanner.doScan(StringUtils.toStringArray(basePackages));
        beanDefinitionHolderSet.forEach(beanDefinitionHolder -> {
            HandleBeanAnnoProperty.registerClassNames.add(beanDefinitionHolder.getBeanName());
        });

    }
}

刷新上下文时获取ServiceBean并保存

@Component
public class HandleBeanAnnoProperty implements ApplicationListener<ContextRefreshedEvent> {
    // 已经注册的所有的类名
    public static List<String> registerClassNames = new ArrayList<>();
    public static List<HandleBeanParentService> beanServiceList = new ArrayList<>();
    public static List<HandleParamParentService> paramServiceList = new ArrayList<>();

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        //所有的bean 放入list中
        registerClassNames.forEach(beanName ->{
            Object bean = SpringContext.getBean(beanName);
            if(bean instanceof  HandleBeanParentService){
                beanServiceList.add((HandleBeanParentService) bean);
            }
            // 如果是param的参数,放入resolvers中
            if(bean instanceof  HandleParamParentService){
                paramServiceList.add((HandleParamParentService) bean);
            }
        });
    }
}

AOP 处理入参和出参

@Aspect
@Slf4j
public class HandleBeanAnnoHandler {
    private String RESULT_OBJECT_NAME = "object";


    /**
     * point 扩展点
     * around 通知
     */
    @Around(value = "@annotation(around)")
    public Object initBean(ProceedingJoinPoint point, HandleBeanAnno around) throws Throwable {

        // 获取方法入参的参数
        Object[] submitBeans = point.getArgs();

        // 每个service执行参数 如某个bean,返回整个bean,并执行数组赋值,
        // 用于防止字符串传递引用
        for(int i = 0; i < submitBeans.length; i++){
            Object submitBean = submitBeans[i];
            submitBeans[i] = handleParam(submitBean);
        }

        // 执行被代理类方法
        Object object = point.proceed(point.getArgs());

        //获取返回的ResultVO 的 Field(即object) 的参数
        Field field = object.getClass().getDeclaredField(RESULT_OBJECT_NAME);
        field.setAccessible(true);
        Object param = field.get(object);

        // 处理返回的参数
        handleParam(param);

        log.info("Handle End");
        return object;
    }

    /**
     * 很多情况下是bean,少数情况下是某个RequestParam
     * @param param
     * @return
     */
    private Object handleParam(Object param) throws Exception{
        List<HandleBeanParentService> beanServiceList = HandleBeanAnnoProperty.beanServiceList;
        List<HandleParamParentService> paramServiceList = HandleBeanAnnoProperty.paramServiceList;
        Map<String, HandleService> handleBeanServiceMap = HandleBeanAnnoProperty.annoConnectMap;

        if(null != param){
            if(checkNotBean(param)){
                for(HandleParamParentService service : paramServiceList){
                    param = service.doParam(param);
                }
            }else{
                Field[] fields = param.getClass().getDeclaredFields();
                for(Field field : fields){
                    // 获取field上的多个注解
                    Annotation[] annotations = field.getDeclaredAnnotations();
                    // 判断是否注解,没有注解直接返回
                    if(null == annotations || annotations.length == 0){
                        continue;
                    }
                    field.setAccessible(true);

                    //注解排序
                    AnnoCommonUtil.sort(annotations);

                    for(Annotation annotation : annotations){
                        // bean 参数上的注解名称
                        String annotationName = annotation.annotationType().getName();

                        // 判断注解是否有实现类,存在去执行
                        HandleService handleService = handleBeanServiceMap.get(annotationName);
                        Object beExecuteObject = null;
                        try {
                            beExecuteObject = field.get(param);
                        } catch (IllegalAccessException e) {
                            log.error("不能获取改field的值, field{}",field.getName());
                            continue;
                        }
                        // 启动时定义完成的service bean 在初始化时就存入map中
                        if(null != handleService){
                            Object resultParam = handleService.doParam(beExecuteObject);
                            // 判断返回值是否相同,不相同再赋值,减少反射消耗
                            if(!beExecuteObject.equals(resultParam)) {
                                field.set(param, resultParam);
                            }
                        }else{
                            // 没有实现类 就去所有的实现类中试一次(防止通过代码手动生成bean的情况)
                            for(HandleBeanParentService service : beanServiceList){
                                AbstractHandleAnno abstractHandleAnno = (AbstractHandleAnno)service;
                                // 每个service的注解名称
                                String serviceAnnotationName = abstractHandleAnno.getAnnotation().getName();
                                if(annotationName.equals(serviceAnnotationName)){
                                    handleBeanServiceMap.put(annotationName, service);
                                    Object resultParam =  service.doParam(beExecuteObject);
                                    if(!beExecuteObject.equals(resultParam)) {
                                        field.set(param, resultParam);
                                    }
                                }

                            }
                        }
                    }


                }
            }
        }
        return param;
    }
}

结语

  项目目前还不完善,欢迎下方留言。目前只支持RequestBody的bean支持,而RequestParam尚未支持,这些问题应该会在之后的版本中支持。 完整代码在GitHub上

项目地址

  github.com/trotyzyq/an…