深入理解框架背后的原理及源码分析

目录
  • 问题1
  • 问题2
  • 总结

近期团队中同学遇到几个问题,想在这儿跟大家分享一波,虽说不是很有难度,但是背后也折射出一些问题,值得思考。

开始之前先简单介绍一下我所在团队的技术栈,基于这个背景再展开后面将提到的几个问题,将会有更深刻的体会。

控制层基于SpringMvc,数据持久层基于JdbcTemplate自己封装了一套类MyBatis的Dao框架,视图层基于Velocity模板技术,其余组件基于SpringCloud全家桶。

问题1

某应用发布以后开始报数据库连接池不够用异常,日志如下:

com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 60000, active 500, maxActive 500, creating 0 

很明显这是数据库连接池满了,当时处于业务低峰期,所以很显然并不是由于流量突发造成的,另一种可能性是长事务导致,一般是事务中掺杂了外部网络调用,最终跟业务负责人一起排除了长事务的可能性。

还有什么可能呢?我随即想到了是不是没有释放连接导致,我跟业务负责人说了这个想法,他说这种可能性不大,连接的获取和释放都是由框架完成的,如果这块有问题早反映出来了,我想也是。

框架的确给我们带来了很大的便利性,将业务中一些重复性的工作下沉到框架中,提高了研发效率,不夸张的说有些人脱离了Spring,MyBatis,SpringMvc这些框架,都不会写代码了。

那会是什么原因呢?我又冒出来一个想法,有没有可能是某些功能框架支持不了,所以开发绕过了框架自己实现,进而导致连接没有释放,我跟业务负责人说了这个想法以后,他说:“这个有可能,这次有个功能需要获取到数据库名,所以自己通过Connection对象获取的”,说到这儿答案大概已经出来了,一起看下这段代码:

public String getSchema(String tablename, boolean cached) throws Exception {
    return this.getJdbcTemplate(tablename).getDataSource().getConnection().getCatalog();
}

代码很简单通过JdbcTemplate获取DataSource,再通过DataSource获取Connection,最终通过Connection获取数据库名,就是这一行简单的代码将数据库连接耗尽,因为这里并没有释放连接的动作,之前的为什么都没有问题呢,因为普通的查询都是委派给JdbcTemplate来实现的,它内部会释放连接,找一个简单的query方法看下:

public <T> T query(PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse) throws DataAccessException {
        Assert.notNull(rse, "ResultSetExtractor must not be null");
        this.logger.debug("Executing prepared SQL query");
        return this.execute(psc, new PreparedStatementCallback<T>() {
            @Nullable
            public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
                ResultSet rs = null;
                Object var3;
                try {
                    if (pss != null) {
                        pss.setValues(ps);
                    }
                    rs = ps.executeQuery();
                    var3 = rse.extractData(rs);
                } finally {
                    JdbcUtils.closeResultSet(rs);
                    if (pss instanceof ParameterDisposer) {
                        ((ParameterDisposer)pss).cleanupParameters();
                    }
                }
                return var3;
            }
        });
    }
    public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException {
        Assert.notNull(psc, "PreparedStatementCreator must not be null");
        Assert.notNull(action, "Callback object must not be null");
        if (this.logger.isDebugEnabled()) {
            String sql = getSql(psc);
            this.logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
        }
        Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
        PreparedStatement ps = null;
        Object var13;
        try {
            ps = psc.createPreparedStatement(con);
            this.applyStatementSettings(ps);
            T result = action.doInPreparedStatement(ps);
            this.handleWarnings((Statement)ps);
            var13 = result;
        } catch (SQLException var10) {
            if (psc instanceof ParameterDisposer) {
                ((ParameterDisposer)psc).cleanupParameters();
            }
            String sql = getSql(psc);
            psc = null;
            JdbcUtils.closeStatement(ps);
            ps = null;
            DataSourceUtils.releaseConnection(con, this.getDataSource());
            con = null;
            throw this.translateException("PreparedStatementCallback", sql, var10);
        } finally {
            if (psc instanceof ParameterDisposer) {
                ((ParameterDisposer)psc).cleanupParameters();
            }
            JdbcUtils.closeStatement(ps);
            DataSourceUtils.releaseConnection(con, this.getDataSource());
        }
        return var13;
    }

