写Java的人Spring肯定用过了吧,但是有多少人知道原理呢?
由于版本的迭代,spring体系的越来越完整,代码分析起来也就越复杂,我们先从简单手写的案例来驱动分析源码吧。
spring手写简单版IOC容器和DI依赖注入
通过这些代码了解一个大概,然后再去实践分析源码!
1.预先准备
新建一个空的maven项目,代码结构如下
- action 和 service,接口就不贴了
@TController
public class HelloAction {
@TAutowired
HelloService helloService;
public String hello(String name) {
return helloService.sayHello(name);
}
}
@TService
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "my name is " + name;
}
}
- 由于没有引入任何jar包,
annotation
下面的这几个注解就直接复制改个名字,内容就不贴了 - 配置文件
application.properties
#需要扫描的包路径
packscanner=com.xxx.demo
实现内容都在TestApplicationContext,也就是上下文中
2.TestApplicationContext上下文
2.1 上下文 成员变量和init()
public class TestApplicationContext {
//保存配置
public Properties contextConfig = new Properties();
//保存需要加载的clasName
public List<String> classNames = new ArrayList<String>();
//ioc容器
public ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<String, Object>();
//构造方法 main方法启动时调用
public TestApplicationContext(String... classpath) {
init(classpath);
}
public void init(String... classpath) {
//初始化配置 这里就一个
doLoadConfig(classpath[0]);
//扫描对应的类
doScanner(contextConfig.getProperty("packscanner"));
//实例化对象 并加到IOC容器 加了注解的类
doInstance();
//依赖注入
doAutowired();
System.out.println("application is init");
}
}
2.2 初始化配置doLoadConfig
private void doLoadConfig(String config) {
//读流转配置
InputStream inputStream = this.getClass().getClassLoader()
.getResourceAsStream(config.replace("classpath:", ""));
try {
contextConfig.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
2.3 扫描对应的class类 doScanner
//保存className
private void doScanner(String packscanner) { //com.xxx.demo
//替换成目录形式
URL url = this.getClass().getResource("/" + packscanner.replaceAll("\\.", "/"));
File file = new File(url.getPath());
for (File f : file.listFiles()) {
if (f.isDirectory()) {
//com.xxx.demo.action
doScanner(packscanner + "." + f.getName());
} else {
if (!f.getName().endsWith(".class")) continue;
//com.xxx.demo.action.HelloAction
this.classNames.add(packscanner + "." + f.getName().replaceAll("\\.class", ""));
}
}
}
2.4 对加了注解的类初始化,并添加到IOC容器 doInstance
private void doInstance() {
for (String className : this.classNames) {
try {
Class<?> clazz = Class.forName(className);
//只有加了注解的类才初始化
if (!clazz.isAnnotationPresent(TController.class) && !clazz.isAnnotationPresent(TService.class)) {
continue;
}
//实例化对象 源码中并不是这个流程 这里方便理解 忽略细节
Object instance = clazz.newInstance();
//保存到IOC容器 beanName -> instance 生成bean name 首字母小写
String beanName = toFristLowerCase(clazz.getSimpleName());
//如果是service 看是否有自定义名称
if(clazz.isAnnotationPresent(TService.class)){
TService service = clazz.getAnnotation(TService.class);
if(!"".equals(service.value())){
beanName = service.value();
}
}
//比如beanName 相同的暂不考虑 主要是体现思想
this.ioc.put(beanName,instance);
//如果是接口实现的 需要把接口全路径对应到service
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> i : interfaces) {
this.ioc.put(i.getName(),instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//首字母小写
private String toFristLowerCase(String simpleName) {
char[] chars = simpleName.toCharArray();
chars[0] += 32;
return new String(chars);
}
2.5 @Autowired依赖注入 doAutowired
//简化版的实现 先了解这个思路
private void doAutowired() {
//ioc里面的都是已经初始化的类
for (Map.Entry<String, Object> entry : this.ioc.entrySet()) {
//判断声明的方法里是否有 @TestAutowired注解的
Object instance = entry.getValue();
Class<?> clazz = instance.getClass();
//所有声明的方法
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if(!field.isAnnotationPresent(TAutowired.class)) continue;
//默认beanName是全路径
String name = field.getType().getName();
//通过注解value上的value来确定beanName 等同于@Qualifier(value="xxx") 这里就简化了
TAutowired annotation = field.getAnnotation(TAutowired.class);
if(!"".equals(annotation.value())){
name = annotation.value();
}
Object autowiredService = this.ioc.get(name);
if(autowiredService == null) continue;
//授权 反射赋值
field.setAccessible(true);
try {
field.set(instance,autowiredService);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
3.Test测试
public static void main(String[] args) {
TestApplicationContext context = new TestApplicationContext("classpath:application.properties");
HelloAction action = (HelloAction) context.getBean(HelloAction.class);
String hello = action.hello("张三");
System.out.println(hello);
}
//输出结果
application is init
my name is 张三
理解这个思想以后,我们是不是可以猜想一下AOP是怎么实现的了? 下篇文章一起探索吧
本文仅供参考,不允许转载(因为最开始直接看源码,确实难以入门,这篇文章仅供了解思想)
以上就是本章的全部内容了。源码地址在手写结束篇提供
上一篇:最详细Java中动态代理分析-- Proxy
下一篇:SpringMvc手写简单实现篇 - AOP切面编程篇
若要功夫深,铁杵磨成针