关于IoC的简单想法,以及循环依赖的处理。

关于IoC

前言: IoC是Spring框架的重要内容。希望通过对它的简单模拟能够更加深入的了解Spring的内涵。感谢一直以来为我引路的朱洪教主。

1 什么是IoC

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。—来自度娘

度娘的意思很难懂,在这里我就说说我自己的理解。IoC的意思就是说,当一个类需要被实例化的时候,并不是通过我们编程人员自己通过new()方法来实现的,而是交给第三方来完成的。并且这个类被销毁的时候也应该是由第三方来完成的。(之所以是第三方,是因为Java虚拟机也是一方)

2 IoC的技术核心

首先我们必须知道自己需要产生的对象是什么,并且能够让第三方去知道,这个对象将会第三方自动生成。
因此,我们可以采用某种方式告知第三方。故,注解和XML文件将是我们的好帮手。在这里,我只实现注解方式来完成IoC的操作
最重要的一点是,我们希望第三方产生的对象是一个类的代理对象。这样,关于IoC才能真真正正的实现解耦。并且只有AOP与IoC相结合才能高效的完成工作。
关于AOP的相关讨论,我已经给出,请参考这里: AOP的简单讨论.
在AOP中虽然已经完成了代理对象的注解自动化产生,但是所有代理对象中所有成员的值都是0或者NULL。这是不完整的。IoC的核心在于注入,注入就是把值(可以是对象,可以是基本八大类型以及String类型)赋值给对象的成员。 也就是说,单说IoC是不完整的。
与AOP相同的是,我们需要包扫描技术。

3 IoC的简单实现

由于IoC是基于AOP的基础之上来完成的,所以在这里已经在AOP中出现的相关的代码我不会给出。由于AOP我们已经是完成了自动生成拦截器的地步,因此我们需要改变的只是原来工厂中的某些方法。我们原来在AOP中自动生成代理的AotoProxyFactory类已经可以正式改名字了,我们可以叫它BeanFactory。关于BeanFactory类我们这样说,它是对AotoProxyFactory的补充。
下面,我先给出完成该功能的相关注解

//该类作用于除基本八大类型String类型,之外的类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowire {
	public String beanName() default "";
}

//将会被扫描到的类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Compent {
	public String beanName() default "";
}

//基本八大类和string类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Value {
	public String value();
	public Class<?> klass();
}

从上述注解可以看出,我们定义Compent注解使的这个类被扫描到,并且为其生成代理对象,并且它将被注入。
我们定义的Autowire注解,将会告知这个Compent注解存在类,他需要一个同样被IoC所生成的类的对象作为成员。(这里存在一个问题,后面将会说到)。最后,Value注解为我们将基本八大类型以及String类注入进对象中。

//该类作用于方法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Bean {
	public String beanName() default "";
}

关于Bean注解,我想多说一些
我们通过Bean注解为方法上添加注解,这是为了生成那些没有办法在类上添加Compent注解的类。例如我们的复杂类需要一个Map作为对象,我们是没有办法在Map的源代码上添加注解的,它的源代码不可更改。并且如果是方法上的注解,我们就一定要调用反射机制的invoke方法,反射执行该方法的。作为方法,参数是一个很重要的标志,如果参数是我们BeanFactory内部Map内的对象,这当然是合理的。但如果说该类的对象还没有被放到BeanMap中去,那显然是会出现问题的。
我们给出如下类,他描述了一个Method的基本信息:

public class BeanMetohdDefination {
	private Method method;
	private Class<?> returnType;
	private Parameter[] parameters;
	private Object object;
	private String nick;
	
	BeanMetohdDefination() {
	}

	BeanMetohdDefination(Method method, Class<?> returnType, 
			Parameter[] parameters, Object object, String nick) {
		this.method = method;
		this.returnType = returnType;
		this.parameters = parameters;
		this.object = object;
		this.nick = nick;
	}

	String getNick() {
		return nick;
	}

	Method getMethod() {
		return method;
	}

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

	Parameter[] getParameters() {
		return parameters;
	}

