使用mybatis拦截器实现公共字段自动注入——第三版

前段时间写了两版mybatis拦截器实现公共字段自动注入,感觉第二版还是限制的过多了。用户基本无法扩展字段,也无法自定义方法,作为一个公共的基础框架工具类是不够的,所以又根据以上做了一些修改。

文章目录

  • 思路
  • 注解定义
  • 接口定义
  • 配置开关
  • SpringBean工具类
  • 反射工具类
  • 拦截器设计
  • 最终配置类
  • 使用示例

思路

  • 去除接口中定义的方法和默认实现,只保留一个空接口供用户实现
  • 用户在注解参数中决定该字段是否需要注入和注入方法名
  • 用户实现接口,按需写提供数据方法
  • 拦截器通过spring容器获取接口的实现类,并根据注解参数的方法名通过反射获取方法并调用获取返回值以注入

注解定义

@Target({
     ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AutoStuff {
     

    /**
     * 新增时是否需要自动注入
     * @return
     */
    boolean addFlag() default false;

    /**
     * 新增时的注入方法
     * @return
     */
    String addMethod() default "";

    /**
     * 修改时是否需要自动注入
     * @return
     */
    boolean updateFlag() default false;

    /**
     * 修改时的注入方法
     * @return
     */
    String updateMethod() default "";

}

说明:
addFlag:如果该字段在新增时需要自动注入,则给true
addMethod:当addFlag为true时,会从用户对IOperInfo接口的实现类中,以该值为方法名查找方法,获取数据注入
updateFlag:如果该字段在修改时需要自动注入,则给true
updateMethod:当updateFlag为true时,会从用户对IOperInfo接口的实现类中,以该值为方法名查找方法,获取数据注入

接口定义

public interface IOperInfoService {
     

}

作用:@AutoStuff自动注入的内容都是根据接口获取的,用户必须实现该接口,并在自己的实现类中结合@AutoStuff注解中的参数完善方法

配置开关

该拦截器引入后默认启用,提供开关也使用户在不需要使用注入时,不需要更改代码,只需要修改配置文件即可。

  • 配置模块
/**
* 通过@ConfigurationProperties根据prefix从配置文件中读取配置块
* ignoreInvalidFields: 为了避免消费者填的字段不符合我们规定的字段,无法解析而导致启动报错
* 	比如enabled消费者填的是abc,不是boolean类型。
* 	设置了ignoreInvalidFields,就会忽略配置文件中填的错误内容,而使用我们规定的默认值,如果没有默认值则为null
*/
@ConfigurationProperties(
        prefix = "my.interceptor", ignoreInvalidFields = true
)
public class MybatisInterceptorProperties {
     
	// 默认为true
    private Boolean enabled = Boolean.TRUE;

    public Boolean getEnabled() {
     
        return enabled;
    }

    public void setEnabled(Boolean enabled) {
     
        this.enabled = enabled;
    }
}
  • 配置类
@Configuration
@EnableConfigurationProperties({
     MybatisInterceptorProperties.class})// 激活配置模块
/**
* 控制Configuration是否生效
* name: 数组,property完整名称或部分名称
* matchIfMissing:缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错
* havingValue: 比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置
*/
@ConditionalOnProperty(
        name = {
     "my.interceptor.enabled"},
        matchIfMissing = true,
        havingValue = "true"
)
public class InterceptorConfig {
     

  


}

当不需要使用拦截器时,只需在配置文件中设为false即可关闭

my:
  interceptor:
  	enabled: false

SpringBean工具类

public class SpringBeanUtil implements ApplicationContextAware {
     

    @Autowired
    private ApplicationContext applicationContext;


    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
     
        this.applicationContext = ctx;
    }

    public ApplicationContext getApplicationContext() {
     
        return this.applicationContext;
    }
    
}

反射工具类

public class ReflectUtil {
     

    private static Logger logger = LoggerFactory.getLogger(ReflectUtil.class);


    private ReflectUtil(){
     

    }

    /**
     * 获取该类的所有字段
     * @param object
     * @return
     */
    public static Field[] getAllFields(Object object){
     
        Class clazz = object.getClass();
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null){
     
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }

    /**
     * 根据类名,方法名,反射调用类的方法
     * @param className
     * @param methodName
     * @return
     */
    public static Object getResult(String className,String methodName){
     
        try{
     
            Object obj = Class.forName(className).newInstance();
            Class clazz = obj.getClass();
            Method mothod = clazz.getDeclaredMethod(methodName, null);
            return mothod.invoke(obj, null);
        } catch (NoSuchMethodException e){
     
            logger.error("errCode: {},errMsg: {}", ErrCons.REFLECT_METHOD_NOT_FOUND, "【"+className+ ErrCons.REFLECT_METHOD_NOT_FOUND_MSG+methodName);
            throw new BusinessException(ErrCons.REFLECT_METHOD_NOT_FOUND, "【"+className+ ErrCons.REFLECT_METHOD_NOT_FOUND_MSG+methodName);
        } catch (Exception e) {
     
            logger.error("errCode: {},errMsg: {}", ErrCons.REFLECT_INVOKE_METHOD_ERR, "调用"+methodName+"异常");
            throw new BusinessException(ErrCons.REFLECT_INVOKE_METHOD_ERR, "调用"+methodName+"异常");
        }
    }

}

拦截器设计

@Intercepts({
     @Signature(type = Executor.class, method = "update"
        , args = {
     MappedStatement.class, Object.class})})
public class StuffInterceptor implements Interceptor {
     

    private static Logger logger = LoggerFactory.getLogger(StuffInterceptor.class);

    @Autowired
    private SpringBeanUtil springBeanUtil;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
     
        if (invocation.getTarget() instanceof Executor && invocation.getArgs().length == 2) {
     

            final Executor executor = (Executor) invocation.getTarget();
            // 获取第一个参数
            final MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
            final Object paramObj = invocation.getArgs()[1];
            if (ms.getSqlCommandType() == SqlCommandType.INSERT) {
     
                return this.executeInsert(executor, ms, paramObj);
            } else if (ms.getSqlCommandType() == SqlCommandType.UPDATE) {
     
                return this.executeUpdate(executor, ms, paramObj);
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(final Object target) {
     
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
     
        //
    }

    /**
     * 新增操作
     *
     * @param executor executor
     * @param ms       ms
     * @param paramObj 参数
     * @return 返回执行结果
     */
    private Object executeInsert(final Executor executor, final MappedStatement ms, final Object paramObj) throws Exception {
     
        // 获取IOperInfoService接口的实现类名
        String implClassName = buildImplClassName();
        // 获取实体类的所有字段
        final Field[] fields = ReflectUtil.getAllFields(paramObj);
        for(final Field field : fields){
     
            field.setAccessible(true);
            // 判断是否有@AutoStuff注解
            if(field.isAnnotationPresent(AutoStuff.class)){
     
                AutoStuff autoStuff = field.getAnnotation(AutoStuff.class);
                // addFlag是否为true
                if(autoStuff != null && autoStuff.addFlag()){
     
                    // addMethod为空则抛出异常
                    if(StringUtils.isEmpty(autoStuff.addMethod())){
     
                        logger.error(ErrCons.ERR_MSG , ErrCons.ADDMETHOD_IS_EMPTY, ErrCons.ADDMETHOD_IS_EMPTY_MSG);
                        throw new BusinessException(ErrCons.ADDMETHOD_IS_EMPTY, ErrCons.ADDMETHOD_IS_EMPTY_MSG);
                    }
                    // 根据addMethod构建方法名
                    String methodName = buildMethodName(autoStuff.addMethod());
                    // 设置值
                    field.set(paramObj, ReflectUtil.getResult(implClassName, methodName));
                }
            }
        }
        return executor.update(ms, paramObj);
    }

    /**
     * 修改操作
     *
     * @param executor executor
     * @param ms       ms
     * @param paramObj 参数
     * @return 返回执行结果
     */
    private Object executeUpdate(final Executor executor, final MappedStatement ms, final Object paramObj) throws Exception {
     
        // 获取IOperInfoService接口的实现类名
        String implClassName = buildImplClassName();
        // 获取实体类的所有字段
        final Field[] fields = ReflectUtil.getAllFields(paramObj);
        for(final Field field : fields){
     
            field.setAccessible(true);
            // 判断是否有@AutoStuff注解
            AutoStuff autoStuff = field.getAnnotation(AutoStuff.class);
            // updateFlag是否为true
            if(autoStuff != null && autoStuff.updateFlag()){
     
                // updateMethod为空则抛出异常
                if(StringUtils.isEmpty(autoStuff.updateMethod())){
     
                    logger.error(ErrCons.ERR_MSG , ErrCons.UPDATE_METHOD_IS_EMPTY, ErrCons.UPDATE_METHOD_IS_EMPTY_MSG);
                    throw new BusinessException(ErrCons.UPDATE_METHOD_IS_EMPTY, ErrCons.UPDATE_METHOD_IS_EMPTY_MSG);
                }
                // 根据updateMethod构建方法名
                String methodName = buildMethodName(autoStuff.updateMethod());
                // 设置值
                field.set(paramObj, ReflectUtil.getResult(implClassName, methodName));
            }
        }
        return executor.update(ms, paramObj);
    }

    /**
     * 获取接口IOperInfoService的实现类
     * @return
     */
    private String buildImplClassName(){
     
        String implClassName = "";
        // 通过Spring容器获取
        Map<String, IOperInfoService> result = springBeanUtil.getApplicationContext().getBeansOfType(IOperInfoService.class);
        // 未找到实现类,抛出异常
        if(result.isEmpty()){
     
            logger.error(ErrCons.ERR_MSG , ErrCons.IMPL_CLASS_NOT_FOUND, ErrCons.IMPL_CLASS_NOT_FOUND_MSG);
            throw new BusinessException(ErrCons.IMPL_CLASS_NOT_FOUND, ErrCons.IMPL_CLASS_NOT_FOUND_MSG);
        }
        // 实现类大于1,抛出异常
        if(result.size() > 1){
     
            logger.error(ErrCons.ERR_MSG , ErrCons.IMPL_CLASS_TO_MUCH, ErrCons.IMPL_CLASS_TO_MUCH_MSG);
            throw new BusinessException(ErrCons.IMPL_CLASS_TO_MUCH, ErrCons.IMPL_CLASS_TO_MUCH_MSG);
        }
        // 返回第一个实现类的类名
        for (Map.Entry<String, IOperInfoService> entry : result.entrySet()) {
     
            String name = entry.getValue().getClass().getName();
            if(StringUtils.isNotEmpty(name)){
     
                implClassName = name;
                break;
            }
        }
        return implClassName;
    }

    /**
     * 构建方法名
     * @param methodName 注解的addMethod或updateMethod
     * @return
     */
    private String buildMethodName(String methodName){
     
        return "get"+upperFirstLatter(methodName);
    }

    /**
     * 字符串首字母大写
     * @param letter
     * @return
     */
    private String upperFirstLatter(String letter){
     
        char[] chars = letter.toCharArray();
        if(chars[0]>='a' && chars[0]<='z'){
     
            chars[0] = (char) (chars[0]-32);
            return new String(chars);
        }
        return letter.substring(0, 1).toUpperCase()+letter.substring(1);
    }

}

方法名设计与构建根据各自需求自行修改

最终配置类

@Configuration
@EnableConfigurationProperties({
     MybatisInterceptorProperties.class})
@ConditionalOnProperty(
        name = {
     "microservices.framework.mybaits.autostuff.interceptor.enabled"},
        matchIfMissing = true,
        havingValue = "true"
)
public class InterceptorConfig {
     

    @Bean
    public SpringBeanUtil springBeanUtil() {
     
        return new SpringBeanUtil();
    }

    @Bean
    @DependsOn("springBeanUtil")
    public Interceptor interceptor() {
     
        return new StuffInterceptor();
    }


}

使用示例

  • 实体类
@Data
public class AHello implements Serializable {
     

    private String id;
    private String name;
    @AutoStuff(addFlag = true, addMethod = "aUser")
    private String createUser;
    @AutoStuff(addFlag = true, addMethod = "nowDate")
    private Date createTime;
    @AutoStuff(addFlag = true, addMethod = "operUser", updateFlag = true, updateMethod = "newUser")
    private String lastModifyUser;
    @AutoStuff(addFlag = true, addMethod = "nowTime", updateFlag = true, updateMethod = "nowTime")
    private Date lastModifyTime;
}
  • 实现类
@Service
public class OperInfoImpl implements IOperInfoService {
     

    public String getAUser(){
     
        return "aUser";
    }

    public String getOperUser() {
     
        return "operUser";
    }

    public String getNewUser(){
     
        return "newUser";
    }


    public Date getNowDate() {
     
        Date date = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, +1);//+1今天的时间加一天
        date = calendar.getTime();
        return date;
    }

    public Timestamp getNowTime() {
     
        Date date = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, +1);//+1今天的时间加一天
        date = calendar.getTime();
        return new Timestamp(date.getTime());
    }
}
  • 方法定义规则解释
    例:
@AutoStuff(addFlag = true, addMethod = "nowDate")
private Date createTime;

已知注解中addFlag为true,addMethod为nowdate
将addMethod首字母大写并在开头加上get组成方法名:getNowDate
拦截器就会从实现类OperInfoImpl中调用getNowDate()方法,将该方法的返回值注入到该字段中。
若方法不存在则会抛出异常提示方法不存在。

注意,必须实现且只能实现一次IOperInfoService接口,否则将抛出异常。
addMethod与updateMethod只能由字母、数字、下划线组成

你可能感兴趣的:(Mybatis)