要实现模拟IOC,要使用过spring,明白IOC的执行流程,会使用反射等
1. 通过基于注解的IOC的执行流程整理实现思路
来自楠哥视频里的图
实现步骤
根据该图,我们可以将步骤分为下列几步:
① 扫描包,获取该包下所有加了Component注解的类
② 通过反射机制获取该类的Class,即是图中的原材料的组件
③ 将获取的类的Class和Component注解中的value值封装成为一个beanDefinitions对象
④ 创建一个ioc容器,以存储bean(即是beanName,和对应的实例对象)
2. 实现
(1) 创建层级目录及文件
说明:MyAnnotationConfigApplication 为实现springIOC中的AnnotationConfigApplication
MyBeanDefinition为实现springIOC中的BeanDefinition
MyTools为扫描包的工具类
(2) 自定义要实现的IOC注解
① MyComponent 注解
//自定义一个MyComponent注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
String value() default "";
}
② MyValue 注解
//自定义Value注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyValue {
String value() default "";
}
③ MyAutowired 注解
//自定义autowired注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
}
④ MyQualifier 注解
//自定义Qualifier注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyQualifier {
String value() default "";
}
(3) MyBeanDenifition 类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyBeanDefinition {
private String beanName;
private Class beanClass;
}
使用lombok,MyBeanDefinition为模拟IOC的BeanDeFinition类
成员属性说明:
beanName 为@MyComponent注解中的value值,即是bean名,如果value为空,则使用首位变小写后的类名为beanName
beanClass 为要提供bean(实例对象)的Class(原材料)
(4) MyTools 工具类
实现扫描包的功能,返回一个set集合 包含该包下所有的类
public static Set<Class<?>> getClasses(String pack) {
// 第一个class类的集合
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
// 是否循环迭代
boolean recursive = true;
// 获取包的名字 并进行替换
String packageName = pack;
String packageDirName = packageName.replace('.', '/');
// 定义一个枚举的集合 并进行循环来处理这个目录下的things
Enumeration<URL> dirs;
try {
dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
// 循环迭代下去
while (dirs.hasMoreElements()) {
// 获取下一个元素
URL url = dirs.nextElement();
// 得到协议的名称
String protocol = url.getProtocol();
// 如果是以文件的形式保存在服务器上
if ("file".equals(protocol)) {
// 获取包的物理路径
String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findClassesInPackageByFile(packageName, filePath, recursive, classes);
} else if ("jar".equals(protocol)) {
// 如果是jar包文件
// 定义一个JarFile
System.out.println("jar类型的扫描");
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection()).getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
findClassesInPackageByJar(packageName, entries, packageDirName, recursive, classes);
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
(5) MyAnnotationConfigApplication 类 (重点)
该类的实现步骤:
① 创建一个成员变量HashMap模拟ioc容器
// 创建一个HashMap模拟ioc容器
private Map<String,Object> ioc = new HashMap<>();
② 编写 findBeanDefinition 函数,传入包名,调用工具类,获取到所有添加了MyComponent注解的类后,将其所有类(Class原型)和beanName封装为MyBeanDefinition对象集合后返回
这一步,即实现了MyComponent注解的功能
具体解释见代码注释
public Set<MyBeanDefinition> findMyBeanDefinitions(String packageName){
// 1.获取包下的所有类
Set<Class<?>> classes = com.ruoxi.MyIOC.MyTools.getClasses(packageName); //使用工具类,用set集合获取该包名下的所有类
// 使用HashSet存储BeanDefinition
Set<MyBeanDefinition> beanDefinitions = new HashSet<>();
// 2.遍历这些类,找到添加了注解的类
Iterator<Class<?>> iterator = classes.iterator();
while(iterator.hasNext()){
Class<?> clazz = iterator.next();
// 获取注解的对象
MyComponent componentAnnotation = clazz.getAnnotation(MyComponent.class);
//如果componentAnnotation != null ,则添加了该注解
if(componentAnnotation!=null){
// 获取注解的值
String beanName = componentAnnotation.value();
// 如果没有写beanName(即注解的值) 那么将类名的首字母变小写后作为beanName
if(beanName.equals("")) {
String className = clazz.getSimpleName(); //获取类名
//将类名的第一个截取变小写,然后加上后面的字符串
beanName = className.substring(0,1).toLowerCase()+className.substring(1);
}
// 3. 将这些类封装为BeanDefinition 并存入beanDefinitions
beanDefinitions.add(new MyBeanDefinition(beanName,clazz));
}
}
return beanDefinitions;
}
③ 编写 createObjects 函数,传入上一步封装好的MyBeanDefinition对象集合,在该函数中遍历该集合,利用反射机制创建bean对象,并遍历类的各个属性,以实现MyValue注解的功能
这一步,即实现了MyValue注解的功能
具体解释见代码注释
public void createObject(Set<MyBeanDefinition> beanDefinitions){
Iterator<MyBeanDefinition> iterator = beanDefinitions.iterator();
//遍历beanDefinition集合
while (iterator.hasNext()) {
MyBeanDefinition beanDefinition = iterator.next();
Class clazz = beanDefinition.getBeanClass();
// 获取beanName
String beanName = beanDefinition.getBeanName();
try {
// 利用反射机制创建bean对象 注意这里的是beanDefinition里的beanClass对象
Object object = clazz.getConstructor().newInstance();
//完成属性的赋值
Field[] fields = clazz.getDeclaredFields();
//遍历属性 判断是否添加了myValue注解
for (Field field : fields) {
MyValue myValueAnnotation = field.getAnnotation(MyValue.class);
if(myValueAnnotation != null){
//获取注解的值
String value = myValueAnnotation.value();
//通过截取属性名 拼接为方法名
String fieldName = field.getName();
String methodName = "set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
//得到设置属性的方法
Method method = clazz.getMethod(methodName,field.getType());
//完成数据类型转换
Object val = null;
//将value转化为对应的属性类型,并给属性赋值
switch (field.getType().getName()){ //这里只列举了部分类型
case "java.lang.Integer":
val = Integer.parseInt(value);
break;
case "java.lang.String":
val = value;
break;
case "java.lang.Float":
val = Float.parseFloat(value);
break;
case "java.lang.Double":
val= Double.parseDouble(value);
break;
}
method.invoke(object,val); //执行方法,给属性赋值
}
}
// 将该对象和beanName存入ioc容器
ioc.put(beanName,object);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
④ 编写getBean函数,通过传入beanName名取得bean
// 通过beanName获取bean
public Object getBean(String beanName){
return ioc.get(beanName);
}
⑤ 编写 autowireObject 函数,还是传入BeanDefinition的Set集合,遍历Set中的Class中的属性,查询是否有添加MyAutowired或MyQualifier注解,如果有,则实现对应属性(内部类)注入bean的功能
这一步,即实现了MyAutowired和MyQualifier注解的功能
具体解释见代码注释
//实现autowire和MyQualifier注解的功能
public void autowireObject(Set<MyBeanDefinition> beanDefinitions){
Iterator<MyBeanDefinition> iterator = beanDefinitions.iterator();
while (iterator.hasNext()) {
MyBeanDefinition beanDefinition = iterator.next();
//创建beanDefinition中的beanClass对象
Class clazz = beanDefinition.getBeanClass();
//遍历属性,查找是否有添加 MyAutowired或MyQualifier注解
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
MyAutowired myAutowired = field.getAnnotation(MyAutowired.class);
if(myAutowired!=null){
MyQualifier myQualifier = field.getAnnotation(MyQualifier.class);
//如果有MyQualifier注解,则为ByName方式 否则为ByType方式
if(myQualifier!=null){
try {
//通过beanName获取bean对象 这里即是内部类的对象
String beanName = myQualifier.value();
Object bean = getBean(beanName);
String fieldName = field.getName();
String methodName = "set"+ fieldName.substring(0,1).toUpperCase()+ fieldName.substring(1);
Method method = clazz.getMethod(methodName,field.getType());
//取出对象(bean)
//beanDefinition.getBeanName()即是当前的beanName
Object object = getBean(beanDefinition.getBeanName());
//执行method 将bean注入到object中,这里即是将对象的bean赋值给内部类
method.invoke(object,bean);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
else { //当只有autowired注解时 通过byType方式获取bean
Class fieldClass = field.getType();
// 通过反射获取指定的注解
MyComponent fieldClassAnnotation = (MyComponent) fieldClass.getDeclaredAnnotation(MyComponent.class);
//如果属性的类也添加了MyComponent注解 则获取属性的Class的MyComponent注解的值 并注入属性的值
if(fieldClassAnnotation != null){
String filedBeanName = fieldClassAnnotation.value();
if(filedBeanName.equals("")){ //如果属性的Component注解没有赋值,则将首字母变小写后成为beanName
String className = fieldClass.getSimpleName(); //获取属性类名
//将类名的第一个截取变小写,然后加上后面的字符串
filedBeanName = className.substring(0,1).toLowerCase()+className.substring(1);
}
// 获取该属性的bean
Object fieldBean = getBean(filedBeanName);
try
{
// 拼接字符串得到set属性的方法
String fieldName = fieldClass.getSimpleName();
String setFiledMethodName = "set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
Method method = clazz.getMethod(setFiledMethodName,field.getType());
//取出当前beanDefinition的类对象bean
//beanDefinition.getBeanName()即是当前的beanName
Object object = getBean(beanDefinition.getBeanName());
method.invoke(object,fieldBean);
}
catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
}
⑥ 编写MyAnnotationConfigAppliaction类的有参构造函数,并使用上述所有的函数来实现ioc的功能
public MyAnnotationConfigApplicationContext(String packageName){
//遍历包,找到目标类(原材料)
Set<MyBeanDefinition> beanDefinitions = findMyBeanDefinitions(packageName);
//根据原材料创建bean
createObject(beanDefinitions);
//判断是否有MyAutowired或MyQualifier注解,如有则对应实现功能
autowireObject(beanDefinitions);
}
至此,模拟SpringIOC的功能已大致完成,接下来是测试
3. 测试
(1) 创建俩个类并加上自定义注解
① TestIOC.class
@Data
@AllArgsConstructor
@NoArgsConstructor
@MyComponent
public class TestIOC {
@MyValue("123")
private Integer id;
@MyValue("小明")
private String name;
@MyAutowired
// @MyQualifier("zi")
private Zi zi;
}
② Zi.class
@Data
@NoArgsConstructor
@AllArgsConstructor
@MyComponent
public class Zi {
@MyValue("123456")
private Integer Ziid;
@MyValue("打工人")
private String Ziname;
}
(2) 在main方法中进行测试,打印输出getBean(“testIOC”)
① 不在TestIOC类中给内部类Zi属性添加MyQualifier注解,只单用MyAutowired注解
@MyAutowired
// @MyQualifier("zi")
private Zi zi;
public class Test {
public static void main(String[] args) {
MyAnnotationConfigApplicationContext myAnnotationConfigApplication = new MyAnnotationConfigApplicationContext("com.ruoxi.entity");
System.out.println(myAnnotationConfigApplication.getBean("testIOC"));
}
}
通过打印结果可以看见,成功取出了名为"testIOC"的bean,且各个属性均已赋值,说明@MyComponent注解生效,@MyValue注解也已生效
同时内部类zi也被注入了值,说明Zi类的bean也注入,且TestIOC类的成员变量Zi被注入了这个bean值,说明@MyAutowired注解生效
② 在TestIOC类中给内部类Zi属性添加上MyQualifier注解
@MyAutowired
@MyQualifier("zi")
private Zi zi;
同样成功打印,说明MyQualifier注解也生效
至此,模拟SpringIOC已完成