query方法基于execute这个模板方法实现,在execute内部会通过finally来确保连接的释放

DataSourceUtils.releaseConnection,所以不会有连接耗尽的问题,问题已经很清晰了,改造也很简单,大概有几下几种方法:

1.显示的关闭连接,这里可以借助jdk的try resource语句,简单明了。

 public String getSchema(String tablename, boolean cached) throws Exception {
      try(Connection connection = this.getJdbcTemplate(tablename).getDataSource().getConnection()){
                return connection.getCatalog();
      }
}    

2.借助于JdbcTemplate的模板方法设计思想来解决,它提供了一个execute方法,用户只要实现ConnectionCallback这个接口就可以获取到Connection对象,在内部执行获取数据库名的逻辑,最终关闭连接由finally完成。

/**
   * Execute a JDBC data access operation, implemented as callback action
   * working on a JDBC Connection. This allows for implementing arbitrary
   * data access operations, within Spring's managed JDBC environment:
   * that is, participating in Spring-managed transactions and converting
   * JDBC SQLExceptions into Spring's DataAccessException hierarchy.
   * <p>The callback action can return a result object, for example a domain
   * object or a collection of domain objects.
   * @param action a callback object that specifies the action
   * @return a result object returned by the action, or {@code null} if none
   * @throws DataAccessException if there is any problem
   */
@Nullable
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
    Assert.notNull(action, "Callback object must not be null");
    Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
    Object var10;
    try {
        Connection conToUse = this.createConnectionProxy(con);
        var10 = action.doInConnection(conToUse);
    } catch (SQLException var8) {
        String sql = getSql(action);
        DataSourceUtils.releaseConnection(con, this.getDataSource());
        con = null;
        throw this.translateException("ConnectionCallback", sql, var8);
    } finally {
        DataSourceUtils.releaseConnection(con, this.getDataSource());
    }
        return var10;
 }
jdbcTemplate.execute(new ConnectionCallback<Object>() {
      @Override
      public Object doInConnection(Connection connection) throws SQLException, DataAccessException {
          return connection.getCatalog();
      }
});

虽然两种都能解决问题,但我还是更推崇第二种方式,因为这种更贴合框架的设计思想,将一些重复性的逻辑继续交给框架去实现,这里也体现出框架很重要的一个特点,就是对使用者提供扩展。

问题2

前几天写了一个Spring AOP的拦截功能,发现怎么也进不到拦截逻辑中,表达式确定没问题,让我百思不得其解,最终冷静下来逐步排错。

第一个很明显的错误是被拦截的对象并没有纳入Spring管理,所以当即把对象交由Spring管理,问题依然没有解决,我开始回想代理的原理。

Spring代理提供了两种实现方式,一种是jdk的动态代理,另一种是cglib代理,这两种方式分别适用于代理类实现了接口和代理类未实现接口的情况,其内部思想都是基于某种规约(接口或者父类)来生成一个Proxy对象,在Proxy对象方法调用时先调用InvocationHandler的invoke方法,在invoke方法内部先执行代理逻辑,再执行被代理对象的真实逻辑,这里贴一段jdk动态代理生成的Proxy对象的源文件供大家阅读:

public class ProxyTest {
   /**
  定义目标接口,内部包含一个hello方法(这其实就是一个规约)
  */
    public interface ProxyT{
        void hello();
    }
    /**
    实现类,实现了ProxyT接口
    */
    public static class ProxyTImpl implements ProxyT{
        @Override
        public void hello() {
            System.out.println("aaaa");
        }
    }
    public static void main(String[] args) {
        //设置生成Proxy对象的源文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        ProxyT proxyT1 = (ProxyT)Proxy.newProxyInstance(ProxyT.class.getClassLoader(),new Class[]{ProxyT.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("invoke");
                return method.invoke(proxyT,args);
            }
        });
        proxyT1.hello();
    }
}

