开篇 - 前奏
最近项目后台针对报表查询进行优化,为了避免打开页面慢的问题,新增首次加载不返回数据的需求。由于后台报表较多,如果每个方法添加判断修改量较大且代码重复,故采用自定义注解的形式处理。
后台前端使用Datatables
插件进行实现,每次表格渲染插件会自动传递参数draw
,初始draw=1
, 每次加载值递增。
Datatables是一款jquery表格插件。它是一个高度灵活的工具,可以将任何HTML表格添加高级的交互功能。
- 分页,即时搜索和排序
- 几乎支持任何数据源:DOM, javascript, Ajax 和 服务器处理
- 支持不同主题 DataTables, jQuery UI, Bootstrap, Foundation
- 各式各样的扩展: Editor, TableTools, FixedColumns ……
- 丰富多样的option和强大的API
- 支持国际化
- 超过2900+个单元测试
- 免费开源 ( MIT license )! 商业支持
- 更多特性请到官网查看
正文 - 间奏
配置
请求拦截需要在控制层Controller进行处理,所以需要开启cglib实现动态代理
<!-- 报表首次渲染方法拦截 -->
<aop:config proxy-target-class="true">
<!--切入点-->
<aop:pointcut id="methodPoint" expression="execution(* com.test.demo.web.controller..*.*(..)) and @annotation(org.springframework.web.bind.annotation.RequestMapping)"/>
<!--通知-->
<aop:advisor pointcut-ref="methodPoint" advice-ref="drawInterceptor"/>
</aop:config>
注意事项
因为Spring的Bean扫描和
Spring-MVC
的Bean
扫描是分开的, 两者的Bean
位于两个不同的Application,即父子容器, 而且Spring-MVC
的Bean
扫描要早于Spring的Bean扫描, 所以当Controller Bean
生成完成后, 再执行Spring的Bean扫描,Spring会发现要被AOP代理的Controller Bean已经在容器中存在, 配置AOP就无效了。同样这样的情况也存在于数据库事务中, 如果
Service
的Bean
扫描配置在spring-mvc.xml
中, 而数据库事务管理器配置在application.xml
中, 会导致数据库事务失效, 原理一样。所以这里 ,我们需要把
AOP
放置在Controller
扫描配置的文件中。
示例
Spring
的配置文件application.xml
配置如下:
<context:component-scan base-package="cn.test.demo.dao" />
<context:component-scan base-package="cn.test.demo.service" />
Spring MVC
的配置文件spring-mvc.xml
<!-- 扫描类包,将spring注解的类自动扫描为Bean,同时完成Bean的注入. -->
<context:component-scan base-package="cn.test.demo.controller" />
<!-- aop配置 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="methodPoint" expression="..."></aop:pointcut>
<aop:advisor pointcut-ref="methodPoint" advice-ref="drawInterceptor"/>
</aop:config>
aop:config
中proxy-target-class="true"
开启Cglib动态代理aop:pointcut
切入点配置aop:advisor
通知接口配置
处理
自定义注解
@Target({ METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Draw {
String message() default "{com.test.demo.web.annotation.Draw.message}";
}
java中元注解解释
-
@Target 描述注解的使用范围
public enum ElementType { TYPE, // 类、接口、枚举类 FIELD, // 成员变量(包括:枚举常量) METHOD, // 成员方法 PARAMETER, // 方法参数 CONSTRUCTOR, // 构造方法 LOCAL_VARIABLE, // 局部变量 ANNOTATION_TYPE, // 注解类 PACKAGE, // 可用于修饰:包 TYPE_PARAMETER, // 类型参数,JDK 1.8 新增 TYPE_USE // 使用类型的任何地方,JDK 1.8 新增 }
-
@Retention 描述注解保留的时间范围
public enum RetentionPolicy {
SOURCE, // 源文件保留
CLASS, // 编译期保留,默认值
RUNTIME // 运行期保留,可通过反射去获取注解信息
}
-
@Documented 描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
-
@Inherited 使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)
方法拦截器实现类
@Configuration
public class DrawInterceptor implements MethodInterceptor {
private static final Logger logger = LoggerFactory.getLogger(DrawInterceptor.class);
@Autowired
private HttpServletRequest request;
/**
* 首次渲染
*/
private static final String FIRST_DRAW = "1";
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 判断方法是否加了@Draw 注解
if (invocation.getMethod().isAnnotationPresent(Draw.class)) {
String drawValue = request.getParameter("draw");
if (FIRST_DRAW.equals(drawValue)) {
logger.info("请求{}首次渲染,返回空数据", request.getRequestURI());
// 空结果
return AjaxResultUtil.assembleDataGridResult(new PaginationResult(new Pagination(0, 0), 0, Collections.EMPTY_LIST));
}
}
//执行被拦截的方法
return invocation.proceed();
}
}
使用
在对应请求的处理方法上使用 @Draw,Spring
会自动扫描进行拦截处理。
注:被注解的类需要放到前面我们指定的
cn.test.demo.controlle
下,否则不生效。
示例
@Draw
@RequestMapping("/page")
public VO getPage(){
...
return vo;
}
结尾 - 尾声
至此,我们已完成如下事情
AOP
支持并配置- 自定义注解
- 注解扫描并处理
- 应用
感悟 - 高潮
自定义注解是本小笨蛋儿第一次在企业实际场景中应用,也非常深刻体验到抽取、封装思想带来的便利,也算攻破了自定义注解
这一技术,可很方便控制和修改,将通用部分抽取并封装可极大提升开发效率。
其实受益颇多的还是这种抽取的思想,技术很容易从各大网站博客学习,随学随用,但是思想是一步步从个人经验和前人经验借鉴提升的,一旦思想境界达到一定程度,对于任何新旧技术都不会成为瓶颈。
知行合一,举一反三,方成正果~~
最后引用名人劝解后人的名言:学吧,学无止境,太深了。 ——乾隆赠予兴跃神话
😄😄 加油~
更多信息可关注我的公众号 Java知识星球
,不定时推送更多原创技术知识文章。