欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2022年01月31日 09:46:40   作者:踩刀詩(shī)人  
這篇文章來(lái)為大家深入的介紹了框架背后的原理及源碼分析,希望大家能夠更深層次的理解并使用好框架,在此與君共勉,框架雖好,但不要丟了其背后的原理

近期團(tuán)隊(duì)中同學(xué)遇到幾個(gè)問(wèn)題,想在這兒跟大家分享一波,雖說(shuō)不是很有難度,但是背后也折射出一些問(wèn)題,值得思考。

開(kāi)始之前先簡(jiǎn)單介紹一下我所在團(tuán)隊(duì)的技術(shù)棧,基于這個(gè)背景再展開(kāi)后面將提到的幾個(gè)問(wèn)題,將會(huì)有更深刻的體會(huì)。

控制層基于SpringMvc,數(shù)據(jù)持久層基于JdbcTemplate自己封裝了一套類MyBatis的Dao框架,視圖層基于Velocity模板技術(shù),其余組件基于SpringCloud全家桶。

問(wèn)題1

某應(yīng)用發(fā)布以后開(kāi)始報(bào)數(shù)據(jù)庫(kù)連接池不夠用異常,日志如下:

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

很明顯這是數(shù)據(jù)庫(kù)連接池滿了,當(dāng)時(shí)處于業(yè)務(wù)低峰期,所以很顯然并不是由于流量突發(fā)造成的,另一種可能性是長(zhǎng)事務(wù)導(dǎo)致,一般是事務(wù)中摻雜了外部網(wǎng)絡(luò)調(diào)用,最終跟業(yè)務(wù)負(fù)責(zé)人一起排除了長(zhǎng)事務(wù)的可能性。

還有什么可能呢?我隨即想到了是不是沒(méi)有釋放連接導(dǎo)致,我跟業(yè)務(wù)負(fù)責(zé)人說(shuō)了這個(gè)想法,他說(shuō)這種可能性不大,連接的獲取和釋放都是由框架完成的,如果這塊有問(wèn)題早反映出來(lái)了,我想也是。

框架的確給我們帶來(lái)了很大的便利性,將業(yè)務(wù)中一些重復(fù)性的工作下沉到框架中,提高了研發(fā)效率,不夸張的說(shuō)有些人脫離了Spring,MyBatis,SpringMvc這些框架,都不會(huì)寫(xiě)代碼了。

那會(huì)是什么原因呢?我又冒出來(lái)一個(gè)想法,有沒(méi)有可能是某些功能框架支持不了,所以開(kāi)發(fā)繞過(guò)了框架自己實(shí)現(xiàn),進(jìn)而導(dǎo)致連接沒(méi)有釋放,我跟業(yè)務(wù)負(fù)責(zé)人說(shuō)了這個(gè)想法以后,他說(shuō):“這個(gè)有可能,這次有個(gè)功能需要獲取到數(shù)據(jù)庫(kù)名,所以自己通過(guò)Connection對(duì)象獲取的”,說(shuō)到這兒答案大概已經(jīng)出來(lái)了,一起看下這段代碼:

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

代碼很簡(jiǎn)單通過(guò)JdbcTemplate獲取DataSource,再通過(guò)DataSource獲取Connection,最終通過(guò)Connection獲取數(shù)據(jù)庫(kù)名,就是這一行簡(jiǎn)單的代碼將數(shù)據(jù)庫(kù)連接耗盡,因?yàn)檫@里并沒(méi)有釋放連接的動(dòng)作,之前的為什么都沒(méi)有問(wèn)題呢,因?yàn)槠胀ǖ牟樵兌际俏山oJdbcTemplate來(lái)實(shí)現(xiàn)的,它內(nèi)部會(huì)釋放連接,找一個(gè)簡(jiǎn)單的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這個(gè)模板方法實(shí)現(xiàn),在execute內(nèi)部會(huì)通過(guò)finally來(lái)確保連接的釋放

DataSourceUtils.releaseConnection,所以不會(huì)有連接耗盡的問(wèn)題,問(wèn)題已經(jīng)很清晰了,改造也很簡(jiǎn)單,大概有幾下幾種方法:

1.顯示的關(guān)閉連接,這里可以借助jdk的try resource語(yǔ)句,簡(jiǎn)單明了。

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

2.借助于JdbcTemplate的模板方法設(shè)計(jì)思想來(lái)解決,它提供了一個(gè)execute方法,用戶只要實(shí)現(xiàn)ConnectionCallback這個(gè)接口就可以獲取到Connection對(duì)象,在內(nèi)部執(zhí)行獲取數(shù)據(jù)庫(kù)名的邏輯,最終關(guān)閉連接由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();
      }
});

