diff --git a/.gitee/ISSUE_TEMPLATE.zh-CN.md b/.gitee/ISSUE_TEMPLATE.zh-CN.md deleted file mode 100644 index f09d98dde9597de75ffcdb237c2b580b8fffa3f9..0000000000000000000000000000000000000000 --- a/.gitee/ISSUE_TEMPLATE.zh-CN.md +++ /dev/null @@ -1,13 +0,0 @@ -### 该问题是怎么引起的? - - - -### 重现步骤 - - - -### 报错信息 - - - - diff --git a/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md b/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md deleted file mode 100644 index 66d4332058d27e3c8ef94919138576d71b524467..0000000000000000000000000000000000000000 --- a/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md +++ /dev/null @@ -1,14 +0,0 @@ -### 相关的Issue - - -### 原因(目的、解决的问题等) - - -### 描述(做了什么,变更了什么) - - -### 测试用例(新增、改动、可能影响的功能) - - - - diff --git a/.gitignore b/.gitignore index 5d947ca8879f8a9072fe485c566204e3c2929e80..0107921ca5d8692597f061b1ca57e14a171717af 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,8 @@ bin-release/ *.air *.ipa *.apk - +.idea/* +target/* # Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` # should NOT be excluded as they contain compiler settings and other important # information for Eclipse / Flash Builder. diff --git a/README.md b/README.md index af9809c147a94fd3fe85c66335fb1a6e4d7253c8..119fcc7d395d5ff74736f4dcaebf1956312a44f4 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,14 @@ 实现类似于Spring的容器管理工具,因为项目中不需要使用很重的Spring,而右希望能够拥有它的IOC,DI #### 软件架构 -软件架构说明 - - +@Autowired 注入默认按照类型注入,name属性赋值则严格按照名称注入,没有则不注入 +@bean 类似于Spring的@Bean +@Component 配置类和需要被容器托管的类必须写 +@Note 目前仅用于注释 +@BeanScanner 包扫描注解 +ContextBean.java 容器对bean的封装 +ContextHolder.java 容器 +ContextRun.java 容器启动器,只有调用其中的run方法,容器才能够正常启动 #### 安装教程 1. xxxx @@ -14,10 +19,18 @@ 3. xxxx #### 使用说明 - -1. xxxx -2. xxxx -3. xxxx +```java +/* 扫描bean的必备注解 */ +@BeanScanner(path = "org.needcoke.ioc") +public class Main { + + public static void main(String[] args) { + /* 一行代码轻松获取上下文 */ + ContextHolder context = ContextRun.run(Main.class, args); + + } +} +``` #### 参与贡献 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..fe4513d069554373ade10cf4cbc2470beb433978 --- /dev/null +++ b/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + groupId + needcoke-ioc + 1.0-SNAPSHOT + + + 8 + 8 + + + + + cn.hutool + hutool-all + 5.5.1 + + + \ No newline at end of file diff --git a/src/main/java/org/needcoke/ioc/ContextRun.java b/src/main/java/org/needcoke/ioc/ContextRun.java new file mode 100644 index 0000000000000000000000000000000000000000..839fbee104de685fa14de787936e94cda1480654 --- /dev/null +++ b/src/main/java/org/needcoke/ioc/ContextRun.java @@ -0,0 +1,114 @@ +package org.needcoke.ioc; + +import cn.hutool.core.util.StrUtil; +import org.needcoke.ioc.annotation.Autowired; +import org.needcoke.ioc.annotation.Bean; +import org.needcoke.ioc.annotation.BeanScanner; +import org.needcoke.ioc.annotation.Component; +import org.needcoke.ioc.core.ContextBean; +import org.needcoke.ioc.core.ContextHolder; +import org.needcoke.ioc.util.MethodUtil; +import org.needcoke.ioc.util.PackageUtil; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Set; + +public class ContextRun { + + private static Object init(Class clz) { + try { + return clz.newInstance(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + public static ContextHolder run(Class t, String[] args) { + ContextHolder holder = new ContextHolder(); + BeanScanner annotation = t.getAnnotation(BeanScanner.class); + if (null != annotation) { + String[] paths = annotation.path(); + boolean recursion = annotation.recursion();/* 是否递归加载类 */ + for (String path : paths) { + /* 获取包路径下的所有类 */ + Set> clzFromPkg = PackageUtil.getClzFromPkg(path); + for (Class aClass : clzFromPkg) { + if (aClass.getAnnotation(Component.class) != null) { + Object component = init(aClass); + Method[] methods = aClass.getDeclaredMethods(); + for (Method method : methods) { + Bean methodBean = method.getAnnotation(Bean.class); + if (null != methodBean) { + + if (holder.nameBeanContext.containsKey(methodBean.name())) { + throw new RuntimeException("already has a bean name #" + methodBean.name() + "#"); + } else { + /* 通过反射执行方法,并获取反射值,注解有声明名称则用注解,注解无则使用方法名,存入context */ + ContextBean beanFromMethod = MethodUtil.runMethod(method, component, holder); + if (StrUtil.isBlank(methodBean.name())) { + beanFromMethod.name = method.getName(); + } + holder.putBean(beanFromMethod); + } + + } + } + ContextBean componentBean = new ContextBean(); + String componentName = aClass.getAnnotation(Component.class).name(); + if(StrUtil.isBlank(componentName)){ + componentName = aClass.getSimpleName(); + } + componentBean.name = componentName; + componentBean.clz = aClass; + componentBean.value = component; + holder.putComponent(componentBean); + } + } + + + } + + Set> componentClasses = holder.componentMap.keySet(); + for (Class componentClass : componentClasses) { + Object component = holder.componentMap.get(componentClass); + Field[] fields = componentClass.getDeclaredFields(); + for (Field field : fields) { + Autowired autowiredAnnotation = field.getAnnotation(Autowired.class); + if(null != autowiredAnnotation){ + ContextBean beanAutowired = null; + /* Autowired的name属性不为空的话,就通过名称注入 */ + if(StrUtil.isNotBlank(autowiredAnnotation.name())){ + beanAutowired = holder.nameBeanContext.get(autowiredAnnotation.name()); + if(null != beanAutowired && beanAutowired.clz.equals(field.getType())) { + try { + field.setAccessible(true); + field.set(component, beanAutowired.value); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + else{ + /* 名称为空,则通过类型来匹配 */ + if(holder.typeBeanContext.containsKey(field.getType())){ + beanAutowired = holder.typeBeanContext.get(field.getType()).get(0); + try { + field.setAccessible(true); + field.set(component, beanAutowired.value); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + } + } + } + } + } + return holder; + + } +} diff --git a/src/main/java/org/needcoke/ioc/annotation/Autowired.java b/src/main/java/org/needcoke/ioc/annotation/Autowired.java new file mode 100644 index 0000000000000000000000000000000000000000..8780e4dfb59abd9781004fb197bf465898a83257 --- /dev/null +++ b/src/main/java/org/needcoke/ioc/annotation/Autowired.java @@ -0,0 +1,12 @@ +package org.needcoke.ioc.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Autowired { + + @Note("bean的名称") + String name() default ""; +} diff --git a/src/main/java/org/needcoke/ioc/annotation/Bean.java b/src/main/java/org/needcoke/ioc/annotation/Bean.java new file mode 100644 index 0000000000000000000000000000000000000000..a57c2af6971dc5d569284e5dc11da13cd3bc0ccd --- /dev/null +++ b/src/main/java/org/needcoke/ioc/annotation/Bean.java @@ -0,0 +1,11 @@ +package org.needcoke.ioc.annotation; + +import java.lang.annotation.*; + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Bean { + @Note("名称") + String name() default ""; +} diff --git a/src/main/java/org/needcoke/ioc/annotation/BeanScanner.java b/src/main/java/org/needcoke/ioc/annotation/BeanScanner.java new file mode 100644 index 0000000000000000000000000000000000000000..a627df367480678fcbd47e65f0409672ddc62555 --- /dev/null +++ b/src/main/java/org/needcoke/ioc/annotation/BeanScanner.java @@ -0,0 +1,17 @@ +package org.needcoke.ioc.annotation; + +import java.lang.annotation.*; + +/** + * 项目启动时bean的扫描器 + **/ +@Target(ElementType.TYPE_USE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface BeanScanner { + + @Note("扫描的包路径") + String[] path() default {}; + + boolean recursion() default false; +} diff --git a/src/main/java/org/needcoke/ioc/annotation/Component.java b/src/main/java/org/needcoke/ioc/annotation/Component.java new file mode 100644 index 0000000000000000000000000000000000000000..a6e815ff4b494b58baf8fa1bd1e123f61395d179 --- /dev/null +++ b/src/main/java/org/needcoke/ioc/annotation/Component.java @@ -0,0 +1,11 @@ +package org.needcoke.ioc.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE_USE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Component { + @Note("名称") + String name() default ""; +} diff --git a/src/main/java/org/needcoke/ioc/annotation/Note.java b/src/main/java/org/needcoke/ioc/annotation/Note.java new file mode 100644 index 0000000000000000000000000000000000000000..3821018923f99ff83a02665773e3e908e857b8e3 --- /dev/null +++ b/src/main/java/org/needcoke/ioc/annotation/Note.java @@ -0,0 +1,17 @@ +package org.needcoke.ioc.annotation; + +import java.lang.annotation.*; + +/** + * 用于做注释 + * + * @author Gilgamesh + * @since 0.0.1start + **/ +@Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Note { + + String value() default ""; +} diff --git a/src/main/java/org/needcoke/ioc/app/MainApp/Main.java b/src/main/java/org/needcoke/ioc/app/MainApp/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..25c100840395cbe835604c107340602e0379646b --- /dev/null +++ b/src/main/java/org/needcoke/ioc/app/MainApp/Main.java @@ -0,0 +1,20 @@ +package org.needcoke.ioc.app.MainApp; + +import org.needcoke.ioc.ContextRun; +import org.needcoke.ioc.annotation.BeanScanner; +import org.needcoke.ioc.core.ContextHolder; +import org.needcoke.ioc.test.A002; +import java.util.List; + +@BeanScanner(path = "org.needcoke.ioc") +public class Main { + + public static void main(String[] args) { + ContextHolder run = ContextRun.run(Main.class, args); + List list = run.getBean(A002.class); + + for (A002 a002 : list) { + a002.print(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/needcoke/ioc/core/ContextBean.java b/src/main/java/org/needcoke/ioc/core/ContextBean.java new file mode 100644 index 0000000000000000000000000000000000000000..9a2e7058e445a51adaeb46c8617fe861a31d6665 --- /dev/null +++ b/src/main/java/org/needcoke/ioc/core/ContextBean.java @@ -0,0 +1,27 @@ +package org.needcoke.ioc.core; + + +public class ContextBean { + + public Class clz; + + public Object value; + + public String name; + + public String id; + + public E getValue(){ + return (E) value; + } + + @Override + public String toString() { + return "ContextBean{" + + "clz=" + clz + + ", value=" + value + + ", name='" + name + '\'' + + ", id='" + id + '\'' + + '}'; + } +} diff --git a/src/main/java/org/needcoke/ioc/core/ContextHolder.java b/src/main/java/org/needcoke/ioc/core/ContextHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..189d1e640bd4b27e24442b500aa8f071313dc047 --- /dev/null +++ b/src/main/java/org/needcoke/ioc/core/ContextHolder.java @@ -0,0 +1,90 @@ +package org.needcoke.ioc.core; + +import cn.hutool.core.util.StrUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +public class ContextHolder { + + /** + * id计数器 + **/ + private AtomicLong idCounter = new AtomicLong(0); + + /** + * 所有的component组件 + **/ + public Map, Object> componentMap = new ConcurrentHashMap<>(); + + /** + * ioc容器的id索引 + **/ + public Map idBeanMap = new ConcurrentHashMap<>(); + /** + * ioc容器的名称索引 + **/ + public Map nameBeanContext = new ConcurrentHashMap<>(); + + /** + * ioc容器的类型索引 + **/ + public Map, List> typeBeanContext = new ConcurrentHashMap<>(); + + public List getBean(Class type) { + List ts = new ArrayList<>(); + List contextBeans = typeBeanContext.get(type); + for (ContextBean contextBean : contextBeans) { + ts.add((T) contextBean.value); + } + return ts; + } + + public T getBean(String name) { + return (T) nameBeanContext.get(name).value; + } + + public T getBeanById(String id) { + return (T) idBeanMap.get(id).value; + } + + public void putBean(ContextBean bean) { + if (StrUtil.isNotBlank(bean.name)) { + String name = bean.name; + int i = 1; + String suffix = ""; + while (nameBeanContext.containsKey(name)) { + StrUtil.removeSuffix(name, suffix); + name += i++; + } + bean.name = name; + nameBeanContext.put(name, bean); + if (typeBeanContext.containsKey(bean.clz)) { + typeBeanContext.get(bean.clz).add(bean); + } else { + List beans = new ArrayList<>(); + beans.add(bean); + typeBeanContext.put(bean.clz, beans); + } + if (StrUtil.isBlank(bean.id) || idBeanMap.containsKey(bean.id)) { + bean.id = applyForId().toString(); + } + idBeanMap.put(bean.id, bean); + } + } + + /** + * 向id计数器申请一个id + **/ + private Long applyForId() { + return idCounter.addAndGet(1); + } + + public void putComponent(ContextBean bean) { + componentMap.put(bean.clz, bean.value); + putBean(bean); + } + +} diff --git a/src/main/java/org/needcoke/ioc/test/A002.java b/src/main/java/org/needcoke/ioc/test/A002.java new file mode 100644 index 0000000000000000000000000000000000000000..9d14ad176bce30563532e392df0e2edfd3bf6444 --- /dev/null +++ b/src/main/java/org/needcoke/ioc/test/A002.java @@ -0,0 +1,19 @@ +package org.needcoke.ioc.test; + +import org.needcoke.ioc.annotation.Autowired; +import org.needcoke.ioc.annotation.Component; + +import java.util.Map; + +@Component +public class A002 { + + @Autowired(name = "vvsf") + private Map map; + + public void print(){ + for (String s : map.keySet()) { + System.out.println("s:"+s+"->"+map.get(s)); + } + } +} diff --git a/src/main/java/org/needcoke/ioc/test/A01.java b/src/main/java/org/needcoke/ioc/test/A01.java new file mode 100644 index 0000000000000000000000000000000000000000..28beb8551a4b890fe1e9d1d0c354f5272a4fd2a7 --- /dev/null +++ b/src/main/java/org/needcoke/ioc/test/A01.java @@ -0,0 +1,24 @@ +package org.needcoke.ioc.test; + +import org.needcoke.ioc.annotation.Bean; +import org.needcoke.ioc.annotation.Component; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Component(name = "DDS01") +public class A01 { + + @Bean + public Map map233() { + Map map = new ConcurrentHashMap<>(); + map.put("abc","222"); + return map; + } + + @Bean + public Map vvsf(){ + Map map = new ConcurrentHashMap<>(); + map.put("awerwrqwrbc","wefrgerghtrgh"); + return map; + } +} diff --git a/src/main/java/org/needcoke/ioc/util/MethodUtil.java b/src/main/java/org/needcoke/ioc/util/MethodUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..fc51c755660f5a345fac0c0b98ec63564130f50d --- /dev/null +++ b/src/main/java/org/needcoke/ioc/util/MethodUtil.java @@ -0,0 +1,42 @@ +package org.needcoke.ioc.util; + +import org.needcoke.ioc.core.ContextBean; +import org.needcoke.ioc.core.ContextHolder; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class MethodUtil { + + /** + * 在反射中,未知参数列表调用函数获取返回值 + * + * @param method 方法 + * @param obj 调用方法的对象 + * @param holder ioc上下文 + * @author Gilgamesh + * @since 0.0.1start + **/ + public static ContextBean runMethod(Method method, Object obj, ContextHolder holder) { + ContextBean bean = new ContextBean(); + bean.name = method.getName(); + bean.clz = method.getReturnType(); + Class[] parameterTypes = method.getParameterTypes(); + List params = new ArrayList<>(); + for (Class type : parameterTypes) { + if (holder.typeBeanContext.containsKey(type)) { + params.add(holder.typeBeanContext.get(type).get(0)); + } + } + try { + bean.value = method.invoke(obj, params.toArray()); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return bean; + } +} diff --git a/src/main/java/org/needcoke/ioc/util/PackageUtil.java b/src/main/java/org/needcoke/ioc/util/PackageUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..b7beae8dfe153caa63f459216f1c93658de692ac --- /dev/null +++ b/src/main/java/org/needcoke/ioc/util/PackageUtil.java @@ -0,0 +1,142 @@ +package org.needcoke.ioc.util; + +import java.io.File; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class PackageUtil { + + /** + * 扫描包路径下所有的class文件 + * + * @param pkg + * @return + */ + public static Set> getClzFromPkg(String pkg) { + Set> classes = new LinkedHashSet<>(); + + String pkgDirName = pkg.replace('.', '/'); + try { + Enumeration urls = PackageUtil.class.getClassLoader().getResources(pkgDirName); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + String protocol = url.getProtocol(); + if ("file".equals(protocol)) {// 如果是以文件的形式保存在服务器上 + String filePath = URLDecoder.decode(url.getFile(), "UTF-8");// 获取包的物理路径 + findClassesByFile(pkg, filePath, classes); + } else if ("jar".equals(protocol)) {// 如果是jar包文件 + JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile(); + findClassesByJar(pkg, jar, classes); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + return classes; + } + + + /** + * 扫描包路径下的所有class文件 + * + * @param pkgName 包名 + * @param pkgPath 包对应的绝对地址 + * @param classes 保存包路径下class的集合 + */ + private static void findClassesByFile(String pkgName, String pkgPath, Set> classes) { + File dir = new File(pkgPath); + if (!dir.exists() || !dir.isDirectory()) { + return; + } + + + // 过滤获取目录,or class文件 + File[] dirfiles = dir.listFiles(pathname -> pathname.isDirectory() || pathname.getName().endsWith("class")); + + + if (dirfiles == null || dirfiles.length == 0) { + return; + } + + + String className; + Class clz; + for (File f : dirfiles) { + if (f.isDirectory()) { + findClassesByFile(pkgName + "." + f.getName(), + pkgPath + "/" + f.getName(), + classes); + continue; + } + + + // 获取类名,干掉 ".class" 后缀 + className = f.getName(); + className = className.substring(0, className.length() - 6); + + // 加载类 + clz = loadClass(pkgName + "." + className); + if (clz != null) { + classes.add(clz); + } + } + } + + + /** + * 扫描包路径下的所有class文件 + * + * @param pkgName 包名 + * @param jar jar文件 + * @param classes 保存包路径下class的集合 + */ + private static void findClassesByJar(String pkgName, JarFile jar, Set> classes) { + String pkgDir = pkgName.replace(".", "/"); + + + Enumeration entry = jar.entries(); + + JarEntry jarEntry; + String name, className; + Class claze; + while (entry.hasMoreElements()) { + jarEntry = entry.nextElement(); + + name = jarEntry.getName(); + if (name.charAt(0) == '/') { + name = name.substring(1); + } + + + if (jarEntry.isDirectory() || !name.startsWith(pkgDir) || !name.endsWith(".class")) { + // 非指定包路径, 非class文件 + continue; + } + + + // 去掉后面的".class", 将路径转为package格式 + className = name.substring(0, name.length() - 6); + claze = loadClass(className.replace("/", ".")); + if (claze != null) { + classes.add(claze); + } + } + } + + + private static Class loadClass(String fullClzName) { + try { + return Thread.currentThread().getContextClassLoader().loadClass(fullClzName); + } catch (ClassNotFoundException e) { + } + return null; + } +}