手写一个简易版的spring ioc容器
步骤
1.创建注解
2.提取标记对象
3.实现容器
4.依赖注入
创建注解
创建4个注解,用来自动装配
package org.simpleframework.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author jiangli
*/
@Target(ElementType.TYPE) // 作用在类上
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
提取标记对象
实现思路:
1.指定扫包范围,获取范围内的所有类
2.遍历所有类,获取被注解标记的类并加载进容器
extractPackageClass需要完成的事情:
1.获取到类的加载器
2.通过类加载器获取到加载的资源信息
3.根据不同的资源类型,采用不同的方式获取资源的集合
package org.simpleframework.util;
import java.io.File;
import java.io.FileFilter;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
/**
* @author 江黎
* @date 2022-01-29
*/
public class ClassUtil {
/**
* 文件协议
*/
public static final String FILE_PROTOCOL = "file";
/**
* 字节码文件
*/
public static final String CLASS_SUFFIX = ".class";
/**
* 获取当前包所有类的集合
*
* @param packageName
* @return
*/
public static Set<Class<?>> extractPackageClass(String packageName) {
// 1.获取到类的加载器
ClassLoader classLoader = getClassLoader();
// 2.通过类加载器获取到加载的资源信息
URL url = classLoader.getResource(packageName.replace(".", "/"));
if (url == null) {
System.out.println("unable to retrieve anything from package " + packageName);
return null;
}
// 3.根据不同的资源类型,采用不同的方式获取资源的集合
Set<Class<?>> classSet = null;
// 过滤出文件类型的资源
if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)) {
classSet = new HashSet<>();
File packageDirectory = new File(url.getPath());
extractClassFile(classSet, packageDirectory, packageName);
}
// else { } TODO 此处还可以加入对其他类型资源的处理
return classSet;
}
/**
* 递归获取目标package里面的所有class文件(包括子package)
* @param classSet 装载目标类的集合
* @param fileSource 文件或者目录(扫包的目录)
* @param packageName 包名
*/
private static void extractClassFile(Set<Class<?>> classSet, File fileSource, String packageName) {
// 不是一个目录则直接结束递归方法
if (!fileSource.isDirectory()) {
return;
}
// 如果是一个文件夹,则调用listFiles方法获取文件夹下的文件或文件夹
File[] files = fileSource.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isDirectory()) {
return true;
} else {
// 获取文件的绝对路径
String absolutePath = file.getAbsolutePath();
// 判断是不是字节码文件
if (absolutePath.endsWith(CLASS_SUFFIX)) {
// 若是class文件,则放入classSet
addToClassSet(absolutePath);
}
}
return false;
}
// 根据class文件的绝对路径,获取并生成class对象,并放入classSet中
private void addToClassSet(String absolutePath) {
// 1.从class文件的绝对路径里提取出包含了package的类名
absolutePath = absolutePath.replace(File.separator, ".");
// 去掉项目路径
String className = absolutePath.substring(absolutePath.indexOf(packageName));
// 去掉.class后缀
className = className.substring(0, className.lastIndexOf("."));
// 2.通过反射获取对应的Class对象并放入到classSet中
try {
Class<?> targetClass = Class.forName(className);
classSet.add(targetClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
});
if (files != null && files.length != 0) {
for (File file : files) {
// 递归调用
extractClassFile(classSet, file, packageName);
}
}
}
/**
* 获取ClassLoader
*
* @return
*/
public static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
public static void main(String[] args) {
Set<Class<?>> classSet = extractPackageClass("com.imooc.entity");
System.out.println();
}
}
实现容器
容器的组成部分
1.保存Class对象及其实例的载体
2.容器的加载
3.容器的操作方式
实现容器的加载
1.配置的管理与获取
2.获取指定范围内的Class对象
3.根据配置提取Class对象,连同实例一并存入容器
package org.simpleframework.core;
import org.simpleframework.core.annotation.Component;
import org.simpleframework.core.annotation.Controller;
import org.simpleframework.core.annotation.Repository;
import org.simpleframework.core.annotation.Service;
import org.simpleframework.util.ClassUtil;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author 江黎
* @date 2022-01-30
*/
public class BeanContainer {
/**
* 存放所有被特定注解(Component,Controller,Service等)标记的对象的Map
*/
private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();
/**
* 配置的管理与获取
* 需要被容器管理的bean的注解列表
*/
public static final List<Class<? extends Annotation>> BEAN_ANNOTATION_LIST = Arrays.asList(
Component.class, Controller.class, Repository.class, Service.class
);
/**
* 容器是否已经加载过bean
*/
private boolean loaded = false;
private BeanContainer() {
}
/**
* 获取Bean容器实例
* @return
*/
public static BeanContainer getInstance() {
return BeanContainerHolder.HOLDER.instance;
}
private enum BeanContainerHolder {
/**
* holder
*/
HOLDER;
private final BeanContainer instance;
BeanContainerHolder() {
instance = new BeanContainer();
}
}
/**
* 扫描加载所有bean
* @param packageName 需要扫描的包名
*/
public synchronized void loadBeans(String packageName) {
if (loaded) {
return;
}
Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
if (classSet == null && classSet.isEmpty()) {
System.out.println("extract nothing from package " + packageName);
return;
}
for (Class<?> clazz : classSet) {
for (Class<? extends Annotation> annotation : BEAN_ANNOTATION_LIST) {
// 如果类上面标注了指定的注解
if (clazz.isAnnotationPresent(annotation)) {
// 将目标类本身作为键,目标类的实例作为值,放入到beanMap中
beanMap.put(clazz, ClassUtil.newInstance(clazz));
}
}
}
loaded = true;
}
public static void main(String[] args) {
BeanContainer beanContainer = BeanContainer.getInstance();
beanContainer.loadBeans("com.imooc");
System.out.println();
}
}
使用自定义的注解标注需要加载进容器的类
测试自定义的容器加载bean
实现容器的操作方式
1.增加,删除操作
2.根据Class获取对应实例
3.获取所有的Class和实例
4.通过注解来获取被注解标注的Class
5.通过超类获取子类的Class
6.获取容器载体保存Class的数量
依赖注入
实现思路
1.定义相关的注解标签
2.实现创建被注解标记的成员变量的实例,并将其注入到成员变量里
3.依赖注入的使用
定义相关的注解标签
package org.simpleframework.inject.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 我们自定义的Autowired目前仅支持成员变量注入
*
* @author jiangli
*/
@Target(ElementType.FIELD) // 作用在成员变量上
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
实现创建被注解标记的成员变量的实例,并将其注入到成员变量里
执行ioc
1.遍历bean容器中所有的Class对象
2.遍历Class对象的所有成员变量
3.找出被Autowired标记的成员变量
4.获取这些成员变量的类型
5.获取这些成员变量的类型在容器里对应的实例
6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
package org.simpleframework.inject;
import com.imooc.controller.UserController;
import com.imooc.service.UserService;
import org.simpleframework.core.BeanContainer;
import org.simpleframework.inject.annotation.Autowired;
import org.simpleframework.util.ClassUtil;
import java.lang.reflect.Field;
import java.util.Set;
/**
* DI 依赖注入
*
* @author 江黎
* @date 2022-01-30
*/
public class DependencyInjector {
/**
* bean容器
*/
private BeanContainer beanContainer;
public DependencyInjector() {
beanContainer = BeanContainer.getInstance();
}
/**
* 执行ioc
*/
public void doIoc() {
// 1.遍历bean容器中所有的Class对象
Set<Class<?>> classSet = beanContainer.getClasses();
if (classSet == null || classSet.isEmpty()) {
System.out.println("empty classSet in BeanContainer");
return;
}
for (Class<?> clazz : classSet) {
// 2.遍历Class对象的所有成员变量
Field[] fields = clazz.getDeclaredFields();
if (fields.length == 0) {
continue;
}
for (Field field : fields) {
// 3.找出被Autowired标记的成员变量
if (field.isAnnotationPresent(Autowired.class)) {
Autowired autowired = field.getAnnotation(Autowired.class);
String autowiredValue = autowired.value();
// 4.获取这些成员变量的类型
Class<?> fieldClass = field.getType();
// 5.获取这些成员变量的类型在容器里对应的实例
Object fieldValue = getFieldInstance(fieldClass, autowiredValue);
if (fieldValue == null) {
throw new RuntimeException("unable to inject relevant type, target fieldClass is " + fieldClass.getName() + "autowiredValue is " + autowiredValue);
}
// 6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
Object targetBean = beanContainer.getBean(clazz);
ClassUtil.setField(targetBean, field, fieldValue);
}
}
}
}
/**
* 根据Class在beanContainer中获取其实例或者实现类
*/
private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
Object fieldValue = beanContainer.getBean(fieldClass);
if (fieldValue != null) {
return fieldValue;
} else {
Class<?> implementClass = getImplementClass(fieldClass, autowiredValue);
if (implementClass != null) {
return beanContainer.getBean(implementClass);
} else {
return null;
}
}
}
/**
* 获取接口的实现类
*/
private Class<?> getImplementClass(Class<?> fieldClass, String autowiredValue) {
Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
if (classSet == null || classSet.isEmpty()) {
System.out.println("empty classSet in BeanContainer");
return null;
} else {
// 如果@Autowired's value没有值
if (autowiredValue == null || autowiredValue.length() == 0) {
if (classSet.size() == 1) {
return classSet.iterator().next();
} else {
// 如果多于两个实现类,且用户未指定其中一个实现类,则抛出异常
throw new RuntimeException("multiple implement classes for " + fieldClass.getName() + ", please set @Autowired's value to pick one");
}
} else {
for (Class<?> clazz : classSet) {
if (autowiredValue.equals(clazz.getSimpleName())) {
return clazz;
}
}
}
}
return null;
}
public static void main(String[] args) {
BeanContainer beanContainer = BeanContainer.getInstance();
beanContainer.loadBeans("com.imooc");
UserController userController = (UserController) beanContainer.getBean(UserController.class);
UserService userService = userController.getUserService();
// 执行ioc依赖注入前
new DependencyInjector().doIoc();
// 执行ioc依赖注入后
UserService userService1 = userController.getUserService();
System.out.println();
}
}
至此,实现了简易ioc的整个流程,有装载bean的容器,能够注入依赖