造一個方形的輪子3--控制反轉(zhuǎn)

造一個方形輪子文章目錄:造一個方形的輪子

01淆院、先把車正過來

在上一篇《造一個方形的輪子2--添加配置》的最后又翻車了祖驱,現(xiàn)在先把發(fā)現(xiàn)的這個問題解決,將square項目的包引用其它項目無法啟動的主要原因是沒有正確的指定tomcat運行的關(guān)鍵目錄蚊锹,以及讀取資源的目錄筒占。

也就是說把設(shè)置的目錄結(jié)構(gòu)調(diào)整一下就可以了。

添加一個配置目錄的util類,ClassesPathUtil.java:

/**
 * 處理項目路徑問題
 * @author ixx
 * @date 2019-06-15
 */
public class ClassesPathUtil {
    private static final Logger log = LoggerFactory.getLogger(ClassesPathUtil.class);
    /**
     * 項目目錄(.../classes)
     */
    private String projectPath;
    /**
     * 靜態(tài)資源目錄(.../classes/public)
     */
    private String publicPath;

    public ClassesPathUtil(Class clzz){
        String basePath = clzz.getResource("").getPath();
        //  ..../classes
        projectPath = basePath.substring(0, basePath.indexOf("classes")+7);
        publicPath = setPublic(projectPath, "/public");
    }

    private String setPublic(String basePath, String path){
        File publicFile = new File(basePath+path);
        if(!publicFile.exists()){
            publicFile.mkdirs();
        }
        return basePath+path;
    }

    public String getProjectPath() {
        return projectPath;
    }

    public String getPublicPath() {
        return publicPath;
    }

}

如果classes目錄下沒有public 目錄的話就創(chuàng)建一個糯耍,設(shè)置tomcat.addWebapp(context_path, publicPath)時會用到

修改啟動類(這塊改動有點大,放一個全量代碼吧)囊嘉,SquareApplication.java:

/**
 * 項目啟動類
 * @author ixx
 * @date 2019-6-13
 */
public class SquareApplication {
    private static final Logger log = LoggerFactory.getLogger(SquareApplication.class);
    private static Map<String, Object> CONF_MAP = new HashMap<>();
    private static Tomcat tomcat = null;
    private static String CONTEXT_PATH = "/";
    private static String ENCODING = "UTF-8";
    private static int TOMCAT_PORT = 8080;
    private static ClassesPathUtil classesPathUtil;

    public static void run(Class clzz, String[] args) {
        try {
            long startTime = System.currentTimeMillis();
            classesPathUtil = new ClassesPathUtil(clzz);
            // 加載配置
            loadYaml(classesPathUtil.getProjectPath());
            // 初始化參數(shù)
            setArgs(args);
            // 輸出banner
            printBanner(classesPathUtil.getProjectPath());
            tomcat = new Tomcat();
            // 設(shè)置Tomcat工作目錄
            tomcat.setBaseDir(classesPathUtil.getProjectPath() + "/Tomcat");
            tomcat.setPort(TOMCAT_PORT);
            tomcat.addWebapp(CONTEXT_PATH, classesPathUtil.getPublicPath());
            // 執(zhí)行這句才能支持JDNI查找
            tomcat.enableNaming();
            tomcat.getConnector().setURIEncoding(ENCODING);
            tomcat.start();
            log.info("Tomcat started on port(s): {} with context path '{}'", TOMCAT_PORT, CONTEXT_PATH);
            log.info("Started Application in {} ms." , (System.currentTimeMillis() - startTime));
            // 保持服務器進程
            tomcat.getServer().await();
        } catch (Exception e) {
            log.error("Application startup failed...", e);
        }
    }

    /**
     * 初始化參數(shù)
     * @param args
     */
    private static void setArgs(String[] args){
        Map<String, String> map = ArgsToKVUtil.convert(args);
        if(map.get("--server.port") != null){
            TOMCAT_PORT = Integer.parseInt(map.get("--server.port"));
        }
    }

    /**
     * 加載配置文件
     * @param projectPath
     */
    private static void loadYaml(String projectPath){
        CONF_MAP =  LoadApplicationYmlUtil.load(projectPath);
        if(CONF_MAP.get("server.port") != null){
            TOMCAT_PORT = (Integer)CONF_MAP.get("server.port");
        }
        if(CONF_MAP.get("server.servlet.context-path") != null){
            CONTEXT_PATH = (String)CONF_MAP.get("server.servlet.context-path");
        }
    }

