您的位置:首页 > 其它

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

2020-02-29 18:18 239 查看

关于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;

BeanMetohdDe
1b5d7
fination() {
}

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指针指向了被代理对象的空间,这样,我们的代理才是真的有意义的。我们是真的在原来代码不变的情况下,然后通过代理来有条件的操纵原来空间。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
青梅凝雪香自来 发布了2 篇原创文章 · 获赞 0 · 访问量 136 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