温馨提示:完整代码已上传至(https://github.com/Monkey-yc/IOC),欢迎访问。
近期学习Spring框架,其核心技术乃AOP(见上篇)和IOC。前段时间经过画图和自己尝试实现AOP,发现加深了不少理解,因此尝试着编写了IOC,在此分享心得:
IOC,翻译过来大家把他叫控制反转,那么究竟是控制什么,什么被反转。我的理解如此:先举个例子:双十一快到了,大家都在网上买东西,一般买东西都是下单后卖家从本地发快递,一般需要三到七天,就算顺丰航空速递,也不能改变多大。但是京东在全国各大城市建了一个仓库,他把货物预先存在仓库中,而这个仓库负责周边地区的货物供应,一般离得不是很远的一天就能送到,而同城的当天到达。
IOC就是这个意思:大家都知道成员需要赋值,平时都是在使用的时候再给成员进行赋值。而IOC就像这个仓库,他把你加了注解的方法都会产生对象,存在这个仓库里边,在你用的时候他直接注入,由此大大方便了用户,也提高了效率。
根据我的理解,画了思维结构图如下(图丑,说明问题即可):
图中画虚线的是假调用,真正的调用是将BeanDepot类中的Map得到对象,注入到该成员的。
下面来说说核心类的实现:
首先需要说明三个注解, 这里注解的命名模仿了Spring:
@Component:此注解添加给类,表明该类中有需要注入的成员或需要扫描的方法。说白了就是这个类里边有成员加了@Autowried注解或者有方法加了@Bean方法。
@Bean:这个注解是给方法加的,加了这个注解的方法表示需要被扫描且执行产生对象存进IOC仓库中以备注入时使用。
@Autowried:这个注解是给成员加的,加了这个注解表明这个成员的值会被从IOC容器自动注入,无需初始化即可使用。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Bean {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}
下面先来说说BeanFactory这个类,该类的作用就是生成对象的一个工厂,通过包扫描,扫描带有@Component注解的类,自动给其生成对象,并存储到BeanDepot类的仓库;
因为在实际操作中,类都是封装好的,你不可能去添加注解生成对象,
因此需要写一些带有@Bean注解的方法,通过反射执行带有@Bean注解的方法生成对象
存储到BeanDepot类的仓库中以备注入。
这是包扫描的方法:
public static void scanPackage(String packageName) {
BeanDepot beanDepot = new BeanDepot();
new ScannerPackage() {
@Override
public void dealClass(Class<?> klass) {
if (!klass.isAnnotationPresent(Component.class)) {
return;
}
try {
createBean(beanDepot, klass, null);
Object object = beanDepot.getYcBean(klass).getObject();
Method[] methods = klass.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(Bean.class)) {
continue;
}
invokeBeanMethod(beanDepot, method, object);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.scanPackage(packageName);
}
这是将生成的对象存入到仓库的方法:
//将产生的Object存储进Beandepot仓库
private static void createBean(BeanDepot beanDepot, Class<?> klass, Object object)
throws Exception{
if (object != null) {
beanDepot.addBeanMap(klass, object);
} else {
beanDepot.addBeanMap(klass, klass.newInstance());
}
}
在仓库中以类名为键,以YcBean为值存储对象。下面来说一说这个YcBean这个类。
这个类虽然很简单,但是很重要。他的两个成员,一个就是生成的对象,另一个是一个Boolean类型的,可以说这个boolean类型的成员是该类的一个属性,用来判断该类是否已经被注入了成员的值。将这个属性和生成的对象一起打包封装成一个类,装在仓库中的Map中,可以在注入时调出这个属性判断该类是否已经注入过了,如果注入过了,那就不再进行注入了,如果没有注入,那么继续注入。由此实现了依赖循环的注入。
public class YcBean {
private Object object;
private Boolean isinjection;
YcBean() {
isinjection = false;
}
Object getObject() {
return object;
}
void setObject(Object object) {
this.object = object;
}
Boolean getisInjection() {
return isinjection;
}
void setisInjection(Boolean isinjection) {
this.isinjection = isinjection;
}
}
下面再来看看IOC容器,BeanDepot这个仓库:这个仓库中以Map来存储类名和相对应得YcBean。
通过工厂中的包扫描来添加这个Map。仓库中还给了添加和删除的方法来操作Map。最重要的是实现了注入,根据传递过来的类寻找加了@Autowried注解得成员,如果没有找到有加了@Autowried注解的方法,就说明这个类没有成员需要注入,直接返回。
遇到需要注入的类,将该类对应的是否注入属性设置为true,下次如果在执行到这个类,可以直接使用而不用再注入。
处理完上述事情,就可以来处理带有@Autowried注解的成员了。取得这个成员的类型,看这个类中有没有需要注入的成员,如果有,应该先处理这个成员的类型的类,然而该类中需要处理的成员的类还有成员需要注入呢?很明显此处应使用递归解决。判断如果成员的类中有需要注入的成员,那就在此调用注入的方法,将该成员的类型及其对象传过去。
然处理完成员类的注入后,就可以给该成员注入值了,将该成员类型的对象注入到该成员的对象,即可大功告成。
然后在用户调用的时候,从Map中取出该类对应得YcBean,再从YcBean中取出判断是否注入过得属性值,如果未true,则该类已经被注入过了,直接返回。如果为false,就执行注入的方法给该成员进行赋值,给外界提供了一个得到值得接口。
下面是注入的方法:
private static void injection(Class<?> klass, Object object) {
Field[] fields = klass.getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(Autowired.class)) {
return;
}
//表示该类已被注入过;
beanMap.get(klass.getName()).setisInjection(true);
Class<?> fieldType = field.getType();
YcBean fieldTypeBean = beanMap.get(fieldType.getName());
Object fieldobject = fieldTypeBean.getObject();
//如果成员的类类型需要注入,即成员的类型在被加载时发现他的类中需要注入另一个
//成员,其才能被成功加载。同理,在加载其成员的类的成员时,成员的成员也需要注入成员
// 故此处应使用递归处理。
if(!fieldTypeBean.getisInjection()) {
injection(fieldType, fieldobject);
}
field.setAccessible(true);
try {
field.set(object, fieldobject);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
给外界提供赋值的接口:
public static Object getBean(Class<?> klass) {
YcBean ycBean = beanMap.get(klass.getName());
if(!ycBean.getisInjection()) {
injection(klass, ycBean.getObject());
}
return ycBean.getObject();
}
我给StudentDao类直接赋了值,接下来就测试一下
赋值的StudentDao:
public StudentModel getStuById(String id) {
StudentModel stu = new StudentModel();
stu.setId("12138");
stu.setName("张三");
stu.setPasswd("123456");
return stu;
}
测试类如下:
public class Test {
public static void main(String[] args) {
BeanFactory.scanPackage("com.yc.ioc.student");
StudentAction studentAction = (StudentAction) BeanDepot.getBean(StudentAction.class);
StudentModel student = studentAction.getStudent("12138");
System.out.println(student);
}
}
可以对照上边的图好好推敲一下这个测试,StudentAction类调用了getStudent()方法,在此方法内部,使用studentDao调用了Dao的方法getStuById()方法,但是studentDao并没有初始化赋值,而是通过IOC容器给注入的值,输出如下:
还有依赖循环的测试在此就不多说了,原理上边已经说清楚了,有兴趣的朋友可以去GitHub。
大概核心内容就这么多,我将我编写IOC时认为重要的地方也都在上文做了详细说明。源代码见文首温馨提示。也希望您留下宝贵的意见,共同进步!