文章目录
创建单例容器
- 创建单例容器
- 实现容器
- 实现容器的加载
- 测试用例
使用如下的枚举创建单例容器 .
@Slf4j
// 创建私有的无参构造函数
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanContainer {
/**
* 获取单例的bean容器实例
* @return
*/
public static BeanContainer getInstance() {
return ContainerHolder.HOLDER.instance;
}
private enum ContainerHolder {
HOLDER;
private BeanContainer instance;
ContainerHolder() {
instance = new BeanContainer();
}
}
}
实现容器
容器的组成部分:
- 保存Class 对象以及实例的载体 .
- 容器的加载
- 对外提供容器的操作方式, 便于 客户端操作载体中的对象.
使用ConcurrentHashMap来保存目标类型的对象和实例. ConcurrentHashMap并发性强, JDK8摒弃了之前的分段锁, 采用CAS, 红黑树, 提升细节.
实现容器的加载实现思路.
- 配置的管理与获取
- 获取指定范围内的Class对象
- 依据配置提取Class对象, 连同实例一并存入容器中.
代码如下
@Slf4j
// 创建私有的无参构造函数
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanContainer {
/**
* 存放所有被配置标记的目标对象的map
*/
private final Map beanMap = new ConcurrentHashMap();
/**
* 初始化, 容器未被加载过
*/
private boolean loaded = false;
/**
* 该方法用于判断容器是否被加载过
*
* @return
*/
public boolean isLoaded() {
return loaded;
}
/**
* 1. 配置的管理与获取
* 加载bean的注册列表.
* 把用Component, Controller, Service, Repository 注解标记的类加载到内存中去
*/
private static final List BEAN_ANNOTATION =
Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);
/**
* 返回容器中bean的数量
* @return
*/
public int size() {
return beanMap.size();
}
/**
* 获取单例的bean容器实例
*
* @return
*/
public static BeanContainer getInstance() {
return ContainerHolder.HOLDER.instance;
}
private enum ContainerHolder {
HOLDER;
private BeanContainer instance;
ContainerHolder() {
instance = new BeanContainer();
}
}
/**
* 扫描加载所有的bean
* 3. 依据配置提取Class对象, 连同实例一并存入容器中.
*
* 加上synchronized 防止两个线程去加载容器
*/
public synchronized void loadBeans(String packageName) {
if (isLoaded()) {
log.warn("容器已经被加载过了");
return;
}
// 通过传入的包名, 把类加载到集合中
Set classSet = ClassUtil.extractPackageClass(packageName);
if (ValidationUtil.isEmpty(classSet)) {
log.error("该包获取不到任何类: ", packageName);
return;
}
// 遍历所有加载到的类实例
for (Class clazz : classSet) {
// 遍历所有的注解
for (Class annotation : BEAN_ANNOTATION) {
// 判断该类是否被所需要的注解修饰
if (clazz.isAnnotationPresent(annotation)) {
// 如果是所需要的类
// 那么将目标类本身作为键, 目标类的实例作为值, 放入beanMap中
beanMap.put(clazz, ClassUtil.newInstance(clazz, true));
}
}
}
loaded = true;
}
}
其核心为loadBeans
方法加载bean到容器中. 有如下的注意点和步骤
- 方法用synchronized修饰, 避免多线程的情况下, 两个线程同时去加载bean
- loaded布尔值来标记是否已经加载过bean, 只需加载一次bean到容器中
- 通过
ClassUtil.extractPackageClass(packageName)
工具类根据传入的包路径,把类加入到集合中 - 遍历所有的类的集合, 根据 isAnnotationPresent 来判断是否为所需要的Component, Controller, Service, Repository注解 修饰的类
- 如果是被指定注解修饰的类, 那么将其放入beanMap中.
- 调用
ClassUtil.newInstance(clazz, true)
方法去创建类的实例
newInstance 工具类方法如下
/**
* @param clazz Class 类对象
* @param accessible 是否支持创建出私有class对象实例
* @param class的类型
* @return 实例化的类
*/
public static T newInstance(Class clazz, boolean accessible) {
try {
// 获取无参构造方法
Constructor constructor = clazz.getDeclaredConstructor();
// 设置是否暴力访问
constructor.setAccessible(accessible);
// 创建出实例, 强制转换为T对象
return (T) constructor.newInstance();
} catch (Exception e) {
log.error("创建实例错误 ", e);
throw new RuntimeException();
}
}
测试用例
编写BeanContainerTest
测试类 @BeforeAll 注解用于所有的测试用例执行之前 , 执行仅且一次初始化. 需要先实例化容器实例.
public class BeanContainerTest {
private static BeanContainer beanContainer;
/**
* BeforeAll注解用于所有的测试用例执行之前 , 执行仅且一次初始化.
*/
@BeforeAll
static void init() {
// 初始化 BeanContainer 实例
beanContainer = BeanContainer.getInstance();
}
@DisplayName("加载目标类及实例到BeanContainer")
@Test
public void loadBeansTest() {
// 验证是否加载过是否为false
Assertions.assertEquals(false, beanContainer.isLoaded());
beanContainer.loadBeans("com.springstudy");
int size = beanContainer.size();
// 验证bean的数量是否为7, 目前项目中, com.springstudy 包下有7个类被指定注解修饰.
Assertions.assertEquals(7, size);
// 验证是否加载过是否为true
Assertions.assertEquals(true, beanContainer.isLoaded());
}
}
测试结果如下, 测试通过.