造一個方形輪子文章目錄:造一個方形的輪子
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)載請注明出處!