关于Spring框架中的IOC的模拟实现

* 本博文内容是基于本人的学习理解与实践论证所作

* 如有错误请积极指出,不胜感激

* 转载请注明出处

所用到的工具:

     包扫描工具(如有疑问请参见往期博文)

所用注解及其作用:

     注解类型有三种:‘@Component’、‘@Bean’、‘@Autowired’


     作用:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
	String name() default "";
}

      ——‘@Component’注解的作用对象是类,‘@Bean’注解的作用对象是类中的方法,‘@Autowired’注解的作用对象是类中的需要进行自动注入的成员。

      ——给类加 ‘@Component’注解是为了实现此类的自动注入,目的是在想使用这些类的对象的时不再去亲自new,既直接从beanFactory里直接取出使用。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Bean {
}

      ——给方法加‘@Bean’注解的作用是为了在扫描到一个自动注入类之后,用该类的对象作为前缀执行其加中加了‘@Bean’注解的方法,并将返回值加入到bean工厂中进行处理。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
	String name() default "";
}

      ——给类中的字段加‘@Autowired’注解是为了声明该类中需要进行自动注入的字段。

以上两个注解给出name属性表示支持可以用别名在Bean工厂中取到。

其中@bean注解存在的意义:

      ——@bean注解是作用于方法的。对于方法来说,方法分为无参方法与非无参方法。我们的目的是帮框架使用者生成他们想要的类的实例,并按照其意愿对这些实例进行一些注入操作。

从框架使用者的角度来看:如果涉及到某些类的实例,而这些类并没有提供源代码,所以就无法通过给类添加@component注解或者@Autowired注解去实现。于是用户可以在相关注解了@component的类中添加一些get方法(无参),并给它们加上@bean注解。在这些方法中直接new出并返回此种类的实例。在处理@bean注解时先执行无参的方法,将这些方法返回的实例一个一个加入到Bean工厂中。之后再执行那些带参的注解了@bean的方法也就不会再担心某些参数无法初始化而无法执行的问题了。

处理过程分析:

      ——在包扫描时筛选出注解@Component的类,在每个类中再次筛选出注解@bean注解的方法,将无参的方法先执行,需要参数的方法存储起来(存储方式是封装在类中,详见代码),待无参方法执行完后再将带参方法执行完。此时的bean工厂已经被丰富填充了,可以进行真正的注入操作了。

//存储带参方法的类
public class BeanMethodDefination {
	private Method method;
	private Class<?> returnType;
	private Class<?>[] parametersClass;
	private Object object;
	private String beanName;
	
	public BeanMethodDefination() {
	}

	public BeanMethodDefination(Method method, Class<?> returnType, Class<?>[] parametersClass, Object object, String beanName) {
		this.method = method;
		this.returnType = returnType;
		this.parametersClass = parametersClass;
		this.object = object;
		this.beanName = beanName;
		
	}

	protected String getBeanName() {
		return beanName;
	}

	protected Method getMethod() {
		return method;
	}

	protected Class<?> getReturnType() {
		return returnType;
	}

	protected Class<?>[] getParameters() {
		return parametersClass;
	}

