控制反转和和依赖注入是Spring中的重要概念。控制反转是将当前bean所依赖的其他bean组件的实例化过程交由IoC容器来实现并由IoC容器注入到当前bean当中。之前对其实现有一个简单的认识,即根据类名通过反射的方式加载所需要的bean组件。现在对其做一个通过注解方式进行注入简单的实现。
首先定义Container接口,容器的主要功能即实现对bean的存储,即实现bean的增删查。所以定义其获取、注册、删除、初始化功能。
/**
* IoC容器接口
* @author OrangeXXL
*
*/
public interface Container {
//根据class获取Bean
public <T> T getBean(Class<T> clazz);
//使用name获取Bean
public <T> T getBeanByName(String name);
//将Bean注册到容器
public Object registerBean(Object bean);
//将class注册到容器
public Object registerBean(Class<?> clazz);
//注册带名称的bean到容器
public Object registerBean(String name, Object bean);
//删除一个Bean
public void remove(Class<?> clazz);
//根据名称删除Bean
public void removeByName(String name);
//返回所有bean对象名称
public Set<String> getBeanNames();
//初始化装配
public void initWired();
}
接下来是对容器的具体实现。容器实现的是在加载bean的过程中,对其进行扫描,找到有注解的字段后对其所依赖的bean组件进行注入。首先我们需要存储已加载的bean。所以通过map以<class name,bean object>的形式存储bean。同时用户会对bean进行命名,因此再创建一个map以<custom name, class name>的形式存储bean名称和类名称的对应关系。再初始化当中加载当前bean。因此容器的实现如下。
public class SampleContainer implements Container {
/**
* 创建map用来保存所有beans
* key是className value为Bean对象
*/
private Map<String, Object> beans;
/**
* 存储Bean和className的关系
* 其中key是customName value是className
*/
private Map<String, String> beanKeys;
public SampleContainer() {
this.beans = new ConcurrentHashMap<String, Object>();
this.beanKeys = new ConcurrentHashMap<String, String>();
}
@Override
//根据class获取Bean
public <T> T getBean(Class<T> clazz) {
String name = clazz.getName();
Object object = beans.get(name);
if(null != object){
return (T) object;
}
return null;
}
@Override
//根据name获取bean
public <T> T getBeanByName(String customName) {
String className = beanKeys.get(customName);
Object object = beans.get(className);
if(null != object){
return (T) object;
}
return null;
}
@Override
//将Bean注册到容器
public Object registerBean(Object bean) {
String customName = bean.getClass().getName();
beanKeys.put(customName, customName);
beans.put(customName, bean);
return bean;
}
@Override
//将class注册到容器
public Object registerBean(Class<?> clazz) {
String className = clazz.getName();
beanKeys.put(className, className);
Object bean = ReflectUtil.newInstance(clazz);
beans.put(className, bean);
return bean;
}
@Override
//注册带名称的bean到容器
public Object registerBean(String customName, Object bean) {
String className = bean.getClass().getName();
beanKeys.put(customName, className);
beans.put(className, bean);
return bean;
}
@Override
//删除一个Bean
public void remove(Class<?> clazz) {
String className = clazz.getName();
if(null != className && !className.equals("")){
beanKeys.remove(className);
beans.remove(className);
}
}
@Override
//根据名称删除Bean
public void removeByName(String customName) {
String className = beanKeys.get(customName);
if(null != className && !className.equals("")){
beanKeys.remove(customName);
beans.remove(className);
}
}
@Override
//返回所有bean对象名称
public Set<String> getBeanNames() {
return beanKeys.keySet();
}
@Override
//初始化装配
public void initWired() {
Iterator<Entry<String, Object>> it = beans.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next();
System.out.println("[DEBUG] className=>"+entry.getKey()+"\n[DEBUG] beanName=>"+entry.getValue().getClass().getName());
Object object = entry.getValue();
injection(object);
}
}
/**
* 注入对象
* @param obj
* @return
*/
public void injection(Object obj){
try{
Field[] fields = obj.getClass().getDeclaredFields();
for(Field field : fields){
/**
* 获取Autowired注解标注的字段
* 即获取需要注入的字段
*/
/**
* getAnnotation方法是AccessibleObject类的方法。
* 如果存在注解,则返回该元素的指定类型的注解,否则返回null
*/
Autowired autowired = field.getAnnotation(Autowired.class);
System.out.println("[DEBUG] print autowired field name"+autowired+"end");
if(null != autowired){
//进入判断条件的即为存在Autowired类型注解的字段
Object autowiredField = null;
String name = autowired.name();
System.out.println("[DEBUG] autowired field name"+"["+name+"]");
if(!name.equals("")){
//name不为空,则需要注入的就是bean,所以首先通过beanName拿到className
String className = beanKeys.get(name);
if(null != className && className.equals("")){
//className存在说明曾经注入过。则直接取出相应的实例即可
autowiredField = beans.get(className);
}
if(null == autowiredField){
throw new RuntimeException("Unable to load " + name);
}
}else{
//name为空则需要注入的是class,
System.out.println("[DEBUG] name == null in this");
if(autowired.value() == Class.class){
autowiredField = recursiveAssembly(field.getType());
}else{
autowiredField = this.getBean(autowired.value());
if(autowiredField == null){
autowiredField = recursiveAssembly(autowired.value());
}
}
}
if (null == autowiredField) {
throw new RuntimeException("Unable to load " + field.getType().getCanonicalName());
}
//将获取到的实例注入到相应字段(autowiredField)中
boolean accessible = field.isAccessible();
field.setAccessible(true);
field.set(obj, autowiredField);
field.setAccessible(accessible);
}
}
}catch(SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 装配class
* @param clazz
* @return
*/
private Object recursiveAssembly(Class<?> clazz){
if(null != clazz){
return this.registerBean(clazz);
}
return null;
}
}
这里对initWired函数做一个说明。当我们需要对某个bean进行加载时,首先我们会先将该类注册到容器当中。之后初始化时,我们迭代容器中所有的bean对其进行检查看是否存在需要注入的字段。并进行注入操作。这里迭代容器中的bean有一个很有意思的地方。举个例子,我们需要加载的bean中有若干字段需要被注入。第一次,我们将该bean注册时候去检查其字段并加载它的依赖。当加载完成后,初始化操作还会继续迭代(此时map中有了新的元素)。此时倘若被依赖的bean中同样存在对别的bean的依赖则同样会被加载进来。
但这一处我也有个疑问,在加载当前bean的时候map通过迭代器读取当前Entry,之后我们对当前Value中存储的bean进行注入。而注入时又会向map中添加元素。虽然concurrentHashMap支持迭代器的边访问,便修改的操作。concurrentHashMap通过segements细化粒度来提高其并发度,但是也存在几率新添加的元素和正在访问的元素在一个segement当中。我认为同一个segement中是会阻塞的吧。但是看到有文章说支持边访问边修改。我目前也不太清楚怎么证实。如果有了解的老哥可以评论一下。
另外容器中注册bean中有一个ReflectUtil其中主要有一个newInstance的重载方法。可以根据类名(全限定类名)和类对象创建bean。
/**
* 根据类名创建对象
* @param className
* @return
*/
public static Object newInstance(String className){
Object obj = null;
try{
Class<?> clazz = Class.forName(className);
obj = clazz.newInstance();
}catch(Exception e){
e.printStackTrace();
}
return obj;
}
/**
* 创建类的实例
* @param clazz
* @return
*/
public static Object newInstance(Class<?> clazz){
try{
return clazz.newInstance();
}catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
以上就是容器的基本实现。另外我们需要通过注解对依赖的bean进行标注。因此需要实现一个注解。注解有两个默认参数,一个是类对象,另一个是类名。代码如下。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
/**
* @return 要注入的类类型
*/
Class<?> value() default Class.class;
/**
* @return bean的名称
*/
String name() default "";
}
最后我们创建两个类Stu和OutputStu来进行验证。
- Stu是一个被依赖的bean
public class Stu {
private String name;
private int age;
public void setName(String name){
this.name = name;
}
public void setAge(int age){
this.age = age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
public void show(){
System.out.println(name+"的年龄为:"+age);
}
}
OutputStu是我们希望加载的bean
public class OutputStu {
@Autowired
private Stu s;
public void setValue(String name, int age){
s.setName(name);
s.setAge(age);
}
public void printAns(){
s.show();
}
}
他们之间的关系可以将OutoutStu对应为开发中的Controller将Stu对应为Service
以下是测试类:
public class IoCTest {
public static Container container = new SampleContainer();
public static void testAutowired(){
container.registerBean(OutputStu.class);
container.initWired();
OutputStu op = container.getBeanByName("com.wen.ioc.test.OutputStu");
op.setValue("hezhiheng", 23);
op.printAns();
}
public static void main(String[] args){
testAutowired();
}
}
首先我们加载OutputStu类,之后通过初始化装配,加载依赖beanStu。之后我们获取OutputStu对象进行相应操作。
至此一个通过注解进行注入的简单IoC容器就实现了。
参考来自biezhi的github。如有纰漏欢迎指正交流。