	Object getObject() {
		return object;
	}
	
}

我们在给一个List,这个List存储了当前不能直接执行的方法的一个列表,并且先把无参方法全部执行完成之后,在考虑去执行相关带参的方法。
完成上述的问题后,我们已经生产了很多没有完全注入好的对象(已经注入好的只有成员都是基本八大类型的对象,部分成员为基本八大类型的对象只注入一部分(Value注解的作用))。

在这里总结以下,用来告诉第三方需要产生对象的注解是Compent和Bean注解。而Autowire注解才是注入注解,这个注解告诉了我们对象间的注入关系。并且只有在所有对象都被生成了才能完成整个的注入。也就是先产生,后注入。
注入是最后完成的。
关于我们在将完成对象的注入时,我们先需要思考这一点,如果我们注入一个复杂类,它需要另一个复杂类,但是该复杂类还需要另外一个复杂类来注入。这个时候我们就需要先完成另一个复杂类的注入,此处就需要一个递归程序(这是是有保障的,因为你不可能产生几百万个对象)。

比如这样:
图中的箭头代表着,类之间的注入关系,箭头指的类被箭头尾部装入。

图中的箭头代表这,类的注入关系,箭头指的类被箭头尾部装入
上述的图还有一种很可怕的走向, 由于采用递归,就有下面的一个问题,当一个类是的成员是另一个类,另一个类的成员是该类,那我们的递归就成了无限递归。比如说这样:
在这里插入图片描述
下图给了循环依赖的两个测试类,这是最简单的注入关系,但同时也最能说明问题。

@Compent
public class ClassA {
	@Autowire
	private ClassB b;

	public ClassA() {
	}
	
	public void showClassB(){
		System.out.println("ClassA" + b);
	}
	@Override
	public String toString() {
		return "ClassA    " + super.toString();
	}
	
}
@Compent
public class ClassB {
	@Autowire
	private ClassA a;

	public ClassB() {
	}
	
	@Bean
	public ClassA getClassA(ClassA a){
		this.a = a;
		return a;
	}
	
	public ClassA getClassA(){
		return this.a;
	}

	@Override
	public String toString() {
		return "ClassB      " + super.toString();
	}
}

因此,我在这里插入图片描述们不妨给一个标记,当我们注入时,如果它被注入,或者正在注入,我们就记它为false,表示它不能注入了,在递归注入之前,先查看它能不能注入,如果被标记为false,那就直接结束这次递归调用。这样就能够避免循环依赖。这样:

当有了标志后,上图就变成了第一个图。这样就能完成循环依赖。

这里是整个IoC的最后部分,为了完成它,我们做了很多工作。当外部调用getBean方法时候,我们完成注入。下面的类没有处理Value注解。有兴趣的可以实现一下。在这里就不给出了。

public class BeanFactory {
	//真正的bean
	private HashMap<String, MeProxy> beanMap;
	//bean的昵称
	private HashMap<String, String> beanNameMap;
	
	public BeanFactory() {	
		beanMap = new HashMap<String, MeProxy>();
		beanNameMap = new HashMap<String, String>();
	}
	
	public <T> T getBean(Class<?> klass){
		 MeProxy meProxy = getOwerMeProxy(klass);
		 if (meProxy.isInjection() == false) {
			 injectBean(klass, meProxy.getObject());
		}
		return meProxy.getProxy();
	}
		
	public <T> T getBean(String beanName){
		Class<?> klass = null;
		try {
			klass = Class.forName(beanNameMap.get(beanName));
		} catch (Exception e) {
			new Exception("昵称不存在" + beanName);
			e.printStackTrace();
		}		
		return getBean(klass);
	}
	
