前言
Spring ioc 相信很多人都知道这是Spring框架中一个非常核心的组件,IoC(控制反转),对于初学Spring的人来说,对其的设计思想理解可能非常表面,要理解好Ioc的关键是要明确 以下几点,谁控制谁,控制什么,为何是反转(那正转?),哪些方面反转了 。
谁控制谁,控制什么 还记得你们当时学习servlet的时候在每一层内部通过new关键字进行创建对象吗,要清楚这是程序主动去创建依赖对象; 而当你们接触了Spring之后IoC是有专门一个容器来创建这些对象,控制对象。
为何是反转(那正转?),这个简单理解当我们主动控制去获取依赖对象,这就是正转? 而反转则是由容器来帮忙创建及注入依赖对象; 为何是反转 ? 因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转; 哪些方面反转了?依赖对象的获取被反转了。
手撕最简易版IOC容器
可能你还不明白,觉得很抽象?那么跟着小编手把手带你来实现IOC!
ioc不是一个容器嘛,ok,我们先来定义一个容器,容器的特性肯定拥有存、取对象嘛~
Spring的对象都是一个个bean,但是我们不知道bean类型是什么,那么就泛型体现,还有bean什么时候存放进容器的啊?spring是在启动的时候会进行初始化扫描,我们就定义一个initAutoWired方法来模拟。
先定义一个橘松容器接口JsContainer。
/**
* @创建人 : 头条账号 "深夜敲代码"
* @创建时间 2021/7/13
*/
public interface JsContainer {
/**
* 根据Class获取Bean
* @param clazz
* @return
*/
<T> T getBean(Class<T> clazz);
/**
* 注册一个Class到容器中
*
* @param clazz
*/
Object registerBean(Class<?> clazz);
/**
* 初始化装配
*/
void initAutoWired();
我们再写一个自定义注解标识JsAutowired,来为了后面通过反射获取实例化bean,自定义注解可以指定要注入的类型,以及注入的bean名称。
package com.orangesongjava.ioc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @创建人 : 头条账号 "深夜敲代码"
* @创建时间 2021/7/13
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsAutowired {
/**
* @return 要注入的类类型
*/
Class<?> value() default Class.class;
/**
* @return bean的名称
*/
String name() default "";
}
容器接口和注解都有了,接下来我们思考来实现它!我们定义一个类 JsSampleContainer 来实现容器接口JsContainer 。
package com.orangesongjava.ioc;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @创建人 : 头条账号 "深夜敲代码"
* @创建时间 2021/7/13
*/
@SuppressWarnings("unchecked")
public class JsSampleContainer implements JsContainer {
/**
* 保存所有bean对象,格式为 com.xxx.xxx.XxxClass : @56x2ya
*/
private Map<String, Object> beanNameMap;
/**
* 存储bean和name的关系
*/
private Map<String, String> beanKeys;
public JsSampleContainer() {
this.beanNameMap = new ConcurrentHashMap<>();
this.beanKeys = new ConcurrentHashMap<>();
}
@Override
public <T> T getBean(Class<T> clazz) {
String name = clazz.getName();
Object object = beanNameMap.get(name);
if(null != object){
return (T) object;
}
return null;
}
@Override
public Object registerBean(Class<?> clazz) {
String name = clazz.getName();
beanKeys.put(name, name);
Object bean = newInstance(clazz);
beanNameMap.put(name, bean);
return bean;
}
@Override
public void initAutoWired() {
beanNameMap.forEach((k,v) -> injection(v));
}
/**
* 注入对象
* @param object
*/
public void injection(Object object) {
// 所有字段
try {
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
// 需要注入的字段
JsAutowired jsAutowired = field.getAnnotation(JsAutowired.class);
if (null != jsAutowired) {
// 要注入的字段
Object autoAutoWiredField = null;
String name = jsAutowired.name();
// 如果说这里JsAutowired自定义注解没指定name属性 则默认值是""
if(!name.equals("")){
// 指定了特定的name
String className = beanKeys.get(name);
if(null != className && !className.equals("")){
autoAutoWiredField = beanNameMap.get(className);
}
if (null == autoAutoWiredField) {
throw new RuntimeException("Unable to load " + name);
}
} else {
// JsAutowired注解没有name属性
// 判断注入的类型 是否是类Class类型 默认值也是Class
if(jsAutowired.value() == Class.class){
autoAutoWiredField = register(field.getType());
} else {
// 指定装配的类
autoAutoWiredField = this.getBean(jsAutowired.value());
if (null == autoAutoWiredField) {
autoAutoWiredField = register(jsAutowired.value());
}
}
}
if (null == autoAutoWiredField) {
throw new RuntimeException("Unable to load " + field.getType().getCanonicalName());
}
boolean accessible = field.isAccessible();
field.setAccessible(true);
field.set(object, autoAutoWiredField);
field.setAccessible(accessible);
}
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private Object register(Class<?> clazz){
if(null != clazz){
return this.registerBean(clazz);
}
return null;
}
/**
* 创建一个实例对象
* @param clazz class对象
* @return
*/
public static Object newInstance(Class<?> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
测试一下
我们现在来测试一下,我们先定义一个类OrangeSongJavaService,方便待会测试被调用。注意哦,这个类上面没有任何注解。也就是他并没有被Spring管理起来。
/**
* @创建人 : 头条账号 "深夜敲代码"
* @创建时间 2021/7/13
*/
package com.orangesongjava.ioc;
public class OrangeSongJavaService {
public void writeArticleOnJs(String name){
System.out.println(name + "在头条上写文章!");
}
}
在定义本地调用的模拟调用OrangeSongJavaClient。注意这个类上面也是没有注解,另外引用的OrangeSongJavaService 被我们的自定义注解JsAutowired标识起来了。
/**
* @创建人 : 头条账号 "深夜敲代码"
* @创建时间 2021/7/13
*/
package com.orangesongjava.ioc;
public class OrangeSongJavaClient {
@JsAutowired
private OrangeSongJavaService orangeSongJavaService;
public void work() {
orangeSongJavaService.writeArticleOnJs("深夜敲代码");
}
public OrangeSongJavaService getOrangeSongJavaService() {
return orangeSongJavaService;
}
}
现在我们写个测试main方法测试一下。
/**
* @创建人 : 头条账号 "深夜敲代码"
* @创建时间 2021/7/13
*/
package com.orangesongjava.ioc;
public class IocTest {
private static JsSampleContainer jsContainer = new JsSampleContainer();
public static void main(String[] args) {
// 将类注入容器
jsContainer.registerBean(OrangeSongJavaClient.class);
// 初始化注入-扫描引用
jsContainer.initAutoWired();
// 容器获取bean
OrangeSongJavaClient client jsContainer.getBean(OrangeSongJavaClient.class);
// 执行
client.work();
}
}
如果能输出执行逻辑,则表示这段程序没问题。看到这里,你对ioc理解是不是有点清晰了,有任何疑问,或者不懂的地方可以下方留言。