文章目录
一、手写SpringIOC容器简介
已经实现的功能有:
- 提供注解@Service、@Component、@Controller、@Repositor,标记需要加载到IOC容器管理的类
- 提供了一个ClassUtil工具类,支持加载指定包下及其子包所有.class文件,支持注入属性,支持通过反射class加载实例化对象(仅支持默认构造)
- 提供IOC容器BeanContainer,支持通过ClassUtil工具类,加载被注解标记的类,目前仅支持单例模式以及非懒加载的方式
- 提供IOC容器增产改的方法,获取IOC的Bean的方式有:通过class直接加载;获取某一个标记的注解所有class集合;获取全部的class集合;获取父类或者接口的实现类结合Class
- 提供依赖注入的注解@Autowired,仅支持注入到属性上,该注解提供一个value,用户可以指定获取具体的接口实现类的名称
- 提供完成所有标记在IOC容器中对象属性依赖注入的类DependencyInjector,将扫描@Autowired,根据用户定义的注解value或者默认值注入到属性中
使用BeanContainer的loadBeans方法,指定框架需要管理的包路径,将相关类加入到IOC容器中,再利用DependencyInjector的doIoc方法实现属性的依赖注入。
实现的思路为:
- 创建注解
- 提取标记对象
- 实现容器
- 依赖注入
提取标记对象
- 指定范围,获取范围内全部的类
- 遍历所有类,获取被注解标记的类并加载到容器里
二、创建注解
//设置是作用在类上面的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
//设置是作用在类上面的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
//设置是作用在类上面的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}
//设置是作用在类上面的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
三、提取标记对象
1. 指定范围加载
-
首先需要获取到一个类加载器,通过类加载器加载用户指定包的资源,类加载器详解,查看这个文章:类加载器总结
-
获取类加载的方式,可以通过当前的线程获取
/**
* 获取类加载器
*
* @return 当前类加载器
*/
public static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
- 由于用户传递过来的只是个包名称,而对于不同的机器,项目所在的位置不同,所以就需要通过类加载器获取到资源的具体路径
URL url = classLoader.getResource(packageName.replace(".", "/"));
- getResource()接收的是一个以反斜杠分割的路径,而一般用户提供的都是以“.”分割的,所以我们需要替换成反斜杠
- classLoader.getResource()方法返回的是一个URL,可以获取到协议,域名,端口号,路径,请求参数,如:https[协议]😕/www.xzzz2020.cn[域名]:80[端口号]/public/simplespring[路径]?id=xxx[请求参数]
- 而我们需要关注的是这样的URL:file:///F:/study/Spring,该资源可以定位本机的唯一资源,所以需要通过URL类中两个方法getProtocol()和getPath()获取定位包的文件协议和绝对路径
- 接着主要是递归调用目录所有.class文件,利用反射Class.forName方法创建实例,并加入到集合中
完整代码如下所示:
/**
* 类加载工具类
* @author xzzz2020
* @version 1.0
* @date 2020/7/31 13:20
*/
@Slf4j
public final class ClassUtil {
private static final String FILE_PROTOCLO = "file";
private ClassUtil() {
}
/**
* 获取包下类的集合
*
* @param packageName 包名
* @return 获取类的集合
*/
public static Set<Class<?>> extractPackageClass(String packageName) {
//1. 获取类的加载器,为了获取项目发布的实际路径
ClassLoader classLoader = getClassLoader();
//2. 通过类加载器获取到加载的资源信息
URL url = classLoader.getResource(packageName.replace(".", "/"));
if (url == null) {
log.warn("unable to retrieve anything from package: " + packageName);
return null;
} else {
log.info("load path is "+url.getProtocol()+"://"+url.getPath());
}
//3. 根据不同的资源类型,采用不同的方式获取资源的集合
Set<Class<?>> classSet = null;
//过滤出文件类型的资源
if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCLO)){
classSet = new HashSet<>();
File packageDirectory = new File(url.getPath());//获取文件的实际路径
//递归遍历该目录下所有的class文件
extractClassFile(classSet,packageDirectory,packageName);
}
return classSet;
}
/**
* 递归遍历该目录下所有的class文件
* @param emptyClassSet 装载目标类的集合
* @param fileSource 文件或目录
* @param packageName 加载的包路径
*/
private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
if (!fileSource.isDirectory()){
}else {
//列出当前目录的所有文件和文件夹
File[] files = fileSource.listFiles(new FileFilter() {
/**
* 该方法是过滤当前目录下的所有文件和目录
* @param file 文件目录
* @return 如果是目录,返回true,如果是.class文件,加载到容器中,其他的返回false
*/
@Override
public boolean accept(File file) {
if (file.isDirectory()){
return true;
}else {
//获取文件的绝对值路径
String absoluteFilePath = file.getAbsolutePath();
if (absoluteFilePath.endsWith(".class")){
//如果是class文件,直接加载
addToClassSet(absoluteFilePath);
}
//TODO 此处可以加入针对其他类型资源的处理
}
return false;
}
/**
* 根据class文件的绝对值路径,获取并生成class对象,并放入classSet中
* @param absoluteFilePath 文件的绝对值路径
*/
private void addToClassSet(String absoluteFilePath) {
//1.从class文件的绝对值路径里提取出包含了package的类名
//如/Users/baidu/imooc/springframework/sampleframework/target/classes/cn/xzzz2020/entity/dto/MainPageInfoDTO.class
//需要弄成cn.xzzz2020.entity.dto.MainPageInfoDTO
absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
className = className.substring(0,className.lastIndexOf("."));
//2.通过反射机制获取对应的Class对象并加入到classSet里
Class<?> targetClass = loadClass(className);
emptyClassSet.add(targetClass);
}
});
//递归遍历全部目录
if (files!=null){//如果file的数组为空,使用foreach,还是会抛出空指针异常
for (File file : files) {
extractClassFile(emptyClassSet,file,packageName);
}
}
}
}
/**
* 获取类加载器
*
* @return 当前类加载器
*/
public static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
/**
* 获取class对象
* @param className class全限定类名
* @return
*/
public static Class<?> loadClass(String className){
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
log.error("Load class error " + e);
throw new RuntimeException();
}
}
/**
* 实例化类对象
* @param clazz 需要实例化的class
* @param accessible 是否允许使用私有的构造函数
* @param <T> 对象
* @return
*/
public static <T> T newInstance(Class<?> clazz,boolean accessible){
try {
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(accessible);
return (T) constructor.newInstance();
} catch (Exception e) {
log.error("newInstance "+clazz+" error "+e);
throw new RuntimeException();
}
}
/**
* 设置类的属性值
* @param field 成员变量
* @param target 类实例
* @param value 成员变量的值
* @param accessible 是否允许设置私有属性
*/
public static void setField(Field field,Object target,Object value,boolean accessible){
field.setAccessible(accessible);
try {
field.set(target,value);
} catch (Exception e) {
log.error("setFiled error ",e);
throw new RuntimeException(e);
}
}
}
四、实现IOC容器
容器一般由以下组成部分:
- 保存Class对象及其实例的载体
- 容器的加载
- 容器的操作方式
容器首先是单例的,但是传统的单例只能避免多线程下重复创建的问题,面对反射的强大能力,很容易就被创建一个新的对象
而Java提供了一个枚举类的形式,可以从源码层面避免创建多个对象。
1. 枚举的单例
//设置私有构造器
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public class BeanContainer {
/**
* 获取容器实例
* @return 容器
*/
public static BeanContainer getInstance() {
return BeanContainerHolder.HOLDER.getInstance();
}
private enum BeanContainerHolder{
HOLDER;
private BeanContainer instance;
BeanContainerHolder(){
instance = new BeanContainer();
}
public BeanContainer getInstance() {
return instance;
}
}
}
2. 保存Class对象及其实例的载体
为了方便调用,将采用map的方式创建载体,但是不是使用HashMap,而是ConcurrentHashMap以保证高并发下的容器线程安全。
//存放所有被标记的map
private final Map<Class<?>,Object> beanMap = new ConcurrentHashMap<>();
3. 容器加载
/**
* 加载Bean到容器中
* @param packageName 包名称
*/
public synchronized void loadBeans(String packageName){
//判断是否重复加载
if (isLoaded()){
log.warn("BeanContainer has been loaded.");
return;
}
//获取指定包下面的class对象
Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
//判断能否加载对象
if (ValidationUtil.isEmpty(classSet)){
log.warn("This package "+packageName+" has nothing.");
return;
}
for (Class<?> clazz :classSet){
for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
//如果类上面标记了自定义的注解,就放入到容器中
if (clazz.isAnnotationPresent(annotation)){
beanMap.put(clazz,ClassUtil.newInstance(clazz,true));
}
}
}
loaded = true;
}
4. 容器的操作(增删改查)
/**
* 判断是否过容器
* @return true代表已经加载过容器了
*/
public boolean isLoaded() {
return loaded;
}
/**
* 获取加载到容器中bean实例的个数
* @return
*/
public int getBeansSize(){
return beanMap.size();
}
/**
* 添加对象实例到容器中
* @param clazz 指定对象
* @param bean 指定实例
* @return 添加的对象
*/
public Object addBean(Class<?> clazz,Object bean){
return beanMap.put(clazz, bean);
}
/**
* 删除容器中bean的实例
* @param clazz 指定对象
* @return 删除的对象
*/
public Object removeBean(Class<?> clazz){
return beanMap.remove(clazz);
}
/**
* 获取指定对象的实例
* @param clazz 指定对象
* @return 指定实例
*/
public Object getBean(Class<?> clazz){
return beanMap.get(clazz);
}
/**
* 获取容器中所有对象的集合
* @return 对象集合
*/
public Set<Class<?>> getClasses(){
return beanMap.keySet();
}
/**
* 获取容器中所有实例化对象集合
* @return 实例化对象集合
*/
public Set<Object> getBeans(){
return new HashSet<>(beanMap.values());
}
/**
* 根据注解获取对象集合
* @param annotation 注解
* @return
*/
public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){
//1.获取beanMap的所有class对象
Set<Class<?>> keySet = getClasses();
if(ValidationUtil.isEmpty(keySet)){
log.warn("nothing in beanMap");
return null;
}
//2.通过注解筛选被注解标记的class对象,并添加到classSet里
Set<Class<?>> classSet = new HashSet<>();
for (Class<?> clazz : keySet) {
if (clazz.isAnnotationPresent(annotation)){
classSet.add(clazz);
}
}
return classSet.size()>0?classSet:null;
}
/**
* 通过接口或者父类获取实现类或者子类的Class集合,不包括其本身
*
* @param interfaceOrClass 接口Class或者父类Class
* @return Class集合
*/
public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){
//1.获取beanMap的所有class对象
Set<Class<?>> keySet = getClasses();
if(ValidationUtil.isEmpty(keySet)){
log.warn("nothing in beanMap");
return null;
}
//2.通过keySet的元素是否是传入的接口或者子类,如果是,并添加到classSet里
Set<Class<?>> classSet = new HashSet<>();
for (Class<?> clazz : keySet) {
//isAssignableFrom可以判断是否是同一个类或者其超类
if (interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)){
classSet.add(clazz);
}
}
return classSet.size()>0?classSet:null;
}
五、依赖注入
1. 依赖注入的注解
/**
* @author xzzz2020
* @version 1.0
* @date 2020/7/31 17:06
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
String value() default "";
}
2. 对有注解的属性进行依赖注入
/**
* 提供依赖注入
*
* @author xzzz2020
* @version 1.0
* @date 2020/7/31 17:12
*/
@Slf4j
public class DependencyInjector {
//Bean容器
private BeanContainer beanContainer;
public DependencyInjector() {
beanContainer = BeanContainer.getInstance();
}
/**
* 执行依赖注入
*/
public void doIoc() {
if (ValidationUtil.isEmpty(beanContainer.getClasses())) {
log.warn("empty classset in BeanContainer");
return;
}
//1.遍历Bean容器中所有的Class对象
Set<Class<?>> classSet = beanContainer.getClasses();
for (Class<?> clazz : classSet) {
//2.遍历Class对象的所有成员变量
Field[] fields = clazz.getDeclaredFields();
if (ValidationUtil.isEmpty(fields)){
continue;
}
//3.找出被Autowired标记的成员变量
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)){
//获取属性上面的注解,来解析属性
Autowired autowired = field.getAnnotation(Autowired.class);
String value = autowired.value();
//4.获取这些成员变量的类型
Class<?> fieldClazz = field.getType();
//5.获取这些成员变量的类型在容器里对应的实例
Object fieldValue = getFieldInstance(fieldClazz,value);
//获取不到对象,注入失败
if (fieldValue == null){
throw new RuntimeException("unable to inject relevant type,target fieldClass is:" + fieldClazz.getName() + " in " +clazz.getSimpleName() );
}
//6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
Object targetBean = beanContainer.getBean(clazz);
ClassUtil.setField(field,targetBean,fieldValue,true);
}
}
}
}
/**
* 根据class对象获取其对象或者实现类
* @param fieldClazz 属性定义的对象
* @return
*/
private Object getFieldInstance(Class<?> fieldClazz,String value) {
Object fieldValue = beanContainer.getBean(fieldClazz);
if (fieldValue!=null){
return fieldClazz;
}else {
Class<?> implClass = getImplementClass(fieldClazz,value);
if (implClass!=null){
return beanContainer.getBean(implClass);
}else {
return null;
}
}
}
/**
* 获取接口的实现类
* @param fieldClazz 接口
* @return
*/
private Class<?> getImplementClass(Class<?> fieldClazz,String value) {
Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClazz);
if (!ValidationUtil.isEmpty(classSet)){
if (ValidationUtil.isEmpty(value)){//代表用户没有指定具体是那个实现类
if (classSet.size()==1){
return classSet.iterator().next();
}else {//如果该接口有多个实现类,则抛出异常
throw new RuntimeException("multiple implemented classes for " + fieldClazz.getName() + " please set @Autowired's value to pick one");
}
}else {//用户指定具体实现子类
for (Class<?> clazz : classSet) {
if (value.equals(clazz.getSimpleName())){//是用户的指定类
return clazz;
}
}
}
}
return null;
}
}