IoC(Inversion of Control)控制反转,是一种设计思想,DI(依赖注入)只是实现ioc的一种方式。是Spring的核心内容。可以xml配置,注解。Spring容器在初始化先读取配置文件,根据配置文件或者元数据创建与组织对象存入容器,程序使用时再从ioc容器中取出需要的对象。
没有IoC,传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,对象的创建和对象间的依赖关系完全在代码中,代码是程序员写的,控制权在程序员手里,对象的创建由代码控制。
有了IoC容器后,控制权发生了反转,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松耦合,控制反转后将对象的创建交给用户,用户需要什么对象就用什么对象,我们程序员再也不用管对象的创建了,更多时间去开发新的业务实现,耦合性大大降低。
总的来说,不使用IoC,类的创建形成多对多依赖关系,耦合度高。IoC相当于一个中间层,IoC与类之间是一对多依赖,将多对多关系变为一对多关系,减少系统整体耦合度。网上一个图片可以很好的解释。 耦合的所有对象之间有关系的,每个对象之间有关系,一层掉一层,我们想要解耦,生成个中间件去链接它们,我们调用中间件就行了,IoC容器将它们解耦了,它们四个不再具有强耦合性了,用户想要去调用谁就去调用谁,独立化。
为了解释IoC(控制反转)和DI(依赖注入)给下面例子
//OneClass类// public class OneClass { private TwoClass two; public OneClass() { } public void doSomething() { System.out.println(two); } } //TwoClass类// public class TwoClass { public TwoClass() { } @Override public String toString() { return "这是TwoClass的对象"; } } //测试 public class Demo1 { public static void main(String[] args) { OneClass oneClass = new OneClass(); oneClass.doSomething(); } } //结果 null上面运行结果是我们很容易想到的,因为OneClass没有对成员two进行赋值操作,输出当然为null了。如果有一个工具,可以悄悄地,自动的对OneClass的成员进行初始化(注入),那么运行就可以有结果了。
依赖注入:相关类的成员不是通过代码(new)完成的,而是靠一套工具来完成的,所以是依赖于工具注入。
控制反转:本来应该是OneClass类对成员two进行控制,而现在是通过外面工具进行控制的,控制权从OneClass变为了工具。
这个工具就是IoC容器。Spring模拟实现可以用XML配置和注解。
首先说明通过XML文件配置完成的注解也可以完成,注解完成的,XML也可以完成。两者各有利弊,要编程者自己斟酌用哪个。
XML配置:
优点:相关配置文件是独立于代码的,不侵害和更改代码,不用改代码就不需要测试。保证了开闭原则(对修改关闭,对扩展开放)。
缺点:如果相关类的关系很复杂,其就需要大量配置文件的抒写。(早些年的配置文件很长,一度被人们称为“配置地狱”)
注解方式:
优点:简单,明了,方便,程序可读性强,无需多写相关配置文件,使得开发效率高。
缺点:对注解进行更改,就需要进行重新的全面的严格的测试,因为修改了内部代码,一经改动就需进行大量测试以得到更改的正确性。
总而言之,条条大路通罗马,不拘一格降人才,XML和注解都可以解决问题,我们先学一样,再去找他们的相通之处就能学习到更多知识。
@Component注解:在类型上面使用的,告知包扫描工具,说明该类型的对象要加到IoC容器里面去,是该容器的一个组件。
@Autowired注解:该注解用于成员或成员设置方法(setter)的上面,其目的是对成员进行注入,这个成员的对象是从IoC容器中取出来。因此就要求被@Autowired注解标识的成员其类型必须有@Component注解。
该类主要描述一个有@Component注解的组件,将其类型和实例化对象本身封装起来形成一个豆子。
前面说了那么久的IoC容器就是这里面的beanPool,其键为类名称,值为一个BeanDefinition的对象。
另外,我们不对八大基本类型和String类型进行注入,其实可以有解决方法就是在@Autowired里加value。
还有,我们这个类需要用到以前写过的包扫描工具。
因为只需要一个IoC容器,我们就不需要FactoryBuilder,直接在Factory里进行扫描。
在没有完全扫描所有需要的包以前,是没有办法确定依赖关系的完整性的,所以不能进行注入工作。所以注入工作应该在得到或者引用相关Bean的时候。这可以称为懒汉模式。
懒:扫描完包,相关类的成员还没注入,等你要得到这个类的时候再去注入。
饿:一扫描完,就立刻去进行注入。 饿汉其实根本行不通,因为如果工程需要,多个类需要多次包扫描才能最终完成,这样注入方式是行不通的。
同时不可以重复注入,第二次getBean()如果不加判断就会重复注入,重复注入根本没有必要,解决方法是在BeanDefinition中加是否注入(inject)了标识。
BeanFactory加注入代码
import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; import com.mec.util.PackageScanner; public class BeanFactory { private static final Map<String, BeanDefinition> beanPool = new HashMap<>();//IoC容器 public BeanFactory() { } public static void scanPackage(String packageName) { new PackageScanner() { @Override public void dealClass(Class<?> klass) { if (klass.isPrimitive() || klass.isArray() || klass.isInterface() || klass == String.class || klass.isEnum() || klass.isAnnotation() || !klass.isAnnotationPresent(Component.class) ) { return; } try { Object object = klass.getConstructor(new Class[] {}).newInstance(new Object[] {}); BeanDefinition beanDefinition = new BeanDefinition(klass, object); beanPool.put(klass.getName(), beanDefinition); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } } }.scanPackage(packageName); } @SuppressWarnings("unchecked") public <T> T getBean(String beanId) { BeanDefinition beanDefinition = beanPool.get(beanId); if (beanDefinition == null) { System.out.println("bean[" + beanId + "]没有定义"); //这里可以抛运行时异常 return null; } //在第一次获得相关beanDefinition时候,进行注入,多线程在这有问题 if (!beanDefinition.isInject()) { //经典DLC问题,多线程都用这个inject,去加volatile synchronized (beanPool) { //用beanPool最好,单例的,谁都认识它 if (!beanDefinition.isInject()) { doInject(beanDefinition); beanDefinition.setInject(true); } } } return (T) beanDefinition.getObject(); } public <T> T getBean(Class<?> klass) { return getBean(klass.getName()); } public void addBean(String nickName, BeanDefinition beanDefinition) { //不想用类名称当作键,可以用别名 beanPool.put(nickName, beanDefinition); } private void doInject(BeanDefinition beanDefinition) { Class<?> klass = beanDefinition.getKlass(); Object object = beanDefinition.getObject(); injectByField(klass, object); injectBySetMethod(klass, object); } private void injectByField(Class<?> klass, Object object) { Field[] fields = klass.getDeclaredFields(); for (Field field : fields) { if (!field.isAnnotationPresent(Autowired.class)) { continue; } Class<?> fieldClass = field.getType(); Object value = getBean(fieldClass); field.setAccessible(true); //很糟糕这里,priavte的成员被打开权限了 try { field.set(object, value); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } private void injectBySetMethod(Class<?> klass, Object object) { Method[] methods = klass.getDeclaredMethods(); for (Method method : methods) { if (!method.isAnnotationPresent(Autowired.class) || !method.getName().startsWith("set") || method.getParameterCount() != 1 || method.getModifiers() != Modifier.PUBLIC) { continue; } Class<?> paraClass = method.getParameterTypes()[0]; Object value = getBean(paraClass); try { method.invoke(object, new Object[] {value}); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } }前面实现的代码我们可以看到,对于私有成员的注入,用到了field.setAccessible(true),本来硬性规定的私有成员,现在就这样被别人权限大开了,是否违反我们private关键字的初衷呢?所以说反射机制是个双刃剑,给我们带了了遍历,也带来了危害。对于setAccessible()方法是否会破坏类的访问规则,产生安全隐患,我在知乎上面看到的一篇回答貌似很有道理:
所以我们尽量将Autowired注解设置在公开的setter方法上。话虽这样说,但Spring大部分还是用在成员上,因为是在工具内部完成的,但还是要说清楚危害。
讲Autowired注解用在公开的setter方法上面,考虑setter方法的共性:1. 参数只有一个,且该参数类型是相关成员类型 2.无返回值
在做实验的期间,发现个很有意思的知识。是关于权限修饰符的类Modifier。在输出各个修饰符的值,发现public:1,private:2,protected:4,发现它们都是属于2的次方,这不由得想到了二进制,在类Modifier发现果然是二进制。继续横向学习Linxu的chmod命令也是这样的。在做实验,发现final:16,经过 public final修饰的值为17,从中得知原来多个权限符共同修饰的计算是 |(或运算),那当然了,在计算机位运算一定比加法快,由此得知开发Java的前辈为了使用位运算在底层做了功夫。从中也得知了学习要横向学习。Java开发者很聪明,和Linux结合了起来。
看下面这个例子
import java.util.Calendar; import com.google.gson.Gson; import com.mec.mSpring.core.Autowired; import com.mec.mSpring.core.Component; @Component public class One { @Autowired private Complex complex; @Autowired private Gson gson; //需要导包 @Autowired private Calendar calendar; //内部工具类 public One() { } public void doSomething() { System.out.println(complex); System.out.println(gson); System.out.println(calendar); } } --- @Component public class Complex { private double real; private double vir; public Complex() { } public Complex(double real, double vir) { this.real = real; this.vir = vir; } public double getReal() { return real; } public void setReal(double real) { this.real = real; } public double getVir() { return vir; } public void setVir(double vir) { this.vir = vir; } @Override public String toString() { return "Complex [real=" + real + ", vir=" + vir + "]"; } } --- import com.mec.mSpring.core.BeanFactory; public class Demo3 { public static void main(String[] args) { BeanFactory.scanPackage("com.mec.mSpring.test"); BeanFactory beanFactory = new BeanFactory(); One one = beanFactory.getBean(One.class); one.doSomething(); } } /*运行结果 bean[com.google.gson.Gson]没有定义 bean[java.util.Calendar]没有定义 Complex [real=0.0, vir=0.0] null null */上面例子的三个成员就是@Autowired注解所存在的缺陷。
缺陷一:形如Complex这种类,我们工具只能调用它的无参构造,产生的对象的值都是0,不灵活,不能自定义。 对于类对象的构造用户想个性化,或者类的对象不是简简单单通过无参构造出来后就直接能使用的,还要对这个对象做一些事(例如数据库的Connection设置)。
缺陷二:形如Gson这种jar包的类,我们知道@Autowired只能从IoC容器中获取对象,而所获取的对象所对应的类都需要有@Component注解,对于不可更改的jar包中的类我们没办法加相关注解,也就不能实现注入操作。
缺陷三:形如Calendar这种没有提供可用的构造方法;所谓的没有提供可用的构造方法包括相关构造方法是private的,或其构造方法不能直接调用的,或者相关类的对象根本不是通过无参构造产生的(例如Calendar类,其通过Calendar.getInstance()得到相关对象)。在这种情况下,因为我们工具是调用相关类的无参构造产生的,那么,对于上述情况就不能产生这个类的对象。
@Configuration注解和@Bean注解就是为了解决这种问题产生的。
import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; @Retention(RUNTIME) @Target(TYPE) public @interface Configuration { } import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; @Retention(RUNTIME) @Target(METHOD) public @interface Bean { }@Configuration注解用来标识盛放带有@Bean注解的那个类。
@Bean注解所注释的方法中,你可以更灵活的自定义对象值,将这个方法的返回值类型作为键,返回值作为值放到IoC容器中,还可以合适的调用获取该类对象的方法,这很好的解决了上面三个缺陷。
解决上面例子三个成员就可以这样写一个类。
import java.util.Calendar; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.mec.mSpring.core.Bean; import com.mec.mSpring.core.Configuration; @Configuration public class Config { public Config() { } @Bean public Complex getComplex() { return new Complex(9.8, 2.0); //这个值可以根据需求改动 } @Bean public Gson getGson() { return new GsonBuilder().create(); } @Bean public Calendar getCalendar() { return Calendar.getInstance(); } }修改BeanFactory里的代码
import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; import com.mec.util.PackageScanner; public class BeanFactory { private static final Map<String, BeanDefinition> beanPool = new HashMap<>();//IoC容器 public BeanFactory() { } public static void scanPackage(String packageName) { new PackageScanner() { @Override public void dealClass(Class<?> klass) { if (klass.isPrimitive() || klass.isArray() || klass.isInterface() || klass == String.class || klass.isEnum() || klass.isAnnotation() || (!klass.isAnnotationPresent(Component.class) && !klass.isAnnotationPresent(Configuration.class))) { return; } try { if (klass.isAnnotationPresent(Configuration.class)) { dealBean(klass); return; } Object object = klass.getConstructor(new Class[] {}).newInstance(new Object[] {}); BeanDefinition beanDefinition = new BeanDefinition(klass, object); beanPool.put(klass.getName(), beanDefinition); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } } }.scanPackage(packageName); } @SuppressWarnings("unchecked") public <T> T getBean(String beanId) { BeanDefinition beanDefinition = beanPool.get(beanId); if (beanDefinition == null) { System.out.println("bean[" + beanId + "]没有定义"); //这里可以抛运行时异常 return null; } //在第一次获得相关beanDefinition时候,进行注入,多线程在这有问题 if (!beanDefinition.isInject()) { //经典DLC问题,多线程都用这个inject,去加volatile synchronized (beanPool) { //用beanPool最好,单例的,谁都认识它 if (!beanDefinition.isInject()) { doInject(beanDefinition); beanDefinition.setInject(true); } } } return (T) beanDefinition.getObject(); } public <T> T getBean(Class<?> klass) { return getBean(klass.getName()); } public void addBean(String nickName, BeanDefinition beanDefinition) { //不想用类名称当作键,可以用别名 beanPool.put(nickName, beanDefinition); } private void doInject(BeanDefinition beanDefinition) { Class<?> klass = beanDefinition.getKlass(); Object object = beanDefinition.getObject(); injectByField(klass, object); injectBySetMethod(klass, object); } private void injectByField(Class<?> klass, Object object) { Field[] fields = klass.getDeclaredFields(); for (Field field : fields) { if (!field.isAnnotationPresent(Autowired.class)) { continue; } Class<?> fieldClass = field.getType(); Object value = getBean(fieldClass); field.setAccessible(true); //很糟糕这里,priavte的成员被打开权限了 try { field.set(object, value); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } private void injectBySetMethod(Class<?> klass, Object object) { Method[] methods = klass.getDeclaredMethods(); for (Method method : methods) { if (!method.isAnnotationPresent(Autowired.class) || !method.getName().startsWith("set") || method.getParameterCount() != 1 || method.getModifiers() != Modifier.PUBLIC) { continue; } Class<?> paraClass = method.getParameterTypes()[0]; Object value = getBean(paraClass); try { method.invoke(object, new Object[] {value}); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } private static void dealBean(Class<?> klass) { try { Object configObject = klass.getConstructor(new Class[] {}).newInstance(new Object[] {}); Method[] methods = klass.getMethods(); for (Method method :methods) { if (!method.isAnnotationPresent(Bean.class)) { continue; } int paraCount = method.getParameterCount(); Class<?> returnType = method.getReturnType(); if (paraCount != 0) { //带参数的Bean注解下的方法,这问题有点深,稍后讨论 } else { Object result = method.invoke(configObject, new Object[] {}); BeanDefinition beanDefinition = new BeanDefinition(returnType, result); beanPool.put(returnType.getName(), beanDefinition); } } } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } } } 测试 public class Demo3 { public static void main(String[] args) { BeanFactory.scanPackage("com.mec.mSpring.test"); BeanFactory beanFactory = new BeanFactory(); One one = beanFactory.getBean(One.class); one.doSomething(); } } /* Complex [real=9.8, vir=2.0] {serializeNulls:falsefactories:[Factory[typeHierarchy=com.google.gson.JsonElement gson太长了,简化后的 java.util.GregorianCalendar[time=1602233859225,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2020,MONTH=9,WEEK_OF_YEAR=41,WEEK_OF_MONTH=2,DAY_OF_MONTH=9,DAY_OF_YEAR=283,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=4,HOUR_OF_DAY=16,MINUTE=57,SECOND=39,MILLISECOND=225,ZONE_OFFSET=28800000,DST_OFFSET=0] */可以这样认为,@Configuration注解和@Bean注解为用户创造自己规定的实例化对象放入到IoC容器中提供了可能。
要注意的是,用@Bean注解获得的对象所对应的类不需要@Component注解,你都已经用方法的方式得到对象实例了,就不需要通过扫描并且反射机制去。
细心的读者可能发现了,我们对于带参数的有@Bean注解的方法并没有进行相关代码的编写。是的,因为IoC比较困难的问题之一就在这里,带参数的方法和循环依赖问题都在这里混着,我阐述相关观点又要许多文字,不希望把博文写的太长,这样会引起你们的阅读疲劳,所以有关问题放在下一篇博文。我一定保证下篇会非常精彩,希望对模拟Spring有兴趣的同学一定要看!
循环依赖问题的解决