写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; } } 复制代码
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是怎么实现的了 ?