造一个方形轮子文章目录:造一个方形的轮子
01、先把车正过来
在上一篇《造一个方形的轮子2--添加配置》的最后又翻车了,现在先把发现的这个问题解决,将square项目的包引用其它项目无法启动的主要原因是没有正确的指定tomcat运行的关键目录,以及读取资源的目录。
也就是说把设置的目录结构调整一下就可以了。
添加一个配置目录的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;
/**
* 静态资源目录(.../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 目录的话就创建一个,设置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 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());
// 初始化参数
setArgs(args);
// 输出banner
printBanner(classesPathUtil.getProjectPath());
tomcat = new Tomcat();
// 设置Tomcat工作目录
tomcat.setBaseDir(classesPathUtil.getProjectPath() + "/Tomcat");
tomcat.setPort(TOMCAT_PORT);
tomcat.addWebapp(CONTEXT_PATH, classesPathUtil.getPublicPath());
// 执行这句才能支持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);
}
}
/**
* 初始化参数
* @param args
*/
private static void setArgs(String[] args){
Map 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项目根目录下执行mvn clean install
然后到car项目里去启动项目就可以了。
02、添加注解
实现控制反转的思路,
1、要有自己定义的注解,标识类需要初始化到容器中
2、程序启动时调用初始化方法(默认从启动类目录向下扫描所有包里的类文件)
3、根据接口名称、类名称及添加注解时定义的bean名称初始化到容器中
4、处理Bean互相之间的依赖关系(属于依赖注入)
这里我先只实现一个@Service
和@Component
两个注解,对外提供服务的@Controller
后边实现对外Rest接口的时候再实现。
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的参数可配置,接收设置的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 init(Class clazz){
Map 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 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 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);
// 按接口设置bean
for (Class aClass : beanObject.getInterfacs()) {
map.put(aClass.getName(), beanObject);
}
// 按类设置bean
map.put(beanObject.getClassName(), beanObject);
// 按注解输入value设置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 工具类只实现了基本的,反射生成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 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类已经初始化成功。并放到了Bean容器里。
ps: Abc和AbcImpl这两个类我会暂时先提到git上,从本文开始我后边的代码将以单独的分支提交,就不打tag了。
06、翻车时间
上边把初始化Bean的基本功能实现了一下,但按照目前的实现有一个问题,如果接口有两个实现类的话,那么初始化第二个实现类的时候会覆盖掉第一个Bean在map里设置的值(key
是相同的接口名),要怎么处理呢?
本篇代码地址: https://github.com/iuv/square/tree/square3
本文作者: ixx
本文链接: http://jianpage.com/2019/06/26/square3/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!