PageHelper源码学习

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

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

Page page = PageHelper.startPage(1, 10);

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

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

    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类加载的时候就会获取到。所以只需要判断是否有这个注解就可以完成判断

@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中就会进行使用。

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,那么这里直接取执行。

public Object intercept(Invocation invocation);

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

    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完成。

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,那么意思就是将自己加入进去。

   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?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值