关于页面查询条件保持的思考(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/believer123/article/details/78928693

在项目中,查询条件保持是经常使用到的,特别是管理后台。对于前台页面来说,通常为了访问的方便会使用get的方式进行表单提交,这样进行页面分享或者发送给好友时可以直接打开对于的页面。但是对于管理后台来说,地址栏上的一大串url参数是不允许的,不美观也不安全。
比如在用户查询页面,可以根据用户的年龄,姓名,昵称,等等参数进行查询,而且可能客户已经翻到了第n页上,此时点击某个用户详细,页面跳转到用户详细页面对用户信息进行编辑,编辑完成后点击保存,这时候需要返回到用户查询页面上,并且还得回到用户原来页面。那么可以使用如下的方式:

  • 弹出用户信息页面

这样的好处就是直接可以在页面上编辑,作为弹出层不影响之前查询的条件保持

  • 新开一个页面

这种方式通常不推荐使用,这样容易导致页面打开非常多,具体可以看客户需求

  • 在当前页面跳转

这种是使用最多的一种方式,因为在管理页面上通常来说是只在一个页面上操作的。但是多级页面跳转后查询条件的保持就是问题了。

对于前两种方式都比较简单,这里不多赘述了。本文将重点分析第三种需求的实现方式。

保持条件的方法

这里说说可行的几种方法:

  • 将查询页面中的所有参数带到后续所有页面中

这是最简单的方法,也是最累的方法,如果条件少跳转层级少这种方式是可以使用的,但是如果查询条件一多(通常管理页面查询条件是不少的)或者页面跳转多了,这种方式就呵呵了。

  • 保存到cookie中

作为参数少,且安全性不高的数据可以保存到cookie中,而且还必须管理好cookie的生命周期,其他用户登录时不能获取到之前用户的cookie信息。而且保存的信息不宜多。

  • 将参数缓到后端,等到返回查询页面时再从缓存中获取

比较推荐这种方式,将信息保存到后端后,生成一个缓存key,后面的页面只要传递一个key值即可。下面详述下这种方式的实现。


参数缓到后端

Spring项目中就可以直接使用切面编程和自定义参数注解的方式来实现,在一览页面查询时,将查询出的信息根据SessionId进行缓存即可。
首先介绍下使用Aspectj的方式来实现。

  1. 自定义缓存注解@SearchCache,通过此注解的标注的参数都表示需要缓存起来
  2. 拦截所有Controller中带有SearchCache注解的方法,在方法执行前将信息缓存起来
  3. 在Controller执行前,判断是否有cacheToken参数,如果有的话表示从缓存中读取,将缓存中读取的值作为参数传递到Controller中。
  4. 当用户退出登录时,将缓存信息清理掉

以上步骤简要的说明了整个实现思路,下面来一步步具体实现:
首先添加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


猜你喜欢

转载自blog.csdn.net/believer123/article/details/78928693