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

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

前言

之前寫過一篇JNDI實(shí)現(xiàn)一個(gè)依賴注入的文章,很多小伙伴都表示很疑惑虐译,那玩意兒有啥用瘪板,包括這篇文章可能你也覺得沒啥用,確實(shí)在實(shí)際開發(fā)中都是使用spring來做依賴查找漆诽、依賴注入侮攀,那你有沒有想過在沒有spring的年代是怎么做依賴查找和依賴注入的?沒錯(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ù)薄坏。看完本文你將了解如果讓你來實(shí)現(xiàn)一個(gè)spring 的思路寨闹,大致流程胶坠。

實(shí)踐

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

首先對(duì)初始化方法就行重構(gòu)椭蹄,我們先把初始化的動(dòng)作定義成:初始化環(huán)境矮瘟、實(shí)例化組件、初始化組件三個(gè)步驟塑娇〕合溃看過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ù)全部讀出來祝峻,并放入緩存map中魔吐。具體細(xì)節(jié)參照代碼注釋。

        /**
     * 實(shí)例化組件
     */
    protected void instantiateComponents() {
        // 遍歷獲取所有的組件名稱
        List<String> componentNames = listAllComponentNames();
        // 通過依賴查找莱找,實(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 名稱下沒有子節(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í)例

初始化的過程就是類似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 &&        // 沒有參數(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)在我們用它來整合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);
    }

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谆棱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子圆仔,更是在濱河造成了極大的恐慌垃瞧,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坪郭,死亡現(xiàn)場(chǎng)離奇詭異个从,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門嗦锐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嫌松,“玉大人,你說我怎么就攤上這事奕污∥幔” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵碳默,是天一觀的道長(zhǎng)贾陷。 經(jīng)常有香客問我,道長(zhǎng)嘱根,這世上最難降的妖魔是什么髓废? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮儿子,結(jié)果婚禮上瓦哎,老公的妹妹穿的比我還像新娘砸喻。我一直安慰自己柔逼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布割岛。 她就那樣靜靜地躺著愉适,像睡著了一般。 火紅的嫁衣襯著肌膚如雪癣漆。 梳的紋絲不亂的頭發(fā)上维咸,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音惠爽,去河邊找鬼癌蓖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛婚肆,可吹牛的內(nèi)容都是我干的租副。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼较性,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼用僧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赞咙,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤责循,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后攀操,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體院仿,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年速和,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了意蛀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耸别。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖县钥,靈堂內(nèi)的尸體忽然破棺而出秀姐,到底是詐尸還是另有隱情,我是刑警寧澤若贮,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布省有,位于F島的核電站,受9級(jí)特大地震影響谴麦,放射性物質(zhì)發(fā)生泄漏蠢沿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一匾效、第九天 我趴在偏房一處隱蔽的房頂上張望舷蟀。 院中可真熱鬧,春花似錦面哼、人聲如沸野宜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匈子。三九已至,卻和暖如春闯袒,著一層夾襖步出監(jiān)牢的瞬間虎敦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工政敢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留其徙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓喷户,卻偏偏與公主長(zhǎng)得像唾那,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子摩骨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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