    /**
     * 輸出Banner圖
     * @param projectPath
     */
    private static void printBanner(String projectPath){
        BufferedReader br = null;
        try{
            File f = new File(projectPath+"/default-banner.txt");
            if(f.exists()){
                br = new BufferedReader(new FileReader(f));
            } else {
                InputStream is = SquareApplication.class.getClassLoader().getResourceAsStream("default-banner.txt");
                br = new BufferedReader(new InputStreamReader(is));
            }
            StringBuilder stringBuilder = new StringBuilder("\n");
            String line;
            while ((line = br.readLine()) != null){
                stringBuilder.append(line).append("\n");
            }
            log.info(stringBuilder.toString());
        } catch (Exception e){
            log.info("load banner file error!!", e);
            if(br != null){
                try {
                    br.close();
                } catch (IOException e1) {
                }
            }
        }
    }

}

重新在square項目根目錄下執(zhí)行mvn clean install 然后到car項目里去啟動項目就可以了温技。

02、添加注解

實現(xiàn)控制反轉(zhuǎn)的思路扭粱,

1舵鳞、要有自己定義的注解,標識類需要初始化到容器中

2琢蛤、程序啟動時調(diào)用初始化方法(默認從啟動類目錄向下掃描所有包里的類文件)

3蜓堕、根據(jù)接口名稱、類名稱及添加注解時定義的bean名稱初始化到容器中

4博其、處理Bean互相之間的依賴關(guān)系(屬于依賴注入)

這里我先只實現(xiàn)一個@Service@Component 兩個注解套才,對外提供服務的@Controller 后邊實現(xiàn)對外Rest接口的時候再實現(xiàn)。

Service.java

package com.jisuye.annotations;
// import ...;
/**
 * Service注解
 * @author ixx
 * @date 2019-06-20
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
     String value() default "";
}

Component.java

package com.jisuye.annotations;
// import ...;
/**
 * Component注解
 * @author ixx
 * @date 2019-06-20
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
     String value() default "";
}

這兩個注解都只有一個value的參數(shù)可配置慕淡,接收設(shè)置的Bean名稱背伴。

03、定義BeanObject對象

定義BeanObject對象作為Bean反射初始化的封裝,除了反射生成的bean對象外還保存一些類的其它信息挂据,方便后邊做依賴的時候使用以清,BeanObject.java:

/**
 * 封裝Bean對象
 * @author ixx
 * @date 2019-06-20
 */
public class BeanObject {
    /**
     * 類全名(帶包路徑)
     */
    private String className;
    /**
     * 類名
     */
    private String simpleName;
    /**
     * 實際對象
     */
    private Object object;
    /**
     * 包路徑(com.jisuye)
     */
    private String packages;
    /**
     * 注解類型集合
     */
    private Annotation[] annotaions;

    /**
     * 接口名
     */
    private Class[] interfacs;
    // ... getter and setter
}

04、Bean初始化工具類

初始化的Bean對象會放在一個HashMap的容器里崎逃,方便其它地方使用掷倔。

程序啟動時初始化Bean的工具類 BeansInitUtil.java(返回這個容器Map):

/**
 * Bean初始化類
 * @author ixx
 * @date 2019-06-20
 */
public class BeansInitUtil {
    private static final Logger log = LoggerFactory.getLogger(BeansInitUtil.class);

    public static Map<String, BeanObject> init(Class clazz){
        Map<String, BeanObject> beansMap = new HashMap<>();
        String path = clazz.getResource("").getPath();
        log.info("===bean init path:{}", path);
        File root = new File(path);
        initFile(root, beansMap);
        return beansMap;
    }

    private static void initFile(File file, Map<String, BeanObject> map){
        File[] fs = file.listFiles();
        for (File f : fs) {
            if(f.isDirectory()){
                // 遞歸目錄
                initFile(f, map);
            } else {
                // 處理class
                loadClass(f, map);
            }
        }
    }
    private static void loadClass(File file, Map<String, BeanObject> map){
        if(file == null){
            return;
        }
        try {
            BeanObject beanObject = new BeanObject();
            log.info("load bean path:{}", file.getPath());
            String path = file.getPath();
            path = path.substring(path.indexOf("classes")+8).replace(".class", "");
            path = path.replace("\\", ".");
            Class clzz = Class.forName(path);
            Annotation[] annotations = clzz.getAnnotations();
            if(annotations.length >0 && filterAnnotation(annotations)){
                beanObject.setAnnotaions(annotations);
                beanObject.setSimpleName(clzz.getSimpleName());
                beanObject.setClassName(clzz.getName());
                beanObject.setInterfacs(clzz.getInterfaces());
                beanObject.setPackages(clzz.getPackage().toString());
                Object obj = clzz.newInstance();
                beanObject.setObject(obj);
                // 按接口設(shè)置bean
                for (Class aClass : beanObject.getInterfacs()) {
                    map.put(aClass.getName(), beanObject);
                }
                // 按類設(shè)置bean
                map.put(beanObject.getClassName(), beanObject);
                // 按注解輸入value設(shè)置bean
                for (Annotation annotation : annotations) {
                    String tmp_name = "";
                    if(annotation instanceof Service){
                        ((Service)annotation).value();
                    } else if(annotation instanceof Component) {
                        ((Component) annotation).value();
                    }
                    if(tmp_name != null && !tmp_name.equals("")) {
                        map.put(tmp_name, beanObject);
                    }
                }
            }
        } catch (Exception e) {
            log.error("init bean error:{}", file.getPath(), e);
        }
    }
    private static boolean filterAnnotation(Annotation[] annotations){
        boolean b = false;
        for (Annotation annotation : annotations) {
            b = annotation instanceof Service || annotation instanceof Component;
        }
        return b;
    }
}

