关于页面查询条件保持的思考(一)
2017-12-29 10:15
288 查看
在项目中,查询条件保持是经常使用到的,特别是管理后台。对于前台页面来说,通常为了访问的方便会使用get的方式进行表单提交,这样进行页面分享或者发送给好友时可以直接打开对于的页面。但是对于管理后台来说,地址栏上的一大串url参数是不允许的,不美观也不安全。
比如在用户查询页面,可以根据用户的年龄,姓名,昵称,等等参数进行查询,而且可能客户已经翻到了第n页上,此时点击某个用户详细,页面跳转到用户详细页面对用户信息进行编辑,编辑完成后点击保存,这时候需要返回到用户查询页面上,并且还得回到用户原来页面。那么可以使用如下的方式:
弹出用户信息页面
这样的好处就是直接可以在页面上编辑,作为弹出层不影响之前查询的条件保持
新开一个页面
这种方式通常不推荐使用,这样容易导致页面打开非常多,具体可以看客户需求
在当前页面跳转
这种是使用最多的一种方式,因为在管理页面上通常来说是只在一个页面上操作的。但是多级页面跳转后查询条件的保持就是问题了。
对于前两种方式都比较简单,这里不多赘述了。本文将重点分析第三种需求的实现方式。
将查询页面中的所有参数带到后续所有页面中
这是最简单的方法,也是最累的方法,如果条件少跳转层级少这种方式是可以使用的,但是如果查询条件一多(通常管理页面查询条件是不少的)或者页面跳转多了,这种方式就呵呵了。
保存到cookie中
作为参数少,且安全性不高的数据可以保存到cookie中,而且还必须管理好cookie的生命周期,其他用户登录时不能获取到之前用户的cookie信息。而且保存的信息不宜多。
将参数缓到后端,等到返回查询页面时再从缓存中获取
比较推荐这种方式,将信息保存到后端后,生成一个缓存key,后面的页面只要传递一个key值即可。下面详述下这种方式的实现。
首先介绍下使用Aspectj的方式来实现。
自定义缓存注解@SearchCache,通过此注解的标注的参数都表示需要缓存起来
拦截所有Controller中带有SearchCache注解的方法,在方法执行前将信息缓存起来
在Controller执行前,判断是否有cacheToken参数,如果有的话表示从缓存中读取,将缓存中读取的值作为参数传递到Controller中。
当用户退出登录时,将缓存信息清理掉
以上步骤简要的说明了整个实现思路,下面来一步步具体实现:
首先添加SearchCache,此注解是作用在方法参数中
cacheImpl:指定缓存的实现类,默认使用的是SessionSearchCache作为缓存,可以在使用注解的时候自定义设定缓存实现类,自定义缓存实现类需要实现ISearchCache接口。
keyGenerator:缓存key生成策略,默认使用的是UUIDKeyGenerator即使用UUID的方式生成缓存key,开发者可以自定义key生成的方式。
value:缓存的key,指定请求参数中哪个字段作为缓存key,并且生成的key将保存到model中对应的key。
其次,拦截所有的Controller方法,本文中将拦截所有参数中添加了注解@SearchCache的方法。并根据参数中是否有cacheToken参数来判断是否需要从缓存中获取查询数据,并且会将缓存key放入model中,方便后续逻辑处理。
最后添加自动配置功能,添加EnableSearchCacheAutoConfiguration,此类会自动配置缓存功能的共通信息,比如缓存key的生成,切面功能的添加,以及Session的监听。
由于此功能是基于Aspectj实现的,所以在使用时候需要添加Aspectj功能,并且将自动配置类进行导入:
@Import(EnableSearchCacheAutoConfiguration.class)
@EnableAspectJAutoProxy
在Controller方法中添加@SearchCache()即可
启动项目,访问地址
![](http://img.blog.csdn.net/20171229095228699?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY21sX2Jsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
访问此页面后会将缓存的cacheToken返回,后续页面跳转中只需要带上cacheToken,再次返回查询页面的时候,系统会自动将cacheToken对应的数据获取出来。就上面的例子而言,在访问的时候即将cacheToken传入,age=1024就会自动从缓存中查询出来。
![](http://img.blog.csdn.net/20171229095246755?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY21sX2Jsb2c=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
本文例子是通过SessionId,将用户的所有查询信息进行缓存,当session销毁时会自动将缓存中的信息清理。这样缓存请求参数的功能就轻地实现了。
文中的代码已上传至:https://github.com/cmlbeliever/cacheable-search 与
https://github.com/cmlbeliever/cacheable-search-aop
其中CacheableSearch工程为具体缓存实现功能lib,CacheableSearchProject为此项目的demo工程。至于使用方式,聪明的你应该知道。
虽然AOP的方式很使用,但是作为Spring的开发者来说,是不是有更好的方式来实现同样的功能?而且如果想要全局替换key的生成和缓存类,除了修改源码,还有什么更好的方式来实现?
详见文章:http://blog.csdn.net/cml_blog/article/details/79308573
使用了这么久的SpringBoot,但是还没有深入了解过注解的实现原理,想知道常用的注解实现原理么?想掌握各种Starter的实现原理么?可以看看我的课程:
http://gitbook.cn/gitchat/column/5a2fbea7626a7a2421b9a18c
比如在用户查询页面,可以根据用户的年龄,姓名,昵称,等等参数进行查询,而且可能客户已经翻到了第n页上,此时点击某个用户详细,页面跳转到用户详细页面对用户信息进行编辑,编辑完成后点击保存,这时候需要返回到用户查询页面上,并且还得回到用户原来页面。那么可以使用如下的方式:
弹出用户信息页面
这样的好处就是直接可以在页面上编辑,作为弹出层不影响之前查询的条件保持
新开一个页面
这种方式通常不推荐使用,这样容易导致页面打开非常多,具体可以看客户需求
在当前页面跳转
这种是使用最多的一种方式,因为在管理页面上通常来说是只在一个页面上操作的。但是多级页面跳转后查询条件的保持就是问题了。
对于前两种方式都比较简单,这里不多赘述了。本文将重点分析第三种需求的实现方式。
保持条件的方法
这里说说可行的几种方法:将查询页面中的所有参数带到后续所有页面中
这是最简单的方法,也是最累的方法,如果条件少跳转层级少这种方式是可以使用的,但是如果查询条件一多(通常管理页面查询条件是不少的)或者页面跳转多了,这种方式就呵呵了。
保存到cookie中
作为参数少,且安全性不高的数据可以保存到cookie中,而且还必须管理好cookie的生命周期,其他用户登录时不能获取到之前用户的cookie信息。而且保存的信息不宜多。
将参数缓到后端,等到返回查询页面时再从缓存中获取
比较推荐这种方式,将信息保存到后端后,生成一个缓存key,后面的页面只要传递一个key值即可。下面详述下这种方式的实现。
参数缓到后端
Spring项目中就可以直接使用切面编程和自定义参数注解的方式来实现,在一览页面查询时,将查询出的信息根据SessionId进行缓存即可。首先介绍下使用Aspectj的方式来实现。
自定义缓存注解@SearchCache,通过此注解的标注的参数都表示需要缓存起来
拦截所有Controller中带有SearchCache注解的方法,在方法执行前将信息缓存起来
在Controller执行前,判断是否有cacheToken参数,如果有的话表示从缓存中读取,将缓存中读取的值作为参数传递到Controller中。
当用户退出登录时,将缓存信息清理掉
以上步骤简要的说明了整个实现思路,下面来一步步具体实现:
首先添加SearchCache,此注解是作用在方法参数中
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @Documented public @interface SearchCache { Class<? extends ISearchCache> cacheImpl() default SessionSearchCache.class; Class<? extends KeyGenerator> keyGenerator() default UUIDKeyGenerator.class; /** * 请求key * * @return */ String value() default "cacheToken"; }
cacheImpl:指定缓存的实现类,默认使用的是SessionSearchCache作为缓存,可以在使用注解的时候自定义设定缓存实现类,自定义缓存实现类需要实现ISearchCache接口。
keyGenerator:缓存key生成策略,默认使用的是UUIDKeyGenerator即使用UUID的方式生成缓存key,开发者可以自定义key生成的方式。
value:缓存的key,指定请求参数中哪个字段作为缓存key,并且生成的key将保存到model中对应的key。
其次,拦截所有的Controller方法,本文中将拦截所有参数中添加了注解@SearchCache的方法。并根据参数中是否有cacheToken参数来判断是否需要从缓存中获取查询数据,并且会将缓存key放入model中,方便后续逻辑处理。
@Aspect() @Order(Ordered.HIGHEST_PRECEDENCE) public class SearchCacheAspect implements BeanFactoryAware { private static Logger log = LoggerFactory.getLogger(SearchCacheAspect.class); private BeanFactory beanFactory; @Pointcut("execution(* *..*.*(.. , @com.cml.learn.cacheablesearch.annotation.SearchCache (*), ..))") public void cacheAspect() { } @Around("cacheAspect()") public Object cacheAdvice(ProceedingJoinPoint point) throws Throwable { // HttpServletRequest request = retrieveParam(point, // HttpServletRequest.class); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // do nothing if (null == request) { log.warn("not found HttpServletRequest param!!!"); return point.proceed(); } Object[] args = point.getArgs(); // 获取添加了注解的参数对象 ParamHolder<SearchCache> searchCachePutHolder = retrieveParamConfig(point, SearchCache.class); if (null != searchCachePutHolder) { String cacheTokenKey = searchCachePutHolder.anonTarget.value(); // 修改args参数值 ISearchCache searchCache = beanFactory.getBean(searchCachePutHolder.anonTarget.cacheImpl()); Assert.notNull(searchCache, "cannot found impl class !!!!"); String cacheKey = request.getParameter(cacheTokenKey); // 获取缓存 if (null != cacheKey) { args[searchCachePutHolder.paramIndex] = searchCache.get(cacheKey); } else { cacheKey = generateKey(searchCachePutHolder); // 生成缓存数据 Object value = args[searchCachePutHolder.paramIndex]; searchCache.put(cacheKey, value); } Model model = retrieveParam(point, Model.class); if (null != model) { model.addAttribute(cacheTokenKey, cacheKey); } } Object value = point.proceed(args); return value; } private String generateKey(ParamHolder<SearchCache> searchCachePutHolder) { KeyGenerator keyGenerator = beanFactory.getBean(searchCachePutHolder.anonTarget.keyGenerator()); return keyGenerator.generateKey(); } private <T> T retrieveParam(ProceedingJoinPoint point, Class target) { Object[] args = point.getArgs(); MethodSignature signature = (MethodSignature) point.getSignature(); Method objMethod = signature.getMethod(); Annotation[][] anon = objMethod.getParameterAnnotations(); Class[] paramTypes = objMethod.getParameterTypes(); for (int i = 0; i < paramTypes.length; i++) { if (paramTypes[i].equals(target)) { return (T) args[i]; } } return null; } @SuppressWarnings("unchecked") private <T> ParamHolder<T> retrieveParamConfig(ProceedingJoinPoint point, Class<? extends Annotation> anonTarget) { Object[] args = point.getArgs(); MethodSignature signature = (MethodSignature) point.getSignature(); Method objMethod = signature.getMethod(); Annotation[][] anon = objMethod.getParameterAnnotations(); for (int i = 0; i < anon.length; i++) { Annotation[] an = anon[i]; for (Annotation ann : an) { if (ann.annotationType() == anonTarget) { ParamHolder<T> holder = new ParamHolder<>(); holder.anonTarget = (T) ann; holder.paramIndex = i; holder.argValue = args[i]; holder.paramType = objMethod.getParameterTypes()[i]; return holder; } } } return null; } static class ParamHolder<T> { T anonTarget; Object argValue; int paramIndex; Class paramType; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } }
最后添加自动配置功能,添加EnableSearchCacheAutoConfiguration,此类会自动配置缓存功能的共通信息,比如缓存key的生成,切面功能的添加,以及Session的监听。
@Configuration public class EnableSearchCacheAutoConfiguration { @Bean public UUIDKeyGenerator uuidKeyGenerate() { return new UUIDKeyGenerator(); } /** * 注册session开关监听 * * @param cache * @return */ @Bean public ServletListenerRegistrationBean<SessionCacheListener> sessionListener(SessionSearchCache cache) { ServletListenerRegistrationBean<SessionCacheListener> listenerRegistration = new ServletListenerRegistrationBean<>(); listenerRegistration.setListener(new SessionCacheListener(cache)); listenerRegistration.setEnabled(true); return listenerRegistration; } @Bean public SearchCacheAspect searchCacheAspect() { return new SearchCacheAspect(); } @Bean public SessionSearchCache sessionSearchCache() { return new SessionSearchCache(); } }
由于此功能是基于Aspectj实现的,所以在使用时候需要添加Aspectj功能,并且将自动配置类进行导入:
@Import(EnableSearchCacheAutoConfiguration.class)
@EnableAspectJAutoProxy
在Controller方法中添加@SearchCache()即可
@RequestMapping("/testPage") public String testPage(Model model, @SearchCache() User u) { model.addAttribute("key", "searchParam:" + u); System.out.println("testPage==>"); return "dummy"; }
启动项目,访问地址
访问此页面后会将缓存的cacheToken返回,后续页面跳转中只需要带上cacheToken,再次返回查询页面的时候,系统会自动将cacheToken对应的数据获取出来。就上面的例子而言,在访问的时候即将cacheToken传入,age=1024就会自动从缓存中查询出来。
本文例子是通过SessionId,将用户的所有查询信息进行缓存,当session销毁时会自动将缓存中的信息清理。这样缓存请求参数的功能就轻地实现了。
文中的代码已上传至:https://github.com/cmlbeliever/cacheable-search 与
https://github.com/cmlbeliever/cacheable-search-aop
其中CacheableSearch工程为具体缓存实现功能lib,CacheableSearchProject为此项目的demo工程。至于使用方式,聪明的你应该知道。
虽然AOP的方式很使用,但是作为Spring的开发者来说,是不是有更好的方式来实现同样的功能?而且如果想要全局替换key的生成和缓存类,除了修改源码,还有什么更好的方式来实现?
详见文章:http://blog.csdn.net/cml_blog/article/details/79308573
使用了这么久的SpringBoot,但是还没有深入了解过注解的实现原理,想知道常用的注解实现原理么?想掌握各种Starter的实现原理么?可以看看我的课程:
http://gitbook.cn/gitchat/column/5a2fbea7626a7a2421b9a18c
相关文章推荐
- 返回查询页面保持原来的查询条件和数据
- 利用javascript返回查询页面保持原来的查询条件和数据
- 突发奇想-关于列表页面查询条件自动组装的实现
- 关于EnableViewState以及页面中选择asp.net服务端控件和HTML控件的思考
- 关于条件查询DetachedCriteria的一点体会
- oracle 模糊查询 (处理jsp页面传过来的条件)
- 关于html页面中Input(文本框)控件OnChange事件的触发条件
- 关于mybatis 中文条件查询没结果的问题
- 关于 hibernate 逻辑删除 默认查询过滤条件问题(java set 条件)
- 页面上2个按钮,当提交表单的时候调用不同的方法,但是都需要把页面上面的查询条件提交给action
- 在Mybits中,根据页面下拉列表当中不同条件,查询不同的表。
- loushang5关于DataSet通过后台查询出的数据如何映射到前台JSP页面的下拉列表中
- 关于数据库查询语句中的where 1=1条件的应用解析
- 关于版本更新后家用电冰箱2015版附件页面内容修改思考
- THINKPHP分页时保持查询条件
- 使用session在不同页面之间传递参数,sqldatasource按条件查询
- c++中关于字符串的读入——cin、getline、get、gtes(查询+思考+总结)
- loushang5关于多表关联查询结果映射到页面
- ext 列表页面关于多行查询的办法
- OpenJWeb 1.6版本中实现动态设置页面的查询条件列