最终生成的Proxy源文件如下:

package com.sun.proxy;
import coding4fun.ProxyTest.ProxyT;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/**
生成的proxy源码,继承jdk的Proxy类,实现了ProxyT接口
(这里其实也解释了为什么jdk的动态代理只能基于接口实现,不能基于父类,因为Proxy
必须继承jdk的Proxy,而java又是单继承,所以Proxy只能基于接口这个规约来生成)
*/
public final class $Proxy0 extends Proxy implements ProxyT {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    //hello方法将调用权交给了InvocationHandler
    public final void hello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("coding4fun.ProxyTest$ProxyT").getMethod("hello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

到这里其实我已经有了答案,是我给Spring的规约(接口或者父类)出现了问题,首先我要代理的类并没有实现接口,所以这里的规约不是接口,而是我这个类本身,从cglib的原理来讲,它是将要代理的类作为父类来生成一个Proxy类,重写要代理的方法,进而添加代理逻辑,问题就在于我那个类的方法是static的,而static方法是没法重写的,所以导致一直没有进拦截逻辑,将static方法改为实例方法就解决了问题,这里贴一段cglib动态代理生成的Proxy对象的源文件供大家阅读:

public class cglibtest {
    //定义被代理的类ProxyT,内部有一个hello方法
    public static class ProxyT{
        public void hello() {
            System.out.println("aaaa");
        }
    }
    //定义一个方法拦截器,和jdk的InvocationHandler类似
    public static class Interceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            //简单的打印
            System.out.println("before invoke hello");
            //执行被代理类的方法(hello)
            return methodProxy.invokeSuper(o,objects);
        }
    }
    public static void main(String[] args) {
        // 设置CGLib代理类的生成位置
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cg");
        // 设置JDK代理类的输出
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        MethodInterceptor methodInterceptor = new Interceptor();
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(ProxyT.class);
        //设置方法回调
        enhancer.setCallback(methodInterceptor);
        ProxyT proxy = (ProxyT)enhancer.create();
        proxy.hello();
    }
}

最终生成的Proxy源文件如下(删除了部分代码,只保留了重写hello方法逻辑):

//继承ProxyT
public class cglibtest$ProxyT$$EnhancerByCGLIB$$8b3109a3 extends ProxyT implements Factory {
   final void CGLIB$hello$0() {
        super.hello();
    }
    //重写hello方法
    public final void hello() {
        //方法拦截器
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        if (var10000 != null) {
            //执行方法拦截器
            var10000.intercept(this, CGLIB$hello$0$Method, CGLIB$emptyArgs, CGLIB$hello$0$Proxy);
        } else {
            super.hello();
        }
    }
}

总结

前面描述了笔者近期工作中遇到的两个问题,不能说多么有难度,但是我相信应该有不少人都碰到过,不知道你是怎么解决的呢?解决了以后有没有深挖其背后的原理呢,好多人说自己的工作都是简单的crud没有提高,那何不尝试着深挖框架背后的原理,深挖那些看似普通但背后并不简单的问题的本质。

框架虽好,但不要丢了其背后的原理。

