1.自定义注解
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Permission {
}
@Documented和@Deprecated注解长得有点像,@Deprecated是用来标注某个类或者方法不建议再继续使用,@Documented只能用在注解上,如果一个注解@B,被@Documented标注,那么被@B修饰的类,生成文档时,会显示@B。如果@B没有被@Documented标注,最终生成的文档中就不会显示@B。这里的生成文档指的JavaDoc文档!
@Deprecated注解基本上所有框架自定义的注解都会添加,所谓javadoc其实就是JDK给我们提供的一个生成文档的工具!
@Target注解自JDK1.5之后就有了,其作用是定义在注解的上方,表明其注解的作用范围。
ElementType枚举值 | 作用范围 |
---|---|
TYPE | 接口、类、枚举 |
FIELD | 字段、枚举的常量 |
METHOD | 方法 |
PARAMETER | 方法参数 |
CONSTRUCTOR | 构造函数 |
LOCAL_VARIABLE | 局部变量 |
ANNOTATION_TYPE | 注解 |
PACKAGE | 包 |
@Retention是用来修饰注解的,注解的注解,也称为元注解。@Retention修饰注解,用来表示注解的生命周期,生命周期的长短取决于@Retention的属性RetentionPolicy指定的值,例如@Retention(RetentionPolicy.RUNTIME)
取值 | 描述 | 作用范围 | 使用场景 |
---|---|---|---|
RetentionPolicy.SOURCE | 表示注解只保留在源文件,当java文件编译成class文件,就会消失 | 源文件 | 只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings |
RetentionPolicy.CLASS | 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期 | class文件(默认) | 要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife) |
RetentionPolicy.RUNTIME | 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在 | 运行时也存在 | 需要在运行时去动态获取注解信息 |
生命周期选择:
首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要 在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife) ,就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和@SuppressWarnings,则 可选用 SOURCE 注解。
2.实现拦截器
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PermissionInterceptor implements Interceptor {
private static final Log log = LogFactory.get(PermissionInterceptor.class);
/**
* 拦截sql
*
* @param invocation q
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性;:MetaObject是Mybatis提供的一个用于方便、
// 优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
// 先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// id为执行的mapper方法的全路径名,如com.cq.UserMapper.insertUser, 便于后续使用反射
String id = mappedStatement.getId();
Permission permission = null;
// 通过类全路径获取Class对象
Class<?> classType = Class.forName(id.substring(0, id.lastIndexOf(".")));
// 获取当前所拦截的方法名称
String mName = id.substring(id.lastIndexOf(".") + 1);
// 遍历类中所有方法名称,并if匹配上当前所拦截的方法
for (Method method : classType.getDeclaredMethods()) {
if (mName.equals(method.getName())) {
// 判断方法上是否带有自定义@InterceptAnnotation注解
permission = method.getAnnotation(Permission.class);
}
}
if (permission == null) {
return invocation.proceed();
}
BoundSql boundSql = statementHandler.getBoundSql();
// 获取到原始sql语句
String sql = boundSql.getSql().toLowerCase();
log.info("SQL:{}", sql);
// 增强sql
// 通过反射,拦截方法上带有自定义@InterceptAnnotation注解的方法,并增强sql
String mSql = sqlAnnotationEnhance(permission, sql);
//通过反射修改sql语句
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, mSql);
// 打印:增强后的SQL:select * from scenario_storage limit 2
log.info("增强后的SQL:{}", mSql);
return invocation.proceed();
}
/**
* 通过反射,拦截方法上带有自定义@InterceptAnnotation注解的方法,并增强sql
*
* @param permission 自定义注解
* @param sql 所执行的sql语句
*/
private String sqlAnnotationEnhance( Permission permission, String sql) {
//todo 增强sql
return sql;
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
}