提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
本教程适合已经对Spring使用有初步理解以及有实际使用经验的人群,该教程作为技术兴趣讨论范围,未经许可不可随意转载。如果教程有误,欢迎指正,欢迎大家积极讨论。
本系列都将使用注解方式进行演示以及研究,xml方式原理相同,注解更为方便以及是目前生产中最常用和主流方式,所以选择注解方式使用Spring。
IOC是什么?
控制反转(Inversion of Control,缩写为IoC),为Spring核心功能之一,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
手写步骤
1.创建工程
- 使用idea创建一个普通java工程如图所示:
2.基础代码
在spring包中新建如下代码:
- AppContext类
package spring;
/**
* ioc容器核心
*/
public class AppContext {
// 配置类
private Class configClazz;
// 构造方法
public AppContext(Class configClazz) {
this.configClazz = configClazz;
}
/**
* 获取容器内bean
* @param beanName
* @return
*/
public Object getBean(String beanName){
return null;
}
}
- BeanDefinition类
package spring;
/**
* bean定义对象
*/
public class BeanDefinition {
private Class beanClazz; // bean类型
private String scope; // 单例 或者 多例
public Class getBeanClazz() {
return beanClazz;
}
public void setBeanClazz(Class beanClazz) {
this.beanClazz = beanClazz;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
}
- ComponentScan注解
package spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 表示只能作用于类
public @interface ComponentScan {
String value() ;
}
- Component注解
package spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 表示只能作用于类
public @interface Component {
String value() default "";
}
- AppConfig类(src包)
package src;
import spring.ComponentScan;
@ComponentScan("src")
public class AppConfig {
}
3. 逻辑实现
src包中新建两个组件类ComponentA、ComponentB:
package src;
import spring.Component;
@Component("a")
public class ComponentA {
}
package src;
import spring.Component;
@Component
@Scope("duo")
public class ComponentB {
}
这个时候我们来实现容器细节,将AppContext类完善:
package spring;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;
/**
* ioc容器核心
*/
public class AppContext {
// 配置类
private Class configClazz;
// 定义一个bean定义的Map key为beanName value为BeanDefinition对象 至于为什么用这个map后续会详细说就当这个map为普通的hashmap
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap();
// 单例Bean池
private ConcurrentHashMap<String,Object> singleBeanMap = new ConcurrentHashMap();
// 构造方法
public AppContext(Class configClazz) {
this.configClazz = configClazz;
// 完善部分
// 判断是否有ComponentScan注解
if (configClazz.isAnnotationPresent(ComponentScan.class)) {
// 获取配置类上ComponentScan注解
ComponentScan componentScanAnnotation = (ComponentScan) configClazz.getAnnotation(ComponentScan.class);
// 拿到所需要扫描的包路径
String scanPackage = componentScanAnnotation.value();
// 获取类加载器
ClassLoader classLoader = configClazz.getClassLoader();
// 获取包的相对文件路径
String classFilesPath = scanPackage.replace(".", File.pathSeparator);
// 根据classpath加上相对路径就能获取上述包下面所有资源的路径
URL resourcePath = classLoader.getResource(classFilesPath);
// 获取路径
File resourceFiles = new File(resourcePath.getPath());
// 路径下所有文件
File[] files = resourceFiles.listFiles();
// 遍历所有文件
for (File f : files) {
// 获取文件名称
String fileName = f.getName();
// 判断时候是Class文件只针对Class文件做处理
if (fileName.endsWith(".class")){
// 获取类的全包名以及类名称
String className = scanPackage + "." + fileName.replace(".class","");
try {
// 将类加载拿到类
Class<?> scanClass = Class.forName(className);
if (scanClass.isAnnotationPresent(Component.class)) {
Component component = scanClass.getAnnotation(Component.class);
// 获取bean别名 如果没有给bean指定别名 则用类名作为别名
String beanAlias = "".equals(component.value()) ?
fileName.replace(".class","") :component.value() ;
BeanDefinition beanDefinition = new BeanDefinition();
// 只过滤加Component注解的类
beanDefinition.setBeanClazz(scanClass);
// 是否是单例默认为单例当scope注解值为duo的时候才为多例
if (scanClass.isAnnotationPresent(Scope.class)){
Scope scope = scanClass.getAnnotation(Scope.class);
String scopeValue = scope.value();
if ("duo".equals(scopeValue)) {
beanDefinition.setScope("duo");
}else{
beanDefinition.setScope("dan");
}
}else {
// 表示单例
beanDefinition.setScope("dan");
}
// 将扫描到的类放到map容器中
beanDefinitionMap.put(beanAlias,beanDefinition);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}else {
throw new RuntimeException("没有找到ComponentScan注解");
}
}
/**
* 获取容器内bean
* @param beanName
* @return
*/
public Object getBean(String beanName){
// 从Bean容器中获取bean对象
Object singleBean = singleBeanMap.get(beanName);
if (singleBean == null){
// 如果容器中单例bean未被创建则创建对象并且放入容器中
// 从封装好的bean定义信息容器中获取bean信息
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition != null) {
// 如果是单例bean就放入bean池子
// 如果是多例bean就直接返回
String scopeValue = beanDefinition.getScope();
singleBean = createBean(beanDefinition);
if ("dan".equals(scopeValue)) {
singleBeanMap.put(beanName,singleBean);
}
}else {
throw new RuntimeException("该bean未被spring管理");
}
}
return singleBean;
}
/**
* 创建bean
* @param beanDefinition
* @return
*/
private Object createBean(BeanDefinition beanDefinition) {
// 获取bean类
Class beanClazz = beanDefinition.getBeanClazz();
try {
// 获取类的构造器
Constructor declaredConstructor = beanClazz.getDeclaredConstructor(null);
// 调用无参构造 (假如没有无参构造则可以在这里适配逻辑细节 这里就不做适配了)
Object o = declaredConstructor.newInstance(null);
return o;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
4.代码测试
最后编写测试代码如下:
package src;
import spring.AppContext;
public class MainClass {
public static void main(String[] args) {
AppContext appContext = new AppContext(AppConfig.class);
ComponentA componentA1 = (ComponentA) appContext.getBean("a");
ComponentA componentA2 = (ComponentA) appContext.getBean("a");
ComponentB componentB1 = (ComponentB) appContext.getBean("ComponentB");
ComponentB componentB2 = (ComponentB) appContext.getBean("ComponentB");
System.out.println("单例bean ComponentA1 : " + componentA1);
System.out.println("单例bean ComponentA2 : " + componentA2);
System.out.println("单例bean ComponentA是否是同一对象 : " + (componentA1 == componentA2) );
System.out.println("多例bean componentB1 : " + componentB1);
System.out.println("多例bean componentB2 : " + componentB2);
System.out.println("多例bean ComponentA是否是同一对象 : " + (componentB1 == componentB2) );
}
}
结果如下:
可以看到配置了单例的bean:ComponentA取到的对象都是同一个对象,配置了多例的对象ComponentB为多例每个对象都不相等。
总结
上述代码只是最简易版ioc实现,后面的一些后置处理器以及初始化方法DI自动注入都会陆续实现,欢迎大家交流。