我们知道,在单例模式下,SpringBean的生命周期大致有以下几个过程:
1、默认情况下,IoC容器启动后,会立即实例化响应的Bean。只有设置了懒加载或者bean标签的scope属性设置为prototype时,才会在调用getBean()方法时创建实例化对象。
2、容器启动时,会扫描xml配置文档和指定包及其子下的所有Spring实例化注解,生成对应的BeanDefinition信息存储在一个HashMap集合中。
3、通过反射机制,获得bean的Class类对象,从而调用构造方法或工厂方法实例化bean对象。
4、使用依赖注入完成bean中所有属性值的配置注入。
5、Spring调用BeanPostProcessor接口的前置处理方法,对bean进行功能增强。
6、调用默认或指定的初始化方法,对bean对象进行初始化。
7、Spring调用BeanPostProcessor接口的后置处理方法。
8、将该bean对象放入IoC容器的缓存池中,供程序运行时调用。
9、当IoC容器关闭时,Spring调用指定或默认的destroy()方法销毁该bean对象。
为了更深入的理解Spring框架IoC和DI的实现过程,在翻阅了一些资料和课件后,尝试手动定义两个注解@MyComponent和@MyAutowired,来简单实现Spring中控制反转和依赖注入的功能。
步骤如下:
第一步:创建自定义注解(@MyComponent、@MyAutowired)
@Target(ElementType.TYPE) // 该注解只能标注在类上
@Retention(RetentionPolicy.RUNTIME) // 该注解在运行时生效
public @interface MyComponent {
}
@Target({ElementType.FIELD}) // 该注解只能标注在类的属性上
@Retention(RetentionPolicy.RUNTIME) // 该注解在运行时生效
public @interface MyAutowired {
}
第二步:创建自定义的bean容器接口 MyApplicationContext ,定义一个getBean()方法,传入需要获取的类Class对象,返回bean的实例对象。
public interface MyApplicationContext {
Object getBean(Class clazz);
}
第三步:创建一个类AnnotationApplicationContext实现MyApplicationContext接口。我们知道,Spring容器创建好bean对象后,会将其放入到IoC容器的缓存池中,这里我们通过定义一个静态Map<Class, Object>集合的方式,保存创建出来的bean实例对象。
public class AnnotationApplicationContext implements MyApplicationContext {
/**
* 创建一个map集合,用于存放bean对象
*/
private static Map<Class, Object> beanFactory = new ConcurrentHashMap<>();
/**
* 根据类型获取bean对象
*
* @param clazz
* @return
*/
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
}
第四步: 在类AnnotationApplicationContext中创建一个带参构造函数,传入要扫描的包路径,程序扫描参数中指定包及其子包里面所有类中的@MyComponent注解,并将注解所标注的类通过反射进行实例化。然后对@MyAutowired所标注的属性进行自动装配。
private static String rootPath;
/**
* 创建带参构造函数,传递要扫描的包路径
* 扫描参数中指定包及其子包里面所有类中的@MyComponent注解,并将注解所标注的类通过反射进行实例化
* 然后对@MyAutowired所标注的属性进行自动装配
*
* @param annoPackagePath
*/
public AnnotationApplicationContext(String annoPackagePath) {
//com.atguigu
try {
// 1、 把路径中的.替换成\
String packagePath = annoPackagePath.replaceAll("\\.", "\\\\");
// 2、 获取包的绝对路径
Enumeration<URL> packagePathUrl = Thread.currentThread().getContextClassLoader().getResources(packagePath);
while (packagePathUrl.hasMoreElements()) {
URL url = packagePathUrl.nextElement();
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
// 获取包前面的路径部分,字符串截取
rootPath = filePath.substring(0, filePath.length() - packagePath.length());
// 包扫描
loadMyAnno(new File(filePath));
// 依赖注入
autowired();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 扫描参数中指定包及其子包里面所有类中的@MyComponent注解,并将注解所标注的类通过反射进行实例化
*/
private static void loadMyAnno(File file) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 1、 判断file对象是否是文件夹
if (file.isDirectory()) {
// 2、 获取文件夹里面的所有内容
File[] childFiles = file.listFiles();
// 3、 若文件夹里面为空,直接返回,若不为空,则遍历文件夹
if (childFiles == null || childFiles.length == 0) {
return;
}
// 3.1、 遍历得到每个File对象
for (File childFile : childFiles) {
// 3.2、 继续判断,如果还是文件,递归
if (childFile.isDirectory()) {
loadMyAnno(childFile);
} else {
// 3.3、 当遍历得到的File对象不是文件夹,是一个文件,获取“包路径+类名"部分,
String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);
// 3.4、 判断当前文件类型是否是.class文件
if (pathWithClass.contains(".class")) {
// 3.5、 如果是.class类型,把路径'\'替换成'.',并把.class去掉
String allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
// 3.6、 获取类的Class对象,判断该类是否是接口
Class<?> clazz = Class.forName(allName);
if (!clazz.isInterface()) {
// 3.7、 判断该类是否标注了@MyComponent,如果有注解,则实例化该类的对象
MyComponent annotation = clazz.getAnnotation(MyComponent.class);
if (annotation != null) {
Object instance = clazz.getConstructor().newInstance();
// 3.8、 判断该类是否实现了接口,如果实现了接口,就把对应接口的class作为key,如果没有实现接口,就将该类自己的class作为key,保存到beanFactory中
if (clazz.getInterfaces().length > 0) {
beanFactory.put(clazz.getInterfaces()[0], instance);
} else {
beanFactory.put(clazz, instance);
}
}
}
}
}
}
}
}
/**
* 对@MyAutowired所标注的属性进行自动装配
*/
private void autowired() throws IllegalAccessException {
// bean实例化对象均保存在beanFactory集合中
// 1、遍历beanFactory的map集合
Set<Map.Entry<Class, Object>> entrySet = beanFactory.entrySet();
for (Map.Entry<Class, Object> beanDefinition : entrySet) {
// 2、得到map集合每个元素的value,并获取到value对象的属性
Object obj = beanDefinition.getValue();
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
// 3、遍历得到的每个对象的属性数组,得到每个属性
for (Field field : fields) {
// 4、判断属性上是否标注了@MyAutowired注解,如果有,把对象进行设置(属性注入)
MyAutowired annotation = field.getAnnotation(MyAutowired.class);
if (annotation != null) {
// 设置私有属性允许访问
field.setAccessible(true);
field.set(obj, beanFactory.get(field.getType()));
}
}
}
}
第五步:IoC和DI的基本功能完成后,创建service和dao的接口及实现类,并标注自定义注解。
public interface UserDao {
void add();
}
@MyComponent
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("UserDao add...");
}
}
public interface UserService {
void add();
}
@MyComponent
public class UserServiceImpl implements UserService {
@MyAutowired
private UserDao userDao;
@Override
public void add() {
System.out.println("UserService add...");
userDao.add();
}
}
测试功能:
如此,便手动完成了一个简单的SpringBean创建及管理的过程。