以上就是深入理解框架背后的原理及源码分析的详细内容,更多关于框架原理及源码分析的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java RPC框架熔断降级机制原理解析

    熔断与降级 为什么在RPC环节中有熔断以及降级的需求,详细的原因这里不多解释,从网上搜索一张图做示意. 熔断 我理解熔段主要解决如下几个问题: 当所依赖的对象不稳定时,能够起到快速失败的目的快速失败后,能够根据一定的算法动态试探所依赖对象是否恢复 比如产品详细页获取产品的好评总数时,由于后端服务异常导致客户端每次都需要等到超时.如果短时间内服务不能恢复,那么这段时间内的所有请求时间都将是最大的超时时间,这类消费时间又得不到正确结果的现象是不能容忍的.所以遇到这类情况,就需要根据一定的算法判定服务

  • SSM框架流程及原理分析

    前言:学ssm框架已经有很长时间,今天来复习一下 SSM图示流程: Spring核心:Java反射 Mybatis:动态代理,而动态代理又是基于反射的,所以,ssm框架核心原理在反射. (1)Spring(对象工厂): 平时开发接触最多的估计就是这个IOC容器,它可以装载bean(也就是Java中的类,当然也包括service.dao里面的),有了这个机制,就不用在每次使用这个类的时候为它初始化,很少看到关键字new. (2)SpringMVC(视图控制器): 核心为一个DispatcherSe

  • 浅谈SSH框架中spring的原理

    在ssh项目中,是有明确分工的,spring的作用就相当于将struts和hibernate连接起来,是将两个没有关系的框架的特性,方法,action都放在spring的配置文件中使他们建立关系.取他门各自所长.而这些做法他们自己不知道,他们是听命于spring调度的,他的的任务只是做好自己的事情. 这样做的好处就是任务结构分明,struts只管理显示与做什么,hibernate只关心怎么做,而spring就相当于领导,所以一切的类都要交给spring的工厂创建,这是一种良好的开发模式,体现了一

  • 详解ssh框架原理及流程

    什么是SSH SSH是 struts+spring+hibernate的一个集成框架,是目前较流行的一种web应用程序开源框架.SSH不是一个框架,而是把多个框架(Struts.Spring以及Hibernate)紧密的结合在一起,用于构建灵活.易于扩展的多层Web应用程序. SSH框架的系统从职责上分为四层:表示层.业务逻辑层.数据持久层和域模块层(实体层). SSH(Struts2+Spring+Hibernate)框架的项目,该架构主要分为三个层次: (1)Struts2:负责web层 (

  • 深入理解JavaScript的React框架的原理

    如果你在两个月前问我对React的看法,我很可能这样说: 我的模板在哪里?javascript中的HTML在做些什么疯狂的事情?JSX开起来非常奇怪!快向它开火,消灭它吧! 那是因为我没有理解它. 我发誓,React 无疑是在正确的轨道上, 请听我道来. Good old MVC 在一个交互式应用程序一切罪恶的根源是管理状态. "传统"的方式是MVC架构,或者一些变体. MVC提出你的模型是检验真理的唯一来源 - 所有的状态住在那里. 视图是源自模型,并且必须保持同步. 当模式的转变,

  • 深入理解框架背后的原理及源码分析

    目录 问题1 问题2 总结 近期团队中同学遇到几个问题,想在这儿跟大家分享一波,虽说不是很有难度,但是背后也折射出一些问题,值得思考. 开始之前先简单介绍一下我所在团队的技术栈,基于这个背景再展开后面将提到的几个问题,将会有更深刻的体会. 控制层基于SpringMvc,数据持久层基于JdbcTemplate自己封装了一套类MyBatis的Dao框架,视图层基于Velocity模板技术,其余组件基于SpringCloud全家桶. 问题1 某应用发布以后开始报数据库连接池不够用异常,日志如下: co

  • java编程Reference核心原理示例源码分析

    带着问题,看源码针对性会更强一点.印象会更深刻.并且效果也会更好.所以我先卖个关子,提两个问题(没准下次跳槽时就被问到). 我们可以用ByteBuffer的allocateDirect方法,申请一块堆外内存创建一个DirectByteBuffer对象,然后利用它去操作堆外内存.这些申请完的堆外内存,我们可以回收吗?可以的话是通过什么样的机制回收的? 大家应该都知道WeakHashMap可以用来实现内存相对敏感的本地缓存,为什么WeakHashMap合适这种业务场景,其内部实现会做什么特殊处理呢?

  • Nacos配置中心集群原理及源码分析

    目录 Nacos集群工作原理 配置变更同步入口 AsyncNotifyService AsyncTask 目标节点接收请求 NacosDelayTaskExecuteEngine ProcessRunnable processTasks DumpProcessor.process Nacos作为配置中心,必然需要保证服务节点的高可用性,那么Nacos是如何实现集群的呢? 下面这个图,表示Nacos集群的部署图. Nacos集群工作原理 Nacos作为配置中心的集群结构中,是一种无中心化节点的设计

  • java并发容器CopyOnWriteArrayList实现原理及源码分析

    CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为"写时复制器",Java并发包中类似的容器还有CopyOnWriteSet.本文会对CopyOnWriteArrayList的实现原理及源码进行分析. 实现原理 我们都知道,集合框架中的ArrayList是非线程安全的,Vector虽是线程安全的,但由于简单粗暴的锁同步机制,

  • SpringBoot2入门自动配置原理及源码分析

    目录 SpringBoot自动配置 一.@SpringBootApplication 1. @SpringBootConfiguration 2. @ComponentScan 3. @EnableAutoConfiguration 二.自动配置示例 1. 未生效的自动配置 2. 生效的自动配置 三.小结 SpringBoot自动配置 之前为什么会去了解一些底层注解,其实就是为了后续更好的了解 springboot 底层的一些原理,比如自动配置原理. 一.@SpringBootApplicati

  • CI框架装载器Loader.php源码分析

    顾名思义,装载器就是加载元素的,使用CI时,经常加载的有: $this->load->library() $this->load->view() $this->load->model() $this->load->database() $this->load->helper() $this->load->config() $this->load->add_package_path() 复制代码 代码如下: /**  * L

  • Java并发编程之ReentrantLock实现原理及源码剖析

    目录 一.ReentrantLock简介 二.ReentrantLock使用 三.ReentrantLock源码分析 1.非公平锁源码分析 2.公平锁源码分析 前面<Java并发编程之JUC并发核心AQS同步队列原理剖析>介绍了AQS的同步等待队列的实现原理及源码分析,这节我们将介绍一下基于AQS实现的ReentranLock的应用.特性.实现原理及源码分析. 一.ReentrantLock简介 ReentrantLock位于Java的juc包里面,从JDK1.5开始出现,是基于AQS同步队列

  • 深入解析spring AOP原理及源码

    目录 @EnableAspectJAutoProxy 找切面 代理对象的创建 代理方法的执行 ExposeInvocationInterceptor#invoke 环绕通知的执行 前置通知的执行 后置通知的执行 返回后通知的执行 异常通知的执行 @EnableAspectJAutoProxy @EnableAspectJAutoProxy注解用于开启AOP功能,那么这个注解底层到底做了什么呢? 查看@EnableAspectJAutoProxy的源码,发现它使用@Import注解向Spring容

  • Hadoop源码分析一架构关系简介

    1. 简介 Hadoop是一个由Apache基金会所开发的分布式系统基础架构 Hadoop起源于谷歌发布的三篇论文:GFS.MapReduce.BigTable.其中GFS是谷歌的分布式文件存储系统,MapReduce是基于这个分布式文件存储系统的一个计算框架,BigTable是一个分布式的数据库.hadoop实现了论文GFS和MapReduce中的内容,Hbase的实现了参考了论文BigTable. 2. hadoop架构 hadoop主要有三个组件 HDFS.YARN和MapReduce.其

  • 深入理解Python虚拟机中复数(complex)的实现原理及源码剖析

    目录 复数数据结构 复数的操作 复数加法 复数取反 Repr 函数 总结 复数数据结构 在 cpython 当中对于复数的数据结构实现如下所示: typedef struct { double real; double imag; } Py_complex; #define PyObject_HEAD PyObject ob_base; typedef struct { PyObject_HEAD Py_complex cval; } PyComplexObject; typedef struc

随机推荐