前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PageHelper源码学习

PageHelper源码学习

作者头像
写一点笔记
发布2020-12-31 10:17:35
4330
发布2020-12-31 10:17:35
举报
文章被收录于专栏:程序员备忘录

做java项目离不来PageHelper,简单说pagehelper就是分页的插件,但是都知道分页其实是对我们要执行的sql之外加上一层,所以相对来说分页其实就是项目开发中的公因式,公因式大家都懂的,开发一个包就可以了。那么我们具体看看pageHelper的原理是是什么的。或者它是怎么做的,我们看看源码的根本想法就是获得一点感悟,这些感悟不仅仅对工作有好处,往远的说还或许影响我们看待事物的角度,久而久之会形成我们的价值观。

从源码中我们看到pagehelper主要有几个包和一些类组成,按照分门别类的思想,那么每个包都对应一个相同的点,而最底下的类应该就是一些功能聚合类。

代码语言:javascript
复制
Page page = PageHelper.startPage(1, 10);

一般来说,我们用pagehelper大概都是上述的方式。

在设置当前页和每页大小的时候,我们看到这里从localpag方法中获取;

代码语言:javascript
复制
    public staticPagestartPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
//申明一个分页实体
        Page page = new Page(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
//从当前线程中获取分页变量
        Page oldPage = getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }
//将分页变量设置到线程的threadlocal
        setLocalPage(page);
        return page;
    }
//具体的threadLocal
    protected static final ThreadLocalLOCAL_PAGE = new ThreadLocal();
    protected static boolean DEFAULT_COUNT = true;
    public PageMethod() {
    }
    protected static void setLocalPage(Page page) {
        LOCAL_PAGE.set(page);
    }
    public staticPagegetLocalPage() {
        return (Page)LOCAL_PAGE.get();
    }

通过上述分析,我们得出的结论就是pageHelper是通过threadlocal来实现分页的,具体就是将分页的参数放到threadlocal中,这是我们设置完毕之后在没有传递参数的原因。除此之外我们还发现如果一次请求中包含多个分页查询时,其实是用的第一个page,并没有重新定义。

如上图所示,pagehelper应该还是支持上述的数据库的。但是并不知道这块是如何整合的。但是作者发现ibatis的接口intercept存在于pagehelper中,我们发现这个接口提供了一个几个方法和一些注解。

该注解是ibatis提供的,type表示拦截的层次,method表示拦截的方法,args表示参数,这是使用了注解,那么在jdk类加载的时候就会获取到。所以只需要判断是否有这个注解就可以完成判断

代码语言:javascript
复制
@Intercepts({@Signature(
    type = Executor.class,
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
    type = Executor.class,
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})

方法setProperties是实例化一个具体的数据库分页实体比如mysql,oracle等。设置好之后,在上边的intercept中就会进行使用。

代码语言:javascript
复制
public void setProperties(Properties properties) {
        this.msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
//拿到数据库类型
        String dialectClass = properties.getProperty("dialect");
        if (StringUtil.isEmpty(dialectClass)) {
            dialectClass = this.default_dialect_class;
        }
        try {
//通过反射拿到对应的pagehelper
            Class aClass = Class.forName(dialectClass);
            this.dialect = (Dialect)aClass.newInstance();
        } catch (Exception var6) {
            throw new PageException(var6);
        }
//设置一些参数
        this.dialect.setProperties(properties);
        String countSuffix = properties.getProperty("countSuffix");
        if (StringUtil.isNotEmpty(countSuffix)) {
            this.countSuffix = countSuffix;
        }
        try {
            this.additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
            this.additionalParametersField.setAccessible(true);
        } catch (NoSuchFieldException var5) {
            throw new PageException(var5);
        }
    }

执行分页,因为在setProperties方法中已经已经实例化pagehelper,那么这里直接取执行。

代码语言:javascript
复制
public Object intercept(Invocation invocation);

在intercept方法中,我们发现最终也是调用用我们获取分页的相关sql并执行了。

代码语言:javascript
复制
    public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
        String sql = boundSql.getSql();
        Page page = this.getLocalPage();
        String orderBy = page.getOrderBy();
        if (StringUtil.isNotEmpty(orderBy)) {
            pageKey.update(orderBy);
            sql = OrderByParser.converToOrderBySql(sql, orderBy);
        }


        return page.isOrderByOnly() ? sql : this.getPageSql(sql, page, pageKey);
    }

分析到这里,我们大概明白了pagehelper仅仅是用来想threadlocal中设置分页参数的,而最周的执行是通过intercept接口来执行的,而接口能被扫描到的原因就是pagehelper中添加了@intercepts注解。既然如此,我们有必要研究一ibatis是如何调用pagehelper的这个接口的。

通过代码跟踪,我们发现plugin类调用了intercept方法,而intercept接口的解析则是方法getSignatureMap完成。

代码语言:javascript
复制
private static Map, Set> getSignatureMap(Interceptor interceptor) {
//解析注解
        Intercepts interceptsAnnotation = (Intercepts)interceptor.getClass().getAnnotation(Intercepts.class);
        if (interceptsAnnotation == null) {
            throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
        } else {
//拿到注解的参数
            Signature[] sigs = interceptsAnnotation.value();
            Map, Set> signatureMap = new HashMap();
            Signature[] var4 = sigs;
            int var5 = sigs.length;
            for(int var6 = 0; var6 < var5; ++var6) {
                Signature sig = var4[var6];
                Set methods = (Set)signatureMap.computeIfAbsent(sig.type(), (k) -> {
                    return new HashSet();
                });
                try {
                    Method method = sig.type().getMethod(sig.method(), sig.args());
                    methods.add(method);
                } catch (NoSuchMethodException var10) {
                    throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + var10, var10);
                }
            }
            return signatureMap;
        }
    }

但是方法wrap方法是谁调用的?作者通过跟踪,发现如下所示。那么我们基本可以得出这个类是ibatis的一个变量,那么是否可以说明ibatis中可以随意调用。

warp方法最后又回调到了plugin类,但是传入的确实this,那么意思就是将自己加入进去。

代码语言:javascript
复制
   public Object plugin(Object target) {
        //传入pagehelperintercept
        return Plugin.wrap(target, this);
    }
    
public static Object wrap(Object target, Interceptor interceptor) {
        Map, Set> signatureMap = getSignatureMap(interceptor);
        Class type = target.getClass();
        Class[] interfaces = getAllInterfaces(type, signatureMap);
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }

作者通过查阅相关资料,发现跟作者想的一样,这里的interceptorChain是一个私有变量。并且在创建相应的拦截器的时候都会调用。

但是作者好奇的是这里初始的intercept是在哪里设置的,最后通过作者的跟踪,发现是pagehelper的自动配置包做了这件事情。

问题分析到这里好像是清晰了好多,但是问题是ibatis是如何调用上边的intercept方法的,虽然我们知道他们走的是jdk反射提供的invoke方法,但是什么时候会invoke?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员备忘录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档