手动实现 Spring 底层机制【初始化 IOC容器+依赖注入+BeanPostProcessor 机制+AOP】[任务阶段1】
引言:Spring框架的ioc容器、依赖注入、BeanPostProcessor后置处理器、AOP面向切面编程等特点为我们的开发带来了极大的便利,但是我们不能只学其中的api,更要懂得Spring的底层机制。
思考Spring框架是如何实现以下功能
Spring 注解方式注入 bean
原生 Spring 如何实现依赖注入和 singleton、prototype
Spring 底层实现,如何实现 IOC 容器创建和初始化
Spring 底层实现, 如何实现 getBean, 根据 singleton 和 prototype 来返回 bean 实例
Spring 如何实现 BeanPostProcessor后置处理器机制
Spring AOP 动态代理实现
下面我来手动实现一下Spring的底层机制【初始化 IOC容器+依赖注入+BeanPostProcessor 机制+AOP】
前置知识:java反射+IO+动态代理+Spring
程序框架图
一、实现任务阶段 1- 编写自己 Spring 容器,实现扫描包, 得到 bean 的 class 对象
1.先创建一个名为mySpring的Maven项目
目录结构
2.在annotation包下定义ComponentScan注解,该注解的value就是要扫描的包
package com.hykedu.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 程序员蛇皮
* @version 1.0
* 1.@Target(ElementType.TYPE)指定我们的 ComponentScan注解可以修饰 Type程序元素
* 2.@Retention(RetentionPolicy.RUNTIME) ComponentScan注解 保留范围
* 3.String value() default ""; 表示 ComponentScan注解 可以传入value
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
String value() default "";
}
3.在ioc包下定义CodeSnakeSpringConfig.java,在该类实现ComponentScan注解,该注解的value就是要扫描的包
package com.hykedu.spring.ioc;
import com.hykedu.spring.annotation.ComponentScan;
/**
* @author 程序员蛇皮
* @version 1.0
* 这是一个配置类,作用类似于原生Spring的 beans.xml 容器配置文件
*/
@ComponentScan(value = "com.hykedu.spring.component")
public class CodeSnakeSpringConfig {
}
4.我们知道Spring扫描包下的类的注解有Component、Controller、Repository、Service,这里我们只在annotation包下定义Component注解做一个示范,其他注解作用非常类似,为了简化流程,这里不写代码示范了
package com.hykedu.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author 程序员蛇皮
* @version 1.0
* 1.@Target(ElementType.TYPE)指定我们的 ComponentScan注解可以修饰 Type程序元素
* 2.@Retention(RetentionPolicy.RUNTIME) ComponentScan注解 保留范围
* 3.String value() default ""; 表示 ComponentScan注解 可以传入value
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default "";
}
5.在ioc包定义CodeSnakeSpringApplicationContext类,CodeSnakeSpringApplicationContext 类的作用类似Spring原生ioc容器
package com.hykedu.spring.ioc;
import com.hykedu.spring.annotation.Component;
import com.hykedu.spring.annotation.ComponentScan;
import java.io.File;
import java.net.URL;
/**
* @author 程序员蛇皮
* @version 1.0
* CodeSnakeSpringApplicationContext 类的作用类似Spring原生ioc容器
*/
@SuppressWarnings("all")
public class CodeSnakeSpringApplicationContext {
private Class configClass;
public CodeSnakeSpringApplicationContext(Class configClass) {
this.configClass = configClass;
System.out.println(configClass);
//获取要扫描的包
//1.先得到SpringConfig配置的@ComponentScan(value = "com.hykedu.spring.component")
ComponentScan componentScan = (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
//2.componentScan的value就是我们要扫描的包
String path = componentScan.value();
System.out.println("要扫描的包:" + path);
//得到要扫描的包下所有的资源(类.class)
//1.得到类的加载器
ClassLoader classLoader = CodeSnakeSpringApplicationContext.class.getClassLoader();
//2.通过类加载器获取到要扫描包的url
path = path.replace(".", "/");//把 . 替换成路径间隔符 /
URL resource = classLoader.getResource(path);
System.out.println("要扫描包的url:" + resource);
//3.将要加载的资源(.class) 路径下的文件进行遍历=>io
File file = new File(resource.getFile());
if (file.isDirectory()) {//pand
File[] files = file.listFiles();
for (File f : files) {
String absolutePath = f.getAbsolutePath();
System.out.println("文件的绝对路径:" + absolutePath);
//这里我们只处理.class文件
if (absolutePath.endsWith(".class")) {
//1.获取到类名
String className = absolutePath.substring
(absolutePath.lastIndexOf("\\") + 1, absolutePath.lastIndexOf(".class"));
System.out.println("类名:" + className);
//2.获取类的完整路径(全类名)
String classFullName = path.replace("/", ".") + "." + className;
System.out.println("全类名:" + classFullName);
//3.判断该类是否需要注入容器,判断该类是不是有@Component/@Controller/@Repository/@Service注解
try {
Class<?> clazz = classLoader.loadClass(classFullName);
if (clazz.isAnnotationPresent(Component.class)) {
//如果在注解指定了value,将其赋值给className
System.out.println("这是一个Spring bean" + clazz);
} else {
System.out.println("这不是一个Spring bean" + clazz);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
}
}
6.测试能否识别带有Component注解的类
在Component包下分别定义没有Component注解的Car类和带有Component注解的StudentDao和StudentService
类,看看程序能否识别出来哪个是Bean对象,哪个不是Bean对象
package com.hykedu.spring.component;
/**
* @author 程序员蛇皮
* @version 1.0
*/
public class Car {
}
package com.hykedu.spring.component;
import com.hykedu.spring.annotation.Component;
/**
* @author 程序员蛇皮
* @version 1.0
*/
@Component
public class StudentDao {
}
package com.hykedu.spring.component;
import com.hykedu.spring.annotation.Component;
/**
* @author 程序员蛇皮
* @version 1.0
*/
@Component
public class StudentService {
}
测试方法AppMain
package com.hykedu.spring;
import com.hykedu.spring.ioc.CodeSnakeSpringApplicationContext;
import com.hykedu.spring.ioc.CodeSnakeSpringConfig;
/**
* @author 程序员蛇皮
* @version 1.0
*/
public class AppMain {
public static void main(String[] args) {
CodeSnakeSpringApplicationContext codeSnakeSpringApplicationContext = new CodeSnakeSpringApplicationContext(CodeSnakeSpringConfig.class);
System.out.println("ok");
}
}
测试结果(成功扫描到了Bean对象)
任务阶段2链接
手动实现 Spring 底层机制【初始化 IOC容器+依赖注入+BeanPostProcessor 机制+AOP】【任务阶段2】