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

2 篇文章 0 订阅
1 篇文章 0 订阅

在项目中,查询条件保持是经常使用到的,特别是管理后台。对于前台页面来说,通常为了访问的方便会使用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


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
软件简介: 西华大学不挂科(以下简称不挂科)是一个集成绩录入、成绩详单、点名册查询、学生照片浏览、成绩查询五大功能为一体的绿色软件,以最初版本不挂科软件(只含成绩查询功能)版面为设计原型,并且在功能上不断扩展的一款小软件,适合于西华大学校本部和人南校区同学使用。 之前,作者发布的不挂科(只含成绩查询功能)和教务通两款软件由于功能上属包含关系,所以后续软件只更新教务通软件,并将教务通软件更名为大家所熟悉的“不挂科”。当前软件为不挂科V2.2,之前作者发布的不挂科系列版本有V1.1、V1.2、V2.1,教务通系列有V1.1、V1.2、V1.3、V2.1。 快捷键: 回车键:①输入验证码后,按下回车键即可登录; ②查照片模块,输入学号后,按下回车键即可查询。 Page Up键:查照片模块,按下Page Up键查询上一个学生照片。 Page Down键:查照片模块,按下Page Up键查询下一个学生照片。 特别说明: 1、个别安全软件会提示为木马,请添加信任并在隔离区恢复文件,重新打开软件,编者以人格担保文件安全; 2、打开软件,输入验证码,点击“登录”,登录成功后,软件底部会弹出“成绩录入”、“成绩详单”、“点名册”、“查照片”、“成绩查询”五个图片按钮,默认成绩查询; 3、进入到任何一个模块(成绩查询除外),需要切换到其他模块时,需要单击“返回”按钮; 4、第一次使用软件时,软件会在运行目录中自动生成“选课号.txt”(已存在则不自动生成),用 于记录选课号,请勿随意删除,否则将会丢失已经记录的选课号(“清空”按钮也会清掉所有选课号记录),选课号.txt文件中有选课号范本,文件的每一行为一条选课号记录,手动增加记录后加上回车符,重新运行软件即可; 5、需要用到选课号的模块,输入选课号单击“查询”后,程序会自动记录未保存的选课号; 6、选课号来源:使用自己的帐号登录教务系统,点击“信息查询”菜单,选择“学生选课情况查询”,找到自己要查询课程的选课课号,如:(2012-2013-2)-4200049-0120030086-2。也可以使用本软件的成绩查询模块,查询学生选课情况获取选课号; 7、要使用点名册和成绩详单模块,请确保电脑安装了Microsoft office Excel 2003或者更高版本,查询时请关闭所有Excel表,以及Microsoft office Excel程序。程序需要把所有查询结果数据下载完毕之后才能显示出来。对于没有出来的成绩,查询到的成绩详单只有课程的基本信息,没有具体数据。若需复制成绩单或点名册,数据显示出来之后,单击开始菜单,程序,Microsoft Office,Microsoft Office Excel 2003(或者其他更高版本Excel),将软件界面中你需要的内容复制下来,然后到Excel表粘贴。或者单击表格的任一单元格,然后再次单击“查询”按钮,会有弹窗信息框,“该文档已经修改,是否保存修改结果?”,选择是,保存类型选择Excel工作簿; 8、外网用户查询速度较慢,建议使用校园内网查询; 9、若软件无法使用,确认一下网络连接是否正常,再确认一下是否能正常使用浏览器访问http://jwc.xhu.edu.cn; 10、若需软件更新,请联系作者QQ:527149893或者QQ邮箱。
在使用Vuex实现页面跳转时,需要保持原来的筛选条件,可以通过以下步骤实现: 1. 在Vuex的store中定义一个全局状态对象,该对象用于存储筛选条件。 2. 在筛选条件发生变化时,将新的条件值存储到该全局状态对象中。 3. 在跳转页面前,获取全局状态对象中的筛选条件值。 4. 在目标页面中,在页面加载完成后,通过读取全局状态对象中的筛选条件值,来初始化页面的筛选条件。 5. 在页面组件中使用计算属性或getter方法,从Vuex的全局状态对象中获取筛选条件值,并应用到相应的组件方法中。 以下是一个示例代码,以帮助理解上述步骤: 1. 在Vuex的store.js文件中: ```javascript import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: { filterConditions: {}, // 存储筛选条件的全局状态对象 }, mutations: { updateFilterConditions(state, conditions) { // 更新筛选条件的mutation state.filterConditions = conditions; }, }, }); ``` 2. 在筛选页面组件中: ```javascript import { mapMutations } from 'vuex'; export default { methods: { ...mapMutations(['updateFilterConditions']), applyFilterConditions() { // 将新的筛选条件存储到全局状态对象中 this.updateFilterConditions({ // 筛选条件相关属性和值 }); // 导航到另一个页面 this.$router.push('/目标页面'); }, }, } ``` 3. 在目标页面组件中: ```javascript import { mapState } from 'vuex'; export default { computed: { ...mapState(['filterConditions']), filteredData() { // 使用筛选条件来处理数据 // 在此处应用筛选条件到数据,以获取符合条件的filteredData }, }, created() { // 初始化目标页面时,获取全局状态对象中的筛选条件,并使用它们来初始化页面的筛选条件 this.initializeFilterConditions(); }, methods: { initializeFilterConditions() { // 读取筛选条件,根据需要初始化页面的筛选条件 // 例如:this.filter = this.filterConditions.filter; }, }, } ``` 通过以上步骤,我们可以在使用Vuex实现页面跳转时,保持原来的筛选条件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值