概述
本文主要关注Spring IOC部分
将需要实例化的类提前交给Spring IOC容器,并且将类关联的类也实例化并且赋值给关联的类(称为依赖注入)。懒汉模式呢就是当我们需要这个类的时候才会去实现依赖注入。
这次主要看的是IOC也就是控制反转。
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
(抄写百度百科,有关于Spring框架的IOC定义)
我们采用的是懒汉模式,就是先将扫描到的类先放进去,并不注入,等我开始取对象的时候再开始注入。
既然要保存那么我们就需要建立一个工厂来保存。我用的是注解的方式,当然如果你喜欢XML文件的方式也是可以的。
我们需要三个注解,@Component,@Autowire,@Bean。
- @Component需要实例化的类
- @Autowire写在成员的身上,需要实例化的类的关联类(八大基本类型就没必要了啊)
- @Bean这个是写在方法上面的(这个有一些复杂稍后再解释,和jar包有关)
像这样
@Component
public class OneKlass {
@Autowired
private Complex complex;
@Autowired
private MecPoint point;
写好之后让我们来建一个工厂。(BeanFactory)工厂里面有一个map叫beanPool,它的键为类的名字(String类型的),值为BeanDefinition(一个类)。
BeanDefinition里就是这些东西,还有这些成员的get和set方法。
private Class<?> klass;
private Object object;
private boolean inject;
klass是带有@Component注解的类,object是这个类的对象。inject是判断这个类是否被注入,就是说这个类里的带有@Autowire注解的成员是否被赋值(也可以说是否被实例化成一个对象)。
第一件事当然是进行包扫描了。将扫描到的带有@Component注解的类打扮一下,以这个类的名字为键,再构建一个BeanDefinition为值放入beanPool中就好了。
public class BeanFactory {
private static final Map<String, BeanDefinition> beanPool;
static {
beanPool = new HashMap<String, BeanDefinition>();
}
public BeanFactory() {
}
public void scanBeanByPackage(String packageName) {
ParameterDependance pd = new ParameterDependance();
OnReadyMethodBeanDefinition ormbd = new OnReadyMethodBeanDefinition();
new PackageScanner() {
@Override
public void dealClass(Class<?> klass) {
if(klass.isAnnotation()
|| klass.isEnum()
|| klass.isInterface()
|| klass.isPrimitive()
|| klass.isArray()
|| !klass.isAnnotationPresent(Component.class)) {
return;
}
Object object = null;
try {
object = klass.newInstance();
//构建一个BeanDefinition
BeanDefinition bd = new BeanDefinition();
bd.setKlass(klass);
bd.setObject(object);
beanPool.put(klass.getName(), bd);
} catch (Exception e) {
e.printStackTrace();
}
//这里只收集bean,并不对bean做任何处理。
collectBean(klass, object, ormbd);
}
}.packageScanner(packageName);
//这里才对bean进行处理
pd.dealDependance(ormbd);
dealOnreadyBean(ormbd, pd);
}
我们谈谈注入,当执行getBean方法的时候,就要开始注入了,这个时候已经是所有的扫描结果结束,该放在map里面的已经都在了。(不可能这么简单的)
a类getBean的时候,我们返回它的对象结果,但是如果这个类里有需要我们实例化的成员怎么办,所以就需要我们在得到这个类的对象的时候,同时也要对其需要注入的成员进行注入。这样返回的结果就是一个有灵魂的对象。
设想一种情况,两个类,a和b都有@Component注解,然后a里面有b,b里面有a。这样子去注入的话,如果不加以判断,就会产生循环依赖。因为注入的过程是一个递归啊。
准备注入,先判断inject,为false,先将其设置为true(表示注入了,但是这样做是很危险的,万一后面注入的代码没有正常运行,那就完了),然后去注入。在injectFieldToKlass这个方法里,我们可以得到一个类的所有成员,反射机制啊,我们要注入当然要找需要注入的,就是那些带有@Autowire注解的成员。得到这个成员以后,我们就开始调用getBean方法,看到没有,递归就形成了。但不同的是,这个成员在要注入的时候会判断自己注入过没有,没有就继续,注入过的话就直接返回。这就避免了循环依赖。
private void injectFieldToKlass(BeanDefinition bd) {
Class<?> klass = bd.getKlass();
Object obj = bd.getObject();
Field[] fields = klass.getDeclaredFields();
for(Field field : fields) {
if(!field.isAnnotationPresent(Autowired.class)) {
continue;
}
field.setAccessible(true);
Object value = getBean(field.getType());
try {
field.set(obj, value);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
bd.setInject(true);
}
/*
* getBean();执行的时候,我们就要开始注入了。
*/
@SuppressWarnings("unchecked")
public <T> T getBean(String klassName) {
BeanDefinition beanDefinition = getBeanDefination(klassName);
if(beanDefinition == null) {
throw new RuntimeException(klassName + "该类没有对应的Bean");
}
Object object = beanDefinition.getObject();
//判断该类是否被注入,若没有,才进行注入。注入了就直接返回。被用来解决循环依赖
if(!beanDefinition.isInject()) {
beanDefinition.setInject(true);
injectFieldToKlass(beanDefinition);
}
return (T) object;
}
public <T> T getBean(Class<?> klass) {
return getBean(klass.getName());
}
public BeanDefinition getBeanDefination(Class<?> klass) {
String klassName = klass.getName();
return beanPool.get(klassName);
}
//getBeanDefination();方法只是得到beanDefinition,并不注入。
public BeanDefinition getBeanDefination(String klassName) {
return beanPool.get(klassName);
}
还有最后一个问题,如果我们要给一个jar包自动生成对象,怎么办
总不可能去更改源代码吧,于是我们有一个办法,把生成jar包对象写成一个方法,然后反射调用这个方法,我们就可以得到他的对象了,再把这些对象也放到beanPool里,然后就算注入的时候成员是jar包形式的也不怕了。
别忘了注入一定是在所有的收集工作完成以后才开始。
假如MecPoint是jar包形式的,就像这样来实例化他的对象。
@Component
public class BeanJar {
@Bean
public MecPoint getPoint() {
return new MecPoint();
}
@Bean
public Complex getComplex(OneClass oc){
return new Complex();
}
}
OK,让我们来收集@Bean,有两种情况。一种是无参的,这种可以直接调用生成对象。还有一种是带参的,若是参数可以在工厂里找到,那便是极好的,可要是他的参数也是一个@Bean,那就要先放一放了。带参也有可能是多个参数。那怎么办呢?
/*
* 带有bean注解的method
* 方法中的参数个数不定,我们需要知道参数个数
*/
public class BeanMethodDefinition {
private Class<?> klass;
private Object object;
private Method method;
private int paraCount;
构建一个BeanMethodDefinition,类是放有@Bean注解方法的类,object就是这个类的对象,method就是带有@Bean注解的方法,paraCount就是参数的个数。
我们将参数与方法形成一种对应关系,存在这样一种情况,一个参数可能对应多个方法,于是就变成参数与方法列表形成对应关系,做一个map,以参数类型为键,方法列表为值。
我们在收集@Bean的时候,若是无参就放到一个List里面,泛型类型就是BeanMethodDefinition,这里面放的都是可以执行的方法,后期map里面有了可以执行的方法也放到list里面来。
/*
* 这个类里放的都是已经准备好了的,可以直接调用产生对象的方法。
* 他的功能,存放这些已经准备好的方法,可以再次加入准备好的方法,可以删除已经实现的方法。查看是否存在还有
* 没有实现的方法。
* 我们取的时候从第一个取,存的时候从末尾加。
*/
public class OnReadyMethodBeanDefinition {
private List<BeanMethodDefinition> onReadyMethods;
OnReadyMethodBeanDefinition() {
onReadyMethods = new LinkedList<BeanMethodDefinition>();
}
void in(BeanMethodDefinition bmd) {
onReadyMethods.add(bmd);
}
boolean hasNext() {
return !onReadyMethods.isEmpty();
}
BeanMethodDefinition next() {
return onReadyMethods.remove(0);
}
}
有参就放到map里。放到paraDependance这个map中。
/*
* 这个类里的map用来存放那些暂时还无法实现的方法,带有参数的,而参数还没有找到。
*/
public class ParameterDependance {
private static final Map<Class<?>, List<BeanMethodDefinition>> paraDependance;
static {
paraDependance = new HashMap<Class<?>, List<BeanMethodDefinition>>();
}
//一个参数可能对应多个方法.参数与方法的对应。 将找到的带有bean注解的方法放入map
//如果返回值是false,那么说明是无参,就可以放入onReady。反之,则放入本类的map中
boolean putParameter(BeanMethodDefinition bmd) {
Method method = bmd.getMethod();
Parameter[] parameters = method.getParameters();
if(parameters.length <= 0) {
return false;
}
for(Parameter para : parameters) {
Class<?> type = para.getType();
if(!paraDependance.containsKey(type)) {
paraDependance.put(type, new ArrayList<BeanMethodDefinition>());
}
List<BeanMethodDefinition> beanMethodList = paraDependance.get(type);
beanMethodList.add(bmd);
}
return true;
}
//传递过来的参数,在map中遍历一遍,若是存在依赖关系,参数关系减一,并删除这种依赖关系
/*
* 若是参数个数为零,那么就将这个方法放入onReady,若是List<BeanMethodDefinition>为零,那么就将map
* 中的这个键值对删掉。
* 每实现一个bean方法,得到该对象将其放入工厂中,并检查map中有没有可以放入onReady里的方法。
*/
void dealDependance(Class<?> klass, OnReadyMethodBeanDefinition orbd) {
List<BeanMethodDefinition> beanMethodList = paraDependance.get(klass);
if(!paraDependance.containsKey(klass)) {
return;
}
if(beanMethodList == null) {
return;
}
for(BeanMethodDefinition beanMethod : beanMethodList) {
int count = beanMethod.decreaseParaCount();
if(count == 0) {
orbd.in(beanMethod);
}
beanMethodList.remove(beanMethod);
if(beanMethodList.isEmpty()) {
paraDependance.remove(klass);
}
}
}
/*
* 这样的做法工作量最小,先将工厂里的对象遍历一遍,将map里能解决的方法放入onReady里
* 然后再反射实现onReady里的方法
*/
void dealDependance(OnReadyMethodBeanDefinition orbd) {
BeanFactory bft = new BeanFactory();
for(Class<?> klass : paraDependance.keySet()) {
if(bft.getBeanDefination(klass.getName()) != null) {
dealDependance(klass, orbd);
}
}
}
}
我们在得到所有的bean方法时,把他们分为两类,一类是无参的,一类是有参的。然后放在不同的地方,等到所有的bean方法也收集完成后。
先处理工厂里是否存在可以解决bean方法的参数类型,将paraDependance这个map中的参数类型遍历一遍,若是有对应关系,则方法列表里面的每一个方法参数个数减一,并删除这种对应关系。判断参数个数是否为零,为零,那么就将这个方法放入list里面。判断方法列表是否为空,若为空,说明这个参数对应的方法已经全部解决完了,那么就删除这种对应关系,从paraDependance这个map中。也就是上面的**void dealDependance(OnReadyMethodBeanDefinition orbd)**这个方法。
dealOnreadyBean方法:
然后就是list里面的bean方法了,每解决完一个bean方法,我们就调用上面的 void dealDependance(Class<?> klass, OnReadyMethodBeanDefinition orbd) 这个方法,将得到的对象和OnReadyMethodBeanDefinition传过去。
private void collectBean(Class<?> klass, Object object,
OnReadyMethodBeanDefinition ormbd) {
ParameterDependance parameter = new ParameterDependance();
Method[] methods = klass.getMethods();
for(Method method : methods) {
if(!method.isAnnotationPresent(Bean.class)) {
continue;
}
BeanMethodDefinition bmd = new BeanMethodDefinition();
bmd.setKlass(klass);
bmd.setMethod(method);
bmd.setObject(object);
if(!parameter.putParameter(bmd)) {
ormbd.in(bmd);
}
}
}
/*
* 如果参数是bean方法里面的,这个模式也可以解决。
*/
private Object[] getParas(Method method) {
Parameter[] paras = method.getParameters();
int paraCount = paras.length;
Object[] values = null;
if(paraCount <= 0) {
return values;
}
int i = 0;
values = new Object[paraCount];
for(Parameter para : paras) {
BeanDefinition bd = getBeanDefination(para.getType());
if(bd != null) {
values[i++] = bd.getObject();
}
}
return values;
}
private void dealOnreadyBean(OnReadyMethodBeanDefinition ormbd, ParameterDependance pd) {
while(ormbd.hasNext()) {
BeanMethodDefinition bmd = ormbd.next();
Method method = bmd.getMethod();
Object obj = bmd.getObject();
Object[] paras = getParas(method);
try {
Object object = method.invoke(obj, paras);
Class<?> objectClass = object.getClass();
BeanDefinition bd = new BeanDefinition();
//因为是jar包形式的,没有办法对其进行注入,这还是一个问题。
bd.setInject(true);
bd.setKlass(objectClass);
bd.setObject(object);
beanPool.put(objectClass.getName(), bd);
pd.dealDependance(objectClass, ormbd);
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
e.printStackTrace();
}
}
}
感谢微易码教主的指导。
最后
以上就是生动小霸王为你收集整理的Spring IOC实现原理----------懒汉模式的全部内容,希望文章能够帮你解决Spring IOC实现原理----------懒汉模式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复