Java手写IOC实现过程
参考:手写IOC实现过程
手写ioc前基础知识
什么是IOC(Inversion of Control 控制反转)
IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
什么是DI(Dependency Injection 依赖注入)
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
IOC和DI什么关系
oC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
什么是依赖
传统应用程序设计中所说的依赖一般指“类之间的关系”,那先让我们复习一下类之间的关系:
泛化:表示类与类之间的继承关系、接口与接口之间的继承关系;
实现:表示类对接口的实现;
依赖:当类与类之间有使用关系时就属于依赖关系,不同于关联关系,依赖不具有“拥有关系”,而是一种“相识关系”,只在某个特定地方(比如某个方法体内)才有关系。
关联:表示类与类或类与接口之间的依赖关系,表现为“拥有关系”;具体到代码可以用实例变量来表示;
聚合:属于是关联的特殊情况,体现部分-整体关系,是一种弱拥有关系;整体和部分可以有不一样的生命周期;是一种弱关联;
组合:属于是关联的特殊情况,也体现了体现部分-整体关系,是一种强“拥有关系”;整体与部分有相同的生命周期,是一种强关联;
Spring IoC容器的依赖有两层含义:Bean依赖容器和容器注入Bean的依赖资源:
Bean依赖容器:也就是说Bean要依赖于容器,这里的依赖是指容器负责创建Bean并管理Bean的生命周期,正是由于由容器来控制创建Bean并注入依赖,也就是控制权被反转了,这也正是IoC名字的由来,此处的有依赖是指Bean和容器之间的依赖关系。
容器注入Bean的依赖资源:容器负责注入Bean的依赖资源,依赖资源可以是Bean、外部文件、常量数据等,在Java中都反映为对象,并且由容器负责组装Bean之间的依赖关系,此处的依赖是指Bean之间的依赖关系,可以认为是传统类与类之间的“关联”、“聚合”、“组合”关系。
依赖注入的好处
动态替换Bean依赖对象,程序更灵活:替换Bean依赖对象,无需修改源文件:应用依赖注入后,由于可以采用配置文件方式实现,从而能随时动态的替换Bean的依赖对象,无需修改java源文件;
更好实践面向接口编程,代码更清晰:在Bean中只需指定依赖对象的接口,接口定义依赖对象完成的功能,通过容器注入依赖实现;
更好实践优先使用对象组合,而不是类继承:因为IoC容器采用注入依赖,也就是组合对象,从而更好的实践对象组合。
采用对象组合,Bean的功能可能由几个依赖Bean的功能组合而成,其Bean本身可能只提供少许功能或根本无任何功能,全部委托给依赖Bean,对象组合具有动态性,能更方便的替换掉依赖Bean,从而改变Bean功能;
而如果采用类继承,Bean没有依赖Bean,而是采用继承方式添加新功能,,而且功能是在编译时就确定了,不具有动态性,而且采用类继承导致Bean与子Bean之间高度耦合,难以复用。
增加Bean可复用性:依赖于对象组合,Bean更可复用且复用更简单;
降低Bean之间耦合:由于我们完全采用面向接口编程,在代码中没有直接引用Bean依赖实现,全部引用接口,而且不会出现显示的创建依赖对象代码,而且这些依赖是由容器来注入,很容易替换依赖实现类,从而降低Bean与依赖之间耦合;
代码结构更清晰:要应用依赖注入,代码结构要按照规约方式进行书写,从而更好的应用一些最佳实践,因此代码结构更清晰。
从以上我们可以看出,其实依赖注入只是一种装配对象的手段,设计的类结构才是基础,如果设计的类结构不支持依赖注入,Spring IoC容器也注入不了任何东西,从而从根本上说**“如何设计好类结构才是关键,依赖注入只是一种装配对象手段”。**
手写IOC
标记配置分为集中式管理和分散式管理,即xml文件方式和注解方式配置元数据信息,手写ioc我采用注解配置元数据方式来实现ioc的大致运行过程。
定义元数据配置信息
@Component/@Controller/@Repository@Service
扫描什么样的class装载到ioc容器中进行管理。
@Autowired
什么样的对象进行依赖注入。
1 2 3 4 5 6 7 8 9 10
|
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Component { }
|
1 2 3 4 5 6 7 8 9 10
|
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Controller { }
|
1 2 3 4 5 6 7 8 9 10
|
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Repository { }
|
1 2 3 4 5 6 7 8 9 10
|
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Service { }
|
1 2 3 4 5 6 7 8 9 10 11
|
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { String value() default ""; }
|
实现ioc容器
核心功能:加载被配置标记的class文件,交给ioc容器管理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
| package org.simplespring.core;
import lombok.extern.slf4j.Slf4j; import org.simplespring.core.annotation.Component; import org.simplespring.core.annotation.Controller; import org.simplespring.core.annotation.Repository; import org.simplespring.core.annotation.Service; import org.simplespring.util.ClassUtil; import org.simplespring.util.ValidationUtil;
import java.lang.annotation.Annotation; import java.util.*; import java.util.concurrent.ConcurrentHashMap;
@Slf4j public class BeanContainer {
private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);
private boolean loaded = false;
public static BeanContainer getInstance() { return ContainerHolder.HOLDER.instance; }
private enum ContainerHolder { HOLDER;
private BeanContainer instance;
ContainerHolder() { instance = new BeanContainer(); } }
public boolean isLoad(){ return loaded; }
public int getSize(){ return beanMap.size(); }
public synchronized void loadBeans(String packageName){ if (loaded){ log.warn("Container has loaded!"); return; } Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName); if (ValidationUtil.isEmpty(classSet)) { log.warn("extract nothing from packageName" + packageName); return; }
classSet.stream().forEach(clazz -> { for (Class<? extends Annotation> annotationClazz : BEAN_ANNOTATION) { if (clazz.isAnnotationPresent(annotationClazz)){ beanMap.put(clazz, ClassUtil.newInstance(clazz, true)); } }
});
loaded = true; }
public Object addBean(Class<?> clazz, Object bean) { return beanMap.put(clazz, bean); }
public Object removeBean(Class<?> clazz) { return beanMap.remove(clazz); }
public Object getBean(Class<?> clazz) { return beanMap.get(clazz); }
public Set<Class<?>> getClasses(){ return beanMap.keySet(); }
public Set<Object> getBeans(){ return new HashSet<>( beanMap.values()); }
public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){ Set<Class<?>> keySet = getClasses(); if(ValidationUtil.isEmpty(keySet)){ log.warn("nothing in beanMap"); return null; } Set<Class<?>> classSet = new HashSet<>(); for(Class<?> clazz : keySet){ if(clazz.isAnnotationPresent(annotation)){ classSet.add(clazz); } } return classSet.size() > 0? classSet: null; }
public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){ Set<Class<?>> keySet = getClasses(); if(ValidationUtil.isEmpty(keySet)){ log.warn("nothing in beanMap"); return null; } Set<Class<?>> classSet = new HashSet<>(); for(Class<?> clazz : keySet){ if(interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){ classSet.add(clazz); } } return classSet.size() > 0? classSet: null; }
}
|
定义依赖注入器
核心功能:扫描被管理的bean对象,进行依赖注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
| package org.simplespring.inject;
import lombok.extern.slf4j.Slf4j; import org.simplespring.core.BeanContainer; import org.simplespring.inject.annotation.Autowired; import org.simplespring.util.ClassUtil; import org.simplespring.util.ValidationUtil; import java.lang.reflect.Field; import java.util.Set;
@Slf4j public class DependencyInjector {
private BeanContainer beanContainer; public DependencyInjector(){ beanContainer = BeanContainer.getInstance(); }
public void doIoc(){ if(ValidationUtil.isEmpty(beanContainer.getClasses())){ log.warn("empty classset in BeanContainer"); return; } for(Class<?> clazz : beanContainer.getClasses()){ Field[] fields = clazz.getDeclaredFields(); if (ValidationUtil.isEmpty(fields)){ continue; } for(Field field : fields){ if(field.isAnnotationPresent(Autowired.class)){ Autowired autowired = field.getAnnotation(Autowired.class); String autowiredValue = autowired.value(); Class<?> fieldClass = field.getType(); Object fieldValue = getFieldInstance(fieldClass, autowiredValue); if(fieldValue == null){ throw new RuntimeException("unable to inject relevant type,target fieldClass is:" + fieldClass.getName() + " autowiredValue is : " + autowiredValue); } else { Object targetBean = beanContainer.getBean(clazz); ClassUtil.setField(field, targetBean, fieldValue, true); } } } }
}
private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) { Object fieldValue = beanContainer.getBean(fieldClass); if (fieldValue != null){ return fieldValue; } else { Class<?> implementedClass = getImplementedClass(fieldClass, autowiredValue); if(implementedClass != null){ return beanContainer.getBean(implementedClass); } else { return null; } } }
private Class<?> getImplementedClass(Class<?> fieldClass, String autowiredValue) { Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass); if(!ValidationUtil.isEmpty(classSet)){ if(ValidationUtil.isEmpty(autowiredValue)){ if(classSet.size() == 1){ return classSet.iterator().next(); } else { throw new RuntimeException("multiple implemented classes for " + fieldClass.getName() + " please set @Autowired's value to pick one"); } } else { for(Class<?> clazz : classSet){ if(autowiredValue.equals(clazz.getSimpleName())){ return clazz; } } } } return null; }
}
|
工具包
作为框架 类加载/反射/校验等功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
| package org.simplespring.util;
import lombok.extern.slf4j.Slf4j; import java.io.File; import java.io.FileFilter; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.net.URL; import java.util.Arrays; import java.util.HashSet; import java.util.Set;
@Slf4j public class ClassUtil {
public static final String FILE_PROTOCOL = "file";
public static ClassLoader getClassLoader(){ return Thread.currentThread().getContextClassLoader(); }
public static Class<?> loadClass(String className){ try { return Class.forName(className); } catch (ClassNotFoundException e) { log.error("loadClass failed!",e); throw new RuntimeException(e); }
}
public static <T> T newInstance(Class<?> clazz, boolean accessible){ try { Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(accessible); return (T)constructor.newInstance(); } catch (Exception e) { log.error("new instance failed!"); throw new RuntimeException(e); } }
public static void setField(Field field, Object target, Object value, boolean accessible){ field.setAccessible(accessible); try { field.set(target, value); } catch (IllegalAccessException e) { log.error("setField error", e); throw new RuntimeException(e); } }
public static Set<Class<?>> extractPackageClass(String packageName){
ClassLoader classLoader = getClassLoader(); URL url = classLoader.getResource(packageName.replace(".","/")); if (url == null){ log.warn("unable to retrieve anything from package: " + packageName); return null; }
Set<Class<?>> classSet = null; if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){ classSet = new HashSet<Class<?>>(); File packageDirectory = new File(url.getPath()); extractClassFile(classSet, packageDirectory, packageName); }
return null; }
private static void extractClassFile(Set<Class<?>> classSet, File packageDirectory, String packageName) {
if(!packageDirectory.isDirectory()){ return; } File[] files = packageDirectory.listFiles(new FileFilter() { @Override public boolean accept(File pathname) { if(pathname.isDirectory()){ return true; } else{ String absoluteFilePath = pathname.getAbsolutePath(); if(absoluteFilePath.endsWith(".class")){ addToClassSet(absoluteFilePath); } } return false; }
private void addToClassSet(String absoluteFilePath) { absoluteFilePath = absoluteFilePath.replace(File.separator, "."); String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName)); className = className.substring(0, className.lastIndexOf(".")); Class targetClass = loadClass(className); classSet.add(targetClass); } });
if(files == null){ return; } Arrays.stream(files).forEach( file -> { extractClassFile(classSet,file,packageName); });
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package org.simplespring.util;
import java.util.Collection; import java.util.Map;
public class ValidationUtil {
public static boolean isEmpty(String obj) { return (obj == null || "".equals(obj)); }
public static boolean isEmpty(Object[] obj) { return obj == null || obj.length == 0; }
public static boolean isEmpty(Collection<?> obj){ return obj == null || obj.isEmpty(); }
public static boolean isEmpty(Map<?, ?> obj) { return obj == null || obj.isEmpty(); } }
|