雖然兩種都能解決問(wèn)題,但我還是更推崇第二種方式,因?yàn)檫@種更貼合框架的設(shè)計(jì)思想,將一些重復(fù)性的邏輯繼續(xù)交給框架去實(shí)現(xiàn),這里也體現(xiàn)出框架很重要的一個(gè)特點(diǎn),就是對(duì)使用者提供擴(kuò)展。

問(wèn)題2

前幾天寫(xiě)了一個(gè)Spring AOP的攔截功能,發(fā)現(xiàn)怎么也進(jìn)不到攔截邏輯中,表達(dá)式確定沒(méi)問(wèn)題,讓我百思不得其解,最終冷靜下來(lái)逐步排錯(cuò)。

第一個(gè)很明顯的錯(cuò)誤是被攔截的對(duì)象并沒(méi)有納入Spring管理,所以當(dāng)即把對(duì)象交由Spring管理,問(wèn)題依然沒(méi)有解決,我開(kāi)始回想代理的原理。

Spring代理提供了兩種實(shí)現(xiàn)方式,一種是jdk的動(dòng)態(tài)代理,另一種是cglib代理,這兩種方式分別適用于代理類實(shí)現(xiàn)了接口和代理類未實(shí)現(xiàn)接口的情況,其內(nèi)部思想都是基于某種規(guī)約(接口或者父類)來(lái)生成一個(gè)Proxy對(duì)象,在Proxy對(duì)象方法調(diào)用時(shí)先調(diào)用InvocationHandler的invoke方法,在invoke方法內(nèi)部先執(zhí)行代理邏輯,再執(zhí)行被代理對(duì)象的真實(shí)邏輯,這里貼一段jdk動(dòng)態(tài)代理生成的Proxy對(duì)象的源文件供大家閱讀:

public class ProxyTest {
   /**
  定義目標(biāo)接口,內(nèi)部包含一個(gè)hello方法(這其實(shí)就是一個(gè)規(guī)約)
  */
    public interface ProxyT{
        void hello();
    }
    /**
    實(shí)現(xiàn)類,實(shí)現(xiàn)了ProxyT接口
    */
    public static class ProxyTImpl implements ProxyT{
        @Override
        public void hello() {
            System.out.println("aaaa");
        }
    }
    public static void main(String[] args) {
        //設(shè)置生成Proxy對(duì)象的源文件
        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類,實(shí)現(xiàn)了ProxyT接口
(這里其實(shí)也解釋了為什么jdk的動(dòng)態(tài)代理只能基于接口實(shí)現(xiàn),不能基于父類,因?yàn)镻roxy
必須繼承jdk的Proxy,而java又是單繼承,所以Proxy只能基于接口這個(gè)規(guī)約來(lái)生成)
*/
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方法將調(diào)用權(quán)交給了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());
        }
    }
}

到這里其實(shí)我已經(jīng)有了答案,是我給Spring的規(guī)約(接口或者父類)出現(xiàn)了問(wèn)題,首先我要代理的類并沒(méi)有實(shí)現(xiàn)接口,所以這里的規(guī)約不是接口,而是我這個(gè)類本身,從cglib的原理來(lái)講,它是將要代理的類作為父類來(lái)生成一個(gè)Proxy類,重寫(xiě)要代理的方法,進(jìn)而添加代理邏輯,問(wèn)題就在于我那個(gè)類的方法是static的,而static方法是沒(méi)法重寫(xiě)的,所以導(dǎo)致一直沒(méi)有進(jìn)攔截邏輯,將static方法改為實(shí)例方法就解決了問(wèn)題,這里貼一段cglib動(dòng)態(tài)代理生成的Proxy對(duì)象的源文件供大家閱讀:

public class cglibtest {
    //定義被代理的類ProxyT,內(nèi)部有一個(gè)hello方法
    public static class ProxyT{
        public void hello() {
            System.out.println("aaaa");
        }
    }
    //定義一個(gè)方法攔截器,和jdk的InvocationHandler類似
    public static class Interceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            //簡(jiǎn)單的打印
            System.out.println("before invoke hello");
            //執(zhí)行被代理類的方法(hello)
            return methodProxy.invokeSuper(o,objects);
        }
    }
    public static void main(String[] args) {
        // 設(shè)置CGLib代理類的生成位置
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cg");
        // 設(shè)置JDK代理類的輸出
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        MethodInterceptor methodInterceptor = new Interceptor();
        Enhancer enhancer = new Enhancer();
        //設(shè)置父類
        enhancer.setSuperclass(ProxyT.class);
        //設(shè)置方法回調(diào)
        enhancer.setCallback(methodInterceptor);
        ProxyT proxy = (ProxyT)enhancer.create();
        proxy.hello();
    }
}

最終生成的Proxy源文件如下(刪除了部分代碼,只保留了重寫(xiě)hello方法邏輯):

