引言
上一篇说了下我写的第一版拦截器,通篇写死,复用性极差,经过指导,删掉来过。
更新第三版,注解字段等都由使用者自己定义,而不是限制注解可选内容
需求
- 通过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