手寫篇——使用JNDI完善依賴查找,并整合JPA

文章來(lái)自公眾號(hào)三不猴歡迎關(guān)注我的公眾號(hào),公眾號(hào)內(nèi)回復(fù)666獲取面試資料,回復(fù)電子書獲取200本PDF電子書

前言

之前寫過(guò)一篇JNDI實(shí)現(xiàn)一個(gè)依賴注入的文章,很多小伙伴都表示很疑惑,那玩意兒有啥用,包括這篇文章可能你也覺(jué)得沒(méi)啥用,確實(shí)在實(shí)際開(kāi)發(fā)中都是使用spring來(lái)做依賴查找、依賴注入,那你有沒(méi)有想過(guò)在沒(méi)有spring的年代是怎么做依賴查找和依賴注入的?沒(méi)錯(cuò)就是可以使用JNDI。寫JNDI系列文章的目的是為了了解JAVAEE單體架構(gòu)是如何演變成現(xiàn)在spring 技術(shù)棧的,這是一個(gè)系列后面將一步一步演進(jìn)成主流spring、spring boot、spring cloud風(fēng)格,具有Spring、SpringMVC完整功能的項(xiàng)目。歡迎關(guān)注了解后續(xù)。看完本文你將了解如果讓你來(lái)實(shí)現(xiàn)一個(gè)spring 的思路,大致流程。

實(shí)踐

上篇我們通過(guò)JNDI把xml中的元信息讀出,放入ServletContext中,使用了JNDI中的lookup方法作為依賴查找。下面開(kāi)始對(duì)之前的代碼進(jìn)行重構(gòu)。

首先對(duì)初始化方法就行重構(gòu),我們先把初始化的動(dòng)作定義成:初始化環(huán)境、實(shí)例化組件、初始化組件三個(gè)步驟。看過(guò)spring源碼的小伙伴是不是直呼有內(nèi)味兒了。

public void init(ServletContext servletContext) throws RuntimeException {
        ComponentContext.servletContext = servletContext;
        servletContext.setAttribute(CONTEXT_NAME, this);
        // 獲取當(dāng)前 ServletContext(WebApp)ClassLoader
        this.classLoader = servletContext.getClassLoader();
        initEnvContext();
        instantiateComponents();
        initializeComponents();
    }

初始化環(huán)境

我們這個(gè)項(xiàng)目的環(huán)境就是初始化JNDI的上下文Context,所以代碼如下:

private void initEnvContext() throws RuntimeException {
        if (this.envContext != null) {
            return;
        }
        Context context = null;
        try {
            context = new InitialContext();
            this.envContext = (Context) context.lookup(COMPONENT_ENV_CONTEXT_NAME);
        } catch (NamingException e) {
            throw new RuntimeException(e);
        } finally {
            close(context);
        }
    }

有初始肯定也有銷毀操作,代碼如下:(因?yàn)镴NDI有很多檢查異常,書寫時(shí)有諸多不便,所以這里用了一個(gè)FunctionalInterface 包裝一下,不影響主流程,當(dāng)做這個(gè)context.close()就好,下文不再提及)

private static void close(Context context) {
        if (context != null) {
            ThrowableAction.execute(context::close);
        }
    }

實(shí)例化組件

實(shí)例化組件的思路就是把根目錄下的元數(shù)據(jù)全部讀出來(lái),并放入緩存map中。具體細(xì)節(jié)參照代碼注釋。

        /**
     * 實(shí)例化組件
     */
    protected void instantiateComponents() {
        // 遍歷獲取所有的組件名稱
        List<String> componentNames = listAllComponentNames();
        // 通過(guò)依賴查找,實(shí)例化對(duì)象( Tomcat BeanFactory setter 方法的執(zhí)行,僅支持簡(jiǎn)單類型)
        componentNames.forEach(name -> componentsMap.put(name, lookupComponent(name)));
    }
    
    private List<String> listAllComponentNames() {
        return listComponentNames("/");
    }

    protected List<String> listComponentNames(String name) {
        return executeInContext(context -> {
            NamingEnumeration<NameClassPair> e = executeInContext(context, ctx -> ctx.list(name), true);

            // 目錄 - Context
            // 節(jié)點(diǎn) -
            if (e == null) { // 當(dāng)前 JNDI 名稱下沒(méi)有子節(jié)點(diǎn)
                return Collections.emptyList();
            }

            List<String> fullNames = new LinkedList<>();
            while (e.hasMoreElements()) {
                NameClassPair element = e.nextElement();
                String className = element.getClassName();
                Class<?> targetClass = classLoader.loadClass(className);
                if (Context.class.isAssignableFrom(targetClass)) {
                    // 如果當(dāng)前名稱是目錄(Context 實(shí)現(xiàn)類)的話,遞歸查找
                    fullNames.addAll(listComponentNames(element.getName()));
                } else {
                    // 否則,當(dāng)前名稱綁定目標(biāo)類型的話話,添加該名稱到集合中
                    String fullName = name.startsWith("/") ?
                            element.getName() : name + "/" + element.getName();
                    fullNames.add(fullName);
                }
            }
            return fullNames;
        });
    }
    

