Bootstrap

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

引言

上一篇说了下我写的第一版拦截器,通篇写死,复用性极差,经过指导,删掉来过。

更新第三版,注解字段等都由使用者自己定义,而不是限制注解可选内容

需求

  • 通过mybatis拦截器注入公共字段
  • 需要注入的公共字段:
字段备注
created_by创建人
created_time创建时间
updated_by修改人
updated_time修改时间
  • 注解实现
  • 有些时候,项目不需要使用拦截器,还需要去修改pom,比较麻烦。要支持通过配置文件来控制。

思路

  • 写一个自定义注解,将注解打在字段上,表示需要自动注入。注解设置一个annoType表示字段归属(创建时间、更新时间等等),类型为自定义的枚举类以约束消费者填写的范围。
  • 写一个获取信息的接口提供消费者实现,需要注入的值都从该接口的方法获取。为了避免消费者没实现导致报错,需要写一个默认实现。
  • 通过SpringBoot注解实现配置模块和控制Configuration是否生效

实现

  • 工具类
    1.反射工具类
/**
 * 根据对象获取该类及其基类所有字段
 */
public class ReflectUtil {

    private ReflectUtil(){

    }

    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;
    }

    public static Field[] getAllFields(Class clazz){
        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;
    }
}

2.字段类型枚举类

/**
 * 限制注入的类型
 */
public enum FieldEnum {

    STRING(String.class),
    LONG(Long.class),
    DATE(Date.class),
    TIMESTAMP(Timestamp.class);

    FieldEnum(Class fieldType) {
        this.fieldType = fieldType;
    }

    private Class fieldType;

    public Class getFieldType() {
        return fieldType;
    }
}

3.注解归属枚举类

/**
* 可供选择的归属类型
*/
public enum AnnoEnum {

    CREATE_BY("createBy"),
    CREATE_TIME("createTime"),
    UPDATE_BY("updateBy"),
    UPDATE_TIME("updateTime");

    AnnoEnum(String annoType) {
        this.annoType = annoType;
    }

    private String annoType;

    public String getAnnoType() {
        return annoType;
    }

}
  • 自定义注入注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface AutoStuff {

    AnnoEnum annoType();
}
  • 获取注入信息接口
public interface IOperInfo {
	// 因为注入的用户可能是ID也可能是姓名或者别的,就写了两种
    Long getOperUserLong();

    String getOperUserString();

    String getString();

    Long getLong();

    Date getDate();

    Timestamp getTimestamp();

}
  • 默认实现类
public class DefaultOperInfoImpl implements IOperInfo {


    @Override
    public Long getOperUserLong() {
        return 0L;
    }

    @Override
    public String getOperUserString() {
        return "";
    }

    @Override
    public String getString() {
        return "";
    }

    @Override
    public Long getLong() {
        return 0L;
    }

    @Override
    public Date getDate() {
        return new Date();
    }

    @Override
    public Timestamp getTimestamp() {
        return new Timestamp(new Date().getTime());
    }
}
  • 拦截器实现
@Intercepts({@Signature(type = Executor.class, method = "update"
        , args = {MappedStatement.class, Object.class})})
public class StuffInterceptor implements Interceptor {

    @Autowired
    private IOperInfo operInfo;

    @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 {
    	// 获取所有字段
        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);
                // 获取字段类型(String.class、Long.class等等)
                Type fieldType = field.getAnnotatedType().getType();
                // 获取注解里写的annoType
                String annoType = autoStuff.annoType().getAnnoType();
				// 先进行字段类型判断,再判断归属,所有信息都是通过operInfo接口获取的
                if(fieldType.equals(FieldEnum.STRING.getFieldType())){
                    if(annoType.equals(AnnoEnum.CREATE_USER.getAnnoType()) || annoType.equals(AnnoEnum.UPDATE_USER.getAnnoType())){
                        field.set(paramObj, operInfo.getOperUserString());
                    }else{
                        field.set(paramObj, operInfo.getString());
                    }
                }else if(fieldType.equals(FieldEnum.LONG.getFieldType())){
                    if(annoType.equals(AnnoEnum.CREATE_USER.getAnnoType()) || annoType.equals(AnnoEnum.UPDATE_USER.getAnnoType())){
                        field.set(paramObj, operInfo.getOperUserLong());
                    }else{
                        field.set(paramObj, operInfo.getLong());
                    }
                }else if(fieldType.equals(FieldEnum.DATE.getFieldType())){
                    field.set(paramObj, operInfo.getDate());
                }else if(fieldType.equals(FieldEnum.TIMESTAMP.getFieldType())){
                    field.set(paramObj, operInfo.getTimestamp());
                }
            }
        }
        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 {
        final Field[] fields = ReflectUtil.getAllFields(paramObj);
        for(final Field field : fields){
            field.setAccessible(true);
            AutoStuff autoStuff = field.getAnnotation(AutoStuff.class);
            Type fieldType = field.getAnnotatedType().getType();
            String annoType = autoStuff.annoType().getAnnoType();

            if(fieldType.equals(FieldEnum.STRING.getFieldType())){
                if(annoType.equals(AnnoEnum.UPDATE_USER.getAnnoType())){
                    field.set(paramObj, operInfo.getOperUserString());
                }else{
                    field.set(paramObj, operInfo.getString());
                }
            }else if(fieldType.equals(FieldEnum.LONG.getFieldType())){
                if(annoType.equals(AnnoEnum.UPDATE_USER.getAnnoType())){
                    field.set(paramObj, operInfo.getOperUserLong());
                }else{
                    field.set(paramObj, operInfo.getLong());
                }
            }else if(fieldType.equals(FieldEnum.DATE.getFieldType()) && annoType.equals(AnnoEnum.UPDATE_TIME.getAnnoType())){
                field.set(paramObj, operInfo.getDate());
            }else if(fieldType.equals(FieldEnum.TIMESTAMP.getFieldType()) && annoType.equals(AnnoEnum.UPDATE_TIME.getAnnoType())){
                field.set(paramObj, operInfo.getTimestamp());
            }
        }
        return executor.update(ms, paramObj);
    }
}
  • 配置
    1.配置模块
/**
* 通过@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;
    }
}

2.配置类

@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 {

    @Bean
    @ConditionalOnMissingBean// 当没有实现类时使用默认实现
    public IOperInfo iOperInfo(){
        return new DefaultOperInfoImpl();
    }

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


}

使用

  • Entity
@Data
public class BaseEntity{

    /**
     * 创建人
     */
    @AutoStuff(annoType = AnnoEnum.CREATE_USER)
    @Column(name = "`created_by`")
    private Long createdBy;

    /**
     * 创建时间
     */
    @AutoStuff(annoType = AnnoEnum.CREATE_TIME)
    @Column(name = "`created_time`")
    private Date createdTime;

    /**
     * 修改人
     */
    @AutoStuff(annoType = AnnoEnum.UPDATE_USER)
    @Column(name = "`updated_ by`")
    private Long updatedBy;

    /**
     * 修改时间
     */
    @AutoStuff(annoType = AnnoEnum.UPDATE_TIME)
    @Column(name = "`updated_time`")
    private Date updatedTime;
}
  • 配置文件
    改成false则拦截器就不生效了,不写则默认生效
my:
  interceptors:
    enabled: true

测试

在这里插入图片描述

;