	private void injectBean(Class<?> klass, Object object){
		 Field[] fields = klass.getDeclaredFields();
		 MeProxy meProxy = getOwerMeProxy(klass);
		 for (Field field : fields) {
			 if (!field.isAnnotationPresent(Autowire.class)) {
					continue;
			 }
			 meProxy.setInjection(true);
			 MeProxy fieldProxy = getOwerMeProxy(field.getType());
			 if (fieldProxy.isInjection() == false) {
				injectBean(field.getType(), fieldProxy.getObject());
			 }
			 field.setAccessible(true);
			 try {
			 //这里的对象,必须是原对象,不能是代理对象。
				field.set(object, fieldProxy.getProxy());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		 meProxy.setInjection(true);
	}
	
	public void packSannerIntercept(String packageName){
		new PackageSanner() {			
			@Override
			public void doSanner(Class<?> klass) {
				if(klass.getAnnotation(Ascept.class) == null){
					return;
				}
				 Method[] methods = klass.getMethods();
				 for (int i = 0; i < methods.length; i++) {
					Method method = methods[i];
					 After after = method.getAnnotation(After.class);
					 Before before = method.getAnnotation(Before.class);
					 ExceptionIntercept exception = method.getAnnotation(ExceptionIntercept.class);
					if (after != null) {						
						dealAfter(after ,method);
					}
					if (before != null) {
						dealBefore(before,method);
					}
					if (exception != null) {
						dealException(exception,method);
					}					
				}			
			}
		}.packageSanner(packageName);
	}
	
	private void  dealAfter(After after, Method method){
		Class<?> klass = after.klass();
		String string = after.methodName();
		Class<?>[] paraType = after.paraKlass();
		MeProxy meProxy= getOwerMeProxy(klass);
		Method methodsClass;
		try {
			methodsClass = klass.getMethod(string, paraType);
			meProxy.addIntercepter(methodsClass, new AfterMethodIntercepter(method));
		}  catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 
	 * @param before
	 * @param method
	 * @throws Throwable
	 * 
	 * @tip
	 *  通过拦截器的方法的参数类型得到需要拦截的方法的精确定位	<p>
	 *  根据拦截器的相关定义,其前置拦截必须要求的参数是拦截方法的参数
	 */
	
	private void  dealBefore(Before before, Method method){
		String[] strings = before.methodNames();
		Class<?> klass = before.klass();
		MeProxy meProxy= getOwerMeProxy(klass);
		for (int i = 0; i < strings.length; i++) {
			try {
				Method methodsClass = klass.getMethod(strings[i], method.getParameterTypes());
				meProxy.addIntercepter(methodsClass, new BeforeMethodIntercepter(method));
			} catch (Exception e) {
				e.printStackTrace();
			} 
		}
	}
	
	private void  dealException(ExceptionIntercept exception, Method method){
		Class<?> klass = exception.klass();
		String string = exception.methodName();
		Class<?>[] paraType = exception.paraKlass();
		MeProxy meProxy= getOwerMeProxy(klass);
		Method methodsClass;
		try {
			methodsClass = klass.getMethod(string, paraType);
			meProxy.addIntercepter(methodsClass, new ExceptionMethodIntercepter(method));
		}  catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private MeProxy getOwerMeProxy(Class <?> klass){
		MeProxy meProxy= beanMap.get(klass.getName());
		if (meProxy == null) {
			try {
				throw new BeanNotExist(klass.getName() + "的对象不存在");
			} catch (BeanNotExist e) {
				e.printStackTrace();
			}
		}
		return meProxy;
	}

	public void packSanner(String packageName){
		List<BeanMetohdDefination> methodList = new ArrayList<>();
		new PackageSanner() {
			@Override
			public void doSanner(Class<?> klass) {
				Compent compent = klass.getDeclaredAnnotation(Compent.class);
				if (compent == null) {
					return;
				}else{
					String str = compent.beanName();
					if (str.length() > 0) {
						beanNameMap.put(str, klass.getName());
					}
					createBean(null,klass);
				}
				Method[] methods = klass.getDeclaredMethods();
				for (Method method : methods) {
					try {
						invokeBeanMethod(method, methodList);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}				
			}
		}.packageSanner(packageName);	
		for (BeanMetohdDefination beanMetohdDefination : methodList) {
			try {
				Object Object = invokeMuliParaMethod(beanMetohdDefination.getMethod(), 
						beanMetohdDefination.getParameters(), beanMetohdDefination.getObject());
				createBean(Object, beanMetohdDefination.getReturnType());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
	}
	
	private void invokeBeanMethod(Method method, List<BeanMetohdDefination> methodList) 
			throws Exception {
		Class<?> klass = method.getReturnType();
		Bean bean = method.getAnnotation(Bean.class);
		if (klass.equals(void.class) || bean == null) {
			return;
		}		
		String name = bean.beanName();
		Parameter[] parameters = method.getParameters();
		Object object = beanMap.get(method.getDeclaringClass().getName()).getObject();
		if (parameters.length <= 0) {
			Object result = method.invoke(object);
			createBean(result,method.getReturnType());
		} else {
			methodList.add(new BeanMetohdDefination(method, klass, parameters, object, name));
		}
		
	}
	
	private void createBean(Object obj, Class<?> klass) {
		MeProxyFactory meProxyFactory = new MeProxyFactory();
		try {
			if(obj == null){
				meProxyFactory.getCGLProxy(klass);
			}else{
				meProxyFactory.getCGLProxy(obj);
			}				
		} catch (Throwable e) {		
			e.printStackTrace();
		}
		beanMap.put(klass.getName(), meProxyFactory.getMeProxy());
	}
	
	private Object invokeMuliParaMethod(Method method, Parameter[] parameters,
			Object object) throws Exception {
		int paraCount = parameters.length;		
		Object[] paras = new Object[paraCount];
		for (int index = 0; index < paraCount; index++) {
			Parameter parameter = parameters[index];
			String className = parameter.getType().getName();
			MeProxy meProxy = beanMap.get(className);
			Object beanObject = meProxy.getObject();
			if (beanObject != null) {
				paras[index] = beanObject;
			}
		}		
		return method.invoke(object, paras);
	}
}

以下是测试

@Ascept
public class Text {
	
	public Text() {
	}
	
	@After(klass = Student.class, methodName = "seek", paraKlass = { Object.class })
	public Class<?>[] funcyin(String name){
		Class<?>[] klass = new Class[4];
		System.out.println("seek方法的后置拦截");
		return klass;	
	}
	
	@Before(klass = Student.class, methodNames = {"seek"}, beanName = "")
	public boolean func(Object obj){
		System.out.println("2这是前置拦截");
		System.out.println((String)obj);
		return true;		
	}
	
	@Before(klass = Student.class, methodNames = {"seek"}, beanName = "")
	public boolean funct(Object obj){
		System.out.println("1这是前置拦截");
		System.out.println((String)obj);
		return true;		
	}
	
	@Before(klass = Student.class, methodNames = {"remove"}, beanName = "")
	public boolean functt(Object obj){
		System.out.println("3这是前置拦截");
		return true;		
	}
	
	public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
	BeanFactory aoto = new BeanFactory();
		aoto.packSanner("com.ioc.text");
		aoto.packSannerIntercept("com.ioc.text");
		Student student = aoto.getBean(Student.class);
		ClassA a = aoto.getBean(ClassA.class);
		ClassB b = aoto.getBean(ClassB.class);
		a.showClassB();
		System.out.println(b);
		System.out.println(a);
		student.seek("111");		
	}

结果如图:
在这里插入图片描述
观察到,ClassB的地址,与a.showClassB的地址值是一样的,也就说,我们的确完成了相关的操作。

最后,我们总结一下如下东西。
第一,无参构造的必要性。我们的IoC的对象,是通过调用无参的构造方法来反射生成的。我们可以思考以下,对于上述的ClasA和ClassB,如果只有单参构造,那么真的是神仙都没办法正确使用这两个类。
第二,get和set方法,这是对字段赋值的强力方法。
第三,this指针的作用,在AOP中,我们发现this指针指向了被代理对象的空间,这样,我们的代理才是真的有意义的。我们是真的在原来代码不变的情况下,然后通过代理来有条件的操纵原来空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值