初始化實(shí)例

初始化的過(guò)程就是類似spring中使用了@Autowired我們要將JNDI上下文context中的實(shí)例注入進(jìn)去,這里我們使用反射調(diào)用set方法完成注入。我們使用Resource注解,目標(biāo)是將使用了@Resource的字段注入上下文中實(shí)例。同時(shí)也對(duì)PostConstruct注解進(jìn)行處理。

protected void initializeComponents() {
        componentsMap.values().forEach(component -> {
            Class<?> componentClass = component.getClass();
            // 注入階段 - {@link Resource}
            injectComponents(component, componentClass);
        // 處理PostConstruct
        processPostConstruct(component, componentClass);
        });
    }


private void injectComponents(Object component, Class<?> componentClass) {
        Stream.of(componentClass.getDeclaredFields())
                .filter(field -> {
                    int mods = field.getModifiers();
                    return !Modifier.isStatic(mods) &&
                            field.isAnnotationPresent(Resource.class);
                }).forEach(field -> {
                    Resource resource = field.getAnnotation(Resource.class);
                    String resourceName = resource.name();
                    Object injectedObject = lookupComponent(resourceName);
                    field.setAccessible(true);
                    try {
                        // 注入目標(biāo)對(duì)象
                        field.set(component, injectedObject);
                    } catch (IllegalAccessException e) {
                    }
                });
    }

private void processPostConstruct(Object component, Class<?> componentClass) {
        Stream.of(componentClass.getMethods())
                .filter(method ->
                        !Modifier.isStatic(method.getModifiers()) &&      // 非 static
                                method.getParameterCount() == 0 &&        // 沒(méi)有參數(shù)
                                method.isAnnotationPresent(PostConstruct.class) // 標(biāo)注 @PostConstruct
                ).forEach(method -> {
                    // 執(zhí)行目標(biāo)方法
                    try {
                        method.invoke(component);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                });
    }

使用篇

我們這個(gè)較為完善的依賴注入依賴查找就算完成了,現(xiàn)在我們用它來(lái)整合JPA。不用spring boot去整合JPA應(yīng)該有很多小伙伴不會(huì)吧。我們需要一個(gè)persistence.xml 文件。

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
     http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="emf" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    </persistence-unit>
</persistence>

在我們上篇提到的Context.xml中加上一下內(nèi)容

<Resource name="jdbc/source"
              auth="Container"
              driverClassName="com.mysql.cj.jdbc.Driver"
              maxIdle="30"
              maxTotal="50"
              maxWaitMillis="-1"
              username="root"
              password="root"
              type="javax.sql.DataSource"
              url="jdbc:mysql://localhost:3306/test?useSSL=true"/>

    <!--
    缺少指定 interface 類型的屬性
    目標(biāo)注入的類型:javax.persistence.EntityManager
    -->
    <Resource name="bean/EntityManager" auth="Container"
              type="study.jpa.DelegatingEntityManager"
              persistenceUnitName="emf"
              propertiesLocation="META-INF/jpa-datasource.properties"
              factory="org.apache.naming.factory.BeanFactory" />

一個(gè)配置文件properties文件

hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.id.new_generator_mappings=false
hibernate.connection.datasource=@jdbc/source

最后我們?cè)趯?shí)現(xiàn)一個(gè)EntityManager就大功告成了。


    private EntityManagerFactory entityManagerFactory;

    @PostConstruct
    public void init() {
        this.entityManagerFactory =
                Persistence.createEntityManagerFactory(persistenceUnitName, loadProperties(propertiesLocation));
    }


        private Map loadProperties(String propertiesLocation) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL propertiesFileURL = classLoader.getResource(propertiesLocation);
        Properties properties = new Properties();
        try {
            properties.load(propertiesFileURL.openStream());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        // 增加 JNDI 引用處理
        ComponentContext componentContext = ComponentContext.getInstance();

        for (String propertyName : properties.stringPropertyNames()) {
            String propertyValue = properties.getProperty(propertyName);
            if (propertyValue.startsWith("@")) {
                String componentName = propertyValue.substring(1);
                Object component = componentContext.getComponent(componentName);
                properties.put(propertyName, component);
            }
        }

        return properties;
    }

        public void persist(Object entity) {
        getEntityManager().persist(entity);
    }

這樣我們就可以通過(guò)調(diào)用persist方法來(lái)實(shí)現(xiàn)插入操作,另外由于篇幅有限本文只提供了部分核心代碼,感興趣的想獲取完整代碼的可以關(guān)注我加我私人微信我發(fā)給你。感謝閱讀到這里!!!!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,305評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,962評(píng)論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,727評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,193評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,411評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,777評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,978評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,216評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,657評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,960評(píng)論 2 373

推薦閱讀更多精彩內(nèi)容