目录
(1)什么是IoC
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配置和注解。
(2)XML配置和注解区别
首先说明通过XML文件配置完成的注解也可以完成,注解完成的,XML也可以完成。两者各有利弊,要编程者自己斟酌用哪个。
XML配置:
优点:相关配置文件是独立于代码的,不侵害和更改代码,不用改代码就不需要测试。保证了开闭原则(对修改关闭,对扩展开放)。
缺点:如果相关类的关系很复杂,其就需要大量配置文件的抒写。(早些年的配置文件很长,一度被人们称为“配置地狱”)
注解方式:
优点:简单,明了,方便,程序可读性强,无需多写相关配置文件,使得开发效率高。
缺点:对注解进行更改,就需要进行重新的全面的严格的测试,因为修改了内部代码,一经改动就需进行大量测试以得到更改的正确性。
总而言之,条条大路通罗马,不拘一格降人才,XML和注解都可以解决问题,我们先学一样,再去找他们的相通之处就能学习到更多知识。
(3)IoC容器的模拟实现
注解:@Component注解
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 Component {
}
@Component
注解:在类型上面使用的,告知包扫描工具,说明该类型的对象要加到IoC容器里面去,是该容器的一个组件。
注解:@Autowired注解
import static java.lang.annotation.ElementType.FIELD;
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({ FIELD, METHOD })
public @interface Autowired {
}
@Autowired
注解:该注解用于成员或成员设置方法(setter)的上面,其目的是对成员进行注入,这个成员的对象是从IoC容器中取出来。因此就要求被@Autowired
注解标识的成员其类型必须有@Component
注解。
BeanDefinition类
public class BeanDefinition {
private Class<?> klass; //那个类
private Object object; //那个类实例化对象,只放一份在ioC容器中
private volatile boolean inject; //是否注入了
BeanDefinition() {
this(null, null);
}
BeanDefinition(Class<?> klass, Object object) {
this.klass = klass;
this.object = object;
this.inject = false;
}
Class<?> getKlass() {
return klass;
}
void setKlass(Class<?> klass) {
this.klass = klass;
}
Object getObject() {
return object;
}
void setObject(Object object) {
this.object = object;
}
boolean isInject() {
return inject;
}
void setInject(boolean inject) {
this.inject = inject;
}
}
该类主要描述一个有@Component
注解的组件,将其类型和实例化对象本身封装起来形成一个豆子。
BeanFactory类
import java.lang.reflect.InvocationTargetException;
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);
/*看似任务把相关组件放到IoC容器中完成了,但实际上,该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 bd = beanPool.get(beanId);
if (bd == null) {
System.out.println("bean[" + beanId + "]没有定义"); //这里可以抛运行时异常
return null;
}
return (T) bd.getObject();
}
public <T> T getBean(Class<?> klass) {
return getBean(klass.getName());
}
public void addBean(String nickName, BeanDefinition beanDefinition) { //不想用类名称当作键,可以用别名
beanPool.put(nickName, beanDefinition);
}
}
前面说了那么久的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();
}
}
}
}
Autowired注解建议应该在方法上而不是成员上
前面实现的代码我们可以看到,对于私有成员的注入,用到了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结合了起来。
注解:@Configuration注解和@Bean注解配合
看下面这个例子
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有兴趣的同学一定要看!