- <strong>
- <strong>
- <strong>
- <strong>
- <strong>
- <strong>
在Spring中IOC是个绝佳的解耦合手段,为了更好的理解我就动手自己写了一个
预备知识:
注解,反射,集合类,lambda表达式,流式API
IOC
如何把一个类注册进去呢?首先我们要让容器“发现”它,所以使用注解,声明它应当加入容器
其中的value即对应的是Spring中的Bean name
<code>@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})public @interface Part { String value() default "";}/<code>
扫描包生成类的工具
当然,有人会说hutool的ClassScaner很好用,但是这里为了加深理解,我就自己写一个
思路就是利用文件名利用Class.forName()得到类的反射再生成实例
<code>public static List<object> find(String packName, ClassFilter classFilter) throws IOException { //获取当前路径 Enumerationentity = Thread.currentThread().getContextClassLoader().getResources(packName); HashSet<string> classPaths = new HashSet<>(); ArrayList<object> classes = new ArrayList<>(); //拿到处理后的路径,处理前为/..../target/classes //处理后为/..../target/classes if (entity.hasMoreElements()) { String path = entity.nextElement().getPath().substring(1); classPaths.add(path); } //这里跳转到我写的一个把路径下的.class文件生成为类名的方法,后面会讲述 //set的元素为类名 比如Entity.Student Set<string> set = loadClassName(classPaths); for (String s : set) { try { Class> c = Class.forName(s); //利用过滤器判断需不需要生成实例 if (classFilter.test(c)){ //这里为了简单使用无参构造器 Constructor> constructor = c.getConstructor(); constructor.setAccessible(true); //将生成的实例加入返回的list集合中 classes.add(constructor.newInstance()); } }catch (ClassNotFoundException| InstantiationException | IllegalAccessException| InvocationTargetException e) { throw new RuntimeException(e); }catch (NoSuchMethodException e){ System.err.println(e.getMessage()); } } return classes; }/<string>/<object>/<string> /<object>/<code>
到来其中的一个核心函数loadClassName
<code>/** * @param classPaths 路径名集合 * @return 类名的集合 */ private static Set<string> loadClassName(HashSet<string> classPaths){ Queue<file> queue = new LinkedList<>(); HashSet<string> classNames = new HashSet<>(); //对每一个路径得到对应所有以.class结尾的文件 classPaths.forEach(p -> { //迭代的方法,树的层次遍历 queue.offer(new File(p)); while (!queue.isEmpty()){ File file = queue.poll(); if (file.isDirectory()) { File[] files = file.listFiles(); for (File file1 : files) { queue.offer(file1); } }else if(file.getName().endsWith(".class")){ //对文件名处理得到类名 // ..../target/classes处理完为 \\....\\target\\classes String replace = p.replace("/", "\\\"); //对于每个.class文件都是以....\\target\\classes开头,去掉开头,去掉后缀就是类名了 String className = file.getPath() .replace(replace, "") .replace(".class", "").replace("\\\", "."); classNames.add(className); } } }); return classNames; }/<string>/<file>/<string>/<string>/<code>
好了,现在就可以扫描包了
上面我也提到了不是所有的类都必须放到容器中,现在让我们看看这个 ClassFilter 过滤器是什么东西吧
<code>@FunctionalInterfacepublic interface ClassFilter{ boolean test(Class c);}/<code>
是个函数式接口,这就意味着使用lambda表达式会很方便
通过这个接口我们就很容易地构造这么一个函数帮我们把所有有@Part注解的类生成好
<code>public staticList<object> findByAnnotation(String packName, Class /<code>annotation) throws IOException{ if (!annotation.isAnnotation()) { throw new RuntimeException("it not an annotation"+annotation.getTypeName()); } ClassFilter classFilter =(c) -> c.getAnnotation(annotation) != null; return find(packName, classFilter); } /<object>
IOC容器
上面的准备工作做的差不多了
该动手写IOC容器了
思考一下在Spring中我们很容易通过bean name得到java bean,所以使用一个Map<string>可以模拟一下。/<string>
这里我们在IOCContainer中添加一个变量
<code>private Map<string> context;/<string>/<code>
构造函数
<code>public IOCContainer(String packName){ try { init(packName); } catch (IOException e) { e.printStackTrace(); } } public IOCContainer(){ //默认扫描所有的包 this(""); }/<code>
初始化函数:
<code> /** * @param packName 路径名在ClassScannerUtil中的函数要使用 * @throws IOException * @author dreamlike_ocean */ public void init(String packName) throws IOException { //做一个bean name 的映射。如果@Part注解中的值不为空则使用value的值做bean name //如果为空就用这个 java bean的类名做bean name Function<object> keyMapper = (o) -> { Class> aClass = o.getClass(); String s = aClass.getAnnotation(Part.class).value(); if (s.isBlank()) { return o.getClass().getTypeName(); } return s; }; context = new HashMap<string>(); //获取所有添加@Part注解的类实例 List<object> objectList = ClassScannerUtil.findByAnnotation(packName, Part.class); //先把自己注入进去 context.put("IOCContainer", this); for (Object o : objectList) { //利用上面写好的映射函数接口 获取bean name String beanName = keyMapper.apply(o); //bean name冲突情况,直接报错 if (context.containsKey(beanName)) { String msg = new StringBuilder().append("duplicate bean name: ") .append(beanName) .append("in") .append(o.getClass()) .append(" and ") .append(context.get(beanName).getClass()).toString(); throw new RuntimeException(msg); } //加入容器 context.put(beanName, o); } //帮助垃圾回收,这个复杂度为O(n),理论上objectList = null也能帮助回收 objectList.clear(); }/<object>/<string>/<object>/<code>
对外暴露的获取Bean的api
<code> /** * * @param beanName * @return 记得判断空指针 * @author dreamlike_ocean */ public Optional<object> getBean(String beanName){ return Optional.ofNullable(context.get(beanName)); } /** * * @param beanName * @param aclass * @param需要返回的类型,类型强转 * @exception ClassCastException 类型强转可能导致无法转化的异常 * @return @author dreamlike_ocean */ public /<object>/<code>Optional getBean(String beanName,Class aclass){ return Optional.ofNullable((T)context.get(beanName)); } /** * * @param interfaceType * @param * @return 所有继承这个接口的集合 * @author dreamlike_ocean */ public List getBeanByInterfaceType(Class interfaceType){ if (!interfaceType.isInterface()) { throw new RuntimeException("it is not an interface type:"+interfaceType.getTypeName()); } return context.values().stream() .filter(o -> interfaceType.isAssignableFrom(o.getClass())) .map(o -> (T)o) .collect(Collectors.toList()); } /** * * @param type * @param * @return 所有这个类型的集合 * @author dreamlike_ocean */ public List getBeanByType(Class type){ return context.values().stream() .filter(o -> type.isAssignableFrom(o.getClass())) .map(o -> (T)o) .collect(Collectors.toList()); } /** * * @return 获取所有值 * @author dreamlike_ocean */ public Collection<object> getBeans(){ return context.values(); } /** * * @return 获取容器 * @author dreamlike_ocean */ public Map<string> getContext(){ return context; }/<string>/<object>
DI
上面我们获取的都是利用无参的构造函数得到的java bean,这和想的差的有点远,我想要的是一幅画,他却给了我一张白纸。这怎么能行!DI模块上,给他整个活!
为了区别通过类型注入还是名称注入,我写了两个注解用于区分
<code>@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)public @interface InjectByName { String value();}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD,ElementType.METHOD})public @interface InjectByType {}/<code>
首先DI先必须知道到底对哪个容器注入,所以通过构造函数传入一个
<code> private IOCContainer iocContainer; public DI(IOCContainer iocContainer) { Objects.requireNonNull(iocContainer); this.iocContainer = iocContainer; }/<code>
先是对字段的按类型注入
<code>/** * * @param o 需要被注入的类 * @author dreamlike_ocean */ private void InjectFieldByType(Object o){ try { //获取内部所有字段 Field[] declaredFields = o.getClass().getDeclaredFields(); for (Field field : declaredFields) { //判断当前字段是否有注解标识 if (field.getAnnotation(InjectByType.class) != null) { //防止因为private而抛出异常 field.setAccessible(true); List list = iocContainer.getBeanByType(field.getType()); //如果找不到,那么注入失败 //这里我选择抛出异常,也可给他赋值为null if(list.size() == 0){ throw new RuntimeException("not find "+field.getType()); } //多于一个也注入失败,和Spring一致 if (list.size()!=1){ throw new RuntimeException("too many"); } //正常注入 field.set(o, list.get(0)); } } } catch (IllegalAccessException e) { e.printStackTrace(); } }/<code>
对字段按名称注入
<code> /** * * @param o 需要被注入的类 * @author dreamlike_ocean */ private void InjectFieldByName(Object o){ try { Field[] declaredFields = o.getClass().getDeclaredFields(); for (Field field : declaredFields) { InjectByName annotation = field.getAnnotation(InjectByName.class); if (annotation != null) { field.setAccessible(true); //通过注解中的bean name寻找注入的值 //这里optional类没有发挥它自己的函数式优势,因为我觉得在lambda表达式里面写异常处理属实不好看 //借用在Stack overflow看的一句话,Oracle用受检异常把lambda玩砸了 Object v = iocContainer.getBean(annotation.value()).get(); if (v != null) { field.set(o, v); }else{ //同样找不到就抛异常 throw new RuntimeException("not find "+field.getType()); } } } } catch (IllegalAccessException e) { e.printStackTrace(); } }/<code>
对函数按类型注入
<code> /** * 这个函数必须是setter函数 * @param o 要被注入的类 * @author dreamlike_ocean */ private void InjectMethod(Object o){ Method[] declaredMethods = o.getClass().getDeclaredMethods(); try { for (Method method : declaredMethods) { //获取添加注解的函数 if (method.getAnnotation(InjectByType.class) != null) { //获取参数列表 Class>[] parameterTypes = method.getParameterTypes(); method.setAccessible(true); int i = method.getParameterCount(); //为储存实参做准备 Object[] param = new Object[i]; //变量重用,现在它代表当前下标了 i=0; for (Class> parameterType : parameterTypes) { List> list = iocContainer.getBeanByType(parameterType); if(list.size() == 0){ throw new RuntimeException("not find "+parameterType+"。method :"+method+"class:"+o.getClass()); } if (list.size()!=1){ throw new RuntimeException("too many"); } //暂时存储实参 param[i++] = list.get(0); } //调用对应实例的函数 method.invoke(o, param); } } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }/<code>
你会发现上面都是私有方法,因为我想对外暴露一个简洁的API
<code> /** * 对字段依次进行按类型注入和按名称注入 * 再对setter方法注入 * @author dreamlike_ocean */ public void inject(){ iocContainer.getBeans().forEach(o -> { InjectFieldByType(o); InjectFieldByName(o); InjectMethod(o); }); }/<code>
测试
做好了,来让我们测一测
<code>@Part("testA")class A{ @InjectByType private B b; public A(){ } public B getB() { return b; }}@Partclass B{ private UUID uuid;public B(){ uuid = UUID.randomUUID();} public UUID getUuid() { return uuid; }}@Partclass C{ public C(){ }}/<code>
测试方法
<code>@Testpublic void test(){ IOCContainer container = new IOCContainer(); DI di = new DI(container); di.inject(); System.out.println(container.getBeanByType(A.class).get(0).getB().getUuid()); System.out.println(container.getBeanByType(B.class).get(0).getUuid());}/<code>
好了这就可以了
作者:dreamlike
原文链接:https://juejin.im/post/5e561077518825492c0504fd
閱讀更多 追逐仰望星空 的文章