05、啟動測試

上邊的BeansInitUtil 工具類只實現(xiàn)了基本的个绍,反射生成bean勒葱,沒有考慮依賴的問題,先啟動測試一下巴柿,添加一個com.jisuye.service.Abc 以及com.jisuye.service.impl.AbcImpl 模擬一個service bean的初始化凛虽,AbcImpl.java:

package com.jisuye.service.impl;

import com.jisuye.annotations.Service;
import com.jisuye.service.Abc;

@Service
public class AbcImpl implements Abc {

    @Override
    public int test(String name) {
        return 0;
    }
}

在啟動方法 SquareApplication.run()方法里添加如下片段:

            //....
            // 輸出banner
            printBanner(classesPathUtil.getProjectPath());
            Map<String, BeanObject> map = BeansInitUtil.init(clzz);
            log.info("beans size is:{}", map.size());
            tomcat = new Tomcat();
            //....

運行T.main()后查看項目輸出:

......
19:04:29.850 [main] INFO com.jisuye.core.SquareApplication - beans size is:2
......

說明新添加的AbcImpl類已經(jīng)初始化成功。并放到了Bean容器里广恢。

ps: Abc和AbcImpl這兩個類我會暫時先提到git上凯旋,從本文開始我后邊的代碼將以單獨的分支提交,就不打tag了钉迷。

06至非、翻車時間

上邊把初始化Bean的基本功能實現(xiàn)了一下,但按照目前的實現(xiàn)有一個問題糠聪,如果接口有兩個實現(xiàn)類的話荒椭,那么初始化第二個實現(xiàn)類的時候會覆蓋掉第一個Bean在map里設(shè)置的值(key 是相同的接口名),要怎么處理呢舰蟆?

本篇代碼地址: https://github.com/iuv/square/tree/square3

本文作者: ixx
本文鏈接: http://jianpage.com/2019/06/26/square3/
版權(quán)聲明: 本作品采用 知識共享署名-非商業(yè)性使用-相同方式共享 4.0 國際許可協(xié)議 進行許可趣惠。轉(zhuǎn)載請注明出處!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末身害,一起剝皮案震驚了整個濱河市味悄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌题造,老刑警劉巖傍菇,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猾瘸,死亡現(xiàn)場離奇詭異界赔,居然都是意外死亡,警方通過查閱死者的電腦和手機牵触,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門淮悼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人揽思,你說我怎么就攤上這事袜腥。” “怎么了钉汗?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵羹令,是天一觀的道長鲤屡。 經(jīng)常有香客問我,道長福侈,這世上最難降的妖魔是什么酒来? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮肪凛,結(jié)果婚禮上堰汉,老公的妹妹穿的比我還像新娘。我一直安慰自己伟墙,他們只是感情好翘鸭,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著戳葵,像睡著了一般就乓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拱烁,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天档址,我揣著相機與錄音,去河邊找鬼邻梆。 笑死守伸,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的浦妄。 我是一名探鬼主播尼摹,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼剂娄!你這毒婦竟也來了蠢涝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤阅懦,失蹤者是張志新(化名)和其女友劉穎和二,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耳胎,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡惯吕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了怕午。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片废登。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖郁惜,靈堂內(nèi)的尸體忽然破棺而出堡距,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布羽戒,位于F島的核電站缤沦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏易稠。R本人自食惡果不足惜疚俱,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缩多。 院中可真熱鬧呆奕,春花似錦、人聲如沸衬吆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逊抡。三九已至姆泻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冒嫡,已是汗流浹背拇勃。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留孝凌,地道東北人方咆。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像蟀架,于是被迫代替她去往敵國和親瓣赂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355

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