//繼承ProxyT
public class cglibtest$ProxyT$$EnhancerByCGLIB$$8b3109a3 extends ProxyT implements Factory {
   final void CGLIB$hello$0() {
        super.hello();
    }
    //重寫(xiě)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) {
            //執(zhí)行方法攔截器
            var10000.intercept(this, CGLIB$hello$0$Method, CGLIB$emptyArgs, CGLIB$hello$0$Proxy);
        } else {
            super.hello();
        }
    }
}

總結(jié)

前面描述了筆者近期工作中遇到的兩個(gè)問(wèn)題,不能說(shuō)多么有難度,但是我相信應(yīng)該有不少人都碰到過(guò),不知道你是怎么解決的呢?解決了以后有沒(méi)有深挖其背后的原理呢,好多人說(shuō)自己的工作都是簡(jiǎn)單的crud沒(méi)有提高,那何不嘗試著深挖框架背后的原理,深挖那些看似普通但背后并不簡(jiǎn)單的問(wèn)題的本質(zhì)。 

框架雖好,但不要丟了其背后的原理。

以上就是深入理解框架背后的原理及源碼分析的詳細(xì)內(nèi)容,更多關(guān)于框架原理及源碼分析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java生成彩色附logo二維碼

    java生成彩色附logo二維碼

    這篇文章主要為大家介紹了java生成帶logo的多彩二維碼,比一般二維碼顏色鮮艷,美觀,如何生成二維碼,下面小編為大家分享實(shí)現(xiàn)代碼,感興趣的小伙伴們可以參考一下
    2016-04-04
  • Java的微信開(kāi)發(fā)中使用XML格式和JSON格式數(shù)據(jù)的示例

    Java的微信開(kāi)發(fā)中使用XML格式和JSON格式數(shù)據(jù)的示例

    這篇文章主要介紹了Java微信開(kāi)發(fā)中使用XML格式和JSON格式數(shù)據(jù)的示例,注意一下json-lib所需要的jar包,需要的朋友可以參考下
    2016-02-02
  • Hystrix?Dashboard斷路監(jiān)控儀表盤(pán)的實(shí)現(xiàn)詳細(xì)介紹

    Hystrix?Dashboard斷路監(jiān)控儀表盤(pán)的實(shí)現(xiàn)詳細(xì)介紹

    這篇文章主要介紹了Hystrix?Dashboard斷路監(jiān)控儀表盤(pán)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-09-09
  • springboot 默認(rèn)靜態(tài)路徑實(shí)例解析

    springboot 默認(rèn)靜態(tài)路徑實(shí)例解析

    這篇文章主要介紹了springboot 默認(rèn)靜態(tài)路徑實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • java8 streamList轉(zhuǎn)換使用詳解

    java8 streamList轉(zhuǎn)換使用詳解

    這篇文章主要介紹了java8 streamList轉(zhuǎn)換使用詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-08-08
  • Java的函數(shù)式編程詳解

    Java的函數(shù)式編程詳解

    用了這么久的Java8,我尋思這種話也好意思說(shuō)出來(lái)嗎,難道自己是PythonBoy出身就是看不懂Java的理由嗎,身為一個(gè)合格的后端Boy不會(huì)還有人看不明白Java的函數(shù)式編程吧,接下來(lái)小編和大家淺聊一下Java的函數(shù)式編程,需要的朋友可以參考下
    2023-10-10
  • Java利用poi讀取Excel詳解實(shí)現(xiàn)

    Java利用poi讀取Excel詳解實(shí)現(xiàn)

    Apache POI 是用Java編寫(xiě)的免費(fèi)開(kāi)源的跨平臺(tái)的 Java API,Apache POI提供API給Java對(duì)Microsoft Office格式檔案讀和寫(xiě)的功能。POI為“Poor Obfuscation Implementation”的首字母縮寫(xiě),意為簡(jiǎn)潔版的模糊實(shí)現(xiàn)
    2022-07-07
  • kafka啟動(dòng)報(bào)錯(cuò)(Cluster ID)不匹配問(wèn)題以及解決

    kafka啟動(dòng)報(bào)錯(cuò)(Cluster ID)不匹配問(wèn)題以及解決

    這篇文章主要介紹了kafka啟動(dòng)報(bào)錯(cuò)(Cluster ID)不匹配問(wèn)題以及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • java內(nèi)部測(cè)試類代碼詳解

    java內(nèi)部測(cè)試類代碼詳解

    這篇文章主要介紹了java內(nèi)部測(cè)試類代碼詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • 懶人 IDEA 插件推薦: EasyCode 一鍵幫你生成所需代碼(Easycode用法)

    懶人 IDEA 插件推薦: EasyCode 一鍵幫你生成所需代碼(Easycode用法)

    這篇文章主要介紹了懶人 IDEA 插件推薦: EasyCode 一鍵幫你生成所需代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08

最新評(píng)論