	protected Object getObject() {
		return object;
	}
	
}
//初期包扫描时所进行的操作
public static void scanPackage(String packageName) {
	ProxyBeanFactory proxyBeanFactory = new ProxyBeanFactory();
	new PackageScanner() {
		@Override
		public void dealClass(Class<?> klass) {
			if(!klass.isAnnotationPresent(Component.class)) {
				return;
			}
			String beanName = klass.getAnnotation(Component.class).name();
			String className = klass.getName();
			try {
				proxyBeanFactory.getCGLProxy(klass);
				Object object = proxyBeanFactory.getMecProxy(klass).getObject();
				
				Method[] methods = klass.getDeclaredMethods();
				for(Method method : methods) {
					if(!method.isAnnotationPresent(Bean.class)) {
						continue;
					}
                    //此处处理所有方法,无参先执行,带参方法将被存储起来
					invokeBeanMethod(method, klass, object, proxyBeanFactory, beanName);
				}
                //添加别名
				if (beanName.length() > 0) {
					proxyBeanFactory.addBean(beanName, className);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

		}
	}.packageScan(packageName);
	//执行带参方法
	for(BeanMethodDefination beanMethodDefination : methodList) {
		Object object = beanMethodDefination.getObject();
		Class<?> klass = beanMethodDefination.getClass();
		Method method = beanMethodDefination.getMethod();
		Class<?>[] parametersClass = beanMethodDefination.getParameters();
		String beanName = beanMethodDefination.getBeanName();
		
		try {
			invokeMulBeanMethod(method, klass, object, parametersClass, proxyBeanFactory, beanName);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	methodList.clear();
}

   递归注入法及其问题:

我们需要被将注入的对象的Autowired成员丰富起来,并返回注入对象的代理对象。也就是AOP了。事实上一开始代理对象和原始对象就一起生成了。在这里先不讨论AOP,后续博文会补充。

此时bean工厂中所有需要注入的对象就已经存在了,现在需要做的就是注入了。

扫描需要注入的类,筛选出注解了Autowired的字段,再在bean工厂中进行查找,如果此字段中又有需要注入的成员,那就可以通过递归的方式进行注入了。
      *  仔细分析存在一个问题,若A类有一个Autowired成员是B类类型的,而这个B类类型又有一个Autowired成员是C类类型的,
 这个C类类型的成员又有一个Autowired成员是A类类型的,这种情况如果不加以控制,这个“循环依赖注入”的问题就会产生无限递归, 将没有办法再继续注入。

      *  解决方法是给这个类的对象在准备注入前就加上一个已经注入的标记,这个标记默认在对象产生的时候为False,之后再进行递归注入,将此对象的标记值作为递归是否进行下去的控制条件就可以完美解决。以下贴出递归注入部分的代码。

private static void injectBean 
(ProxyBeanFactory proxyBeanFactory, Object object, Class<?> klass) {
    MecProxy orgMecProxy = proxyBeanFactory.getMecProxy(klass);
    orgMecProxy.setInjection(true);//立即标记本次注入
	
    Field[] fields = klass.getDeclaredFields();
    for(Field field : fields) {
       if(!field.isAnnotationPresent(Autowired.class)) {   
	    continue;
	}
	Class<?> fieldClass = field.getType();
	MecProxy mecProxy = proxyBeanFactory.getMecProxy(fieldClass);
        //如果本次注入对象中的Autowired字段没有被注入过,先递归此字段,再注入
	if(!mecProxy.isInjection()) {
	    injectBean(proxyBeanFactory, mecProxy.getObject(), fieldClass);
	}
	field.setAccessible(true);
	try {
		field.set(object, mecProxy.getObject());
		System.out.println("注入了:"
                 + mecProxy.getObject() + "到" + object + "的" + field + "中");
	} catch (Exception e) {
		e.printStackTrace();
	}
    }
}

其中的proxyBeanFactory中存储着所有扫描过的bean,可用类型取到,所以是单立的。而取到的MecProxy就是代理、原对象与是否注入过标记的集合类了。

实践验证:【(@Component)StudentAction类中有(@Autowired)studentDao字段,以此依赖关系,最后的StudentModel中有(@Autowired)studentAction字段】,以下贴出输出结果:

(输出结果中的类名字由于存在包名所以很长,在此用‘*’代替一部分)

注入了:*.student.action.StudentAction@137e0d2到*.student.model.StudentModel@59eb14中
注入了:*.student.model.StudentModel@59eb14到*.student.dao.TeacherDao@2e4553中
注入了:*.student.dao.TeacherDao@2e4553到*.student.dao.StudentDao@8c97a5中
注入了:*.student.dao.StudentDao@8c97a5到*.student.action.StudentAction@137e0d2中


studentAction中的studentdao:*.student.dao.StudentDao:@8c97a5
studentDao 中的 teacherDao:*.student.dao.TeacherDao:@2e4553
teacherDao 中的 studentModel:*.student.model.StudentModel:@59eb14
sudentModel 中的 studentAction:*.student.action.StudentAction:@137e0d2

猜你喜欢

转载自blog.csdn.net/coinph/article/details/83188582