# simpleframework
**Repository Path**: charon-h/simpleframework
## Basic Information
- **Project Name**: simpleframework
- **Description**: 剑指Java自研框架 决胜Spring源码
- **Primary Language**: Java
- **License**: GPL-3.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 29
- **Created**: 2021-09-18
- **Last Updated**: 2021-09-18
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 课程概览
本项目来自慕课网:[Spring源码轻松学 一课覆盖Spring核心知识点](https://coding.imooc.com/class/420.html#Prchor)
课程在自研框架和 Spring 框架的穿插讲解中让大家逐渐熟悉 Spring 框架的脉络。通过从 0 搭建一个较为完备的 Web 框架来提升框架设计能力,辅以通俗易懂的 Spring 核心模块源码的讲解,带你了解 Spring 框架的设计思路。
课程分为下面两条主线,一条是自研 Spring 框架,源码放在`src/main/java/org/simpleframework`这个包下面(其中 java 包下面的 com 是一个 Web 项目的 Demo,主要用于测试自研框架的功能,而 demo 是实现自研源码过程中涉及的泛型、涉及模式、反射、注解的讲解 demo ),另外一条主线是跟着老师剖析 Spring 框架源码,这个源码需要你自己去 Spring 官网 clone 到本地进行编译(英语不好的小伙伴可以找找有中文注释的源码版本),本仓库没有。
## 环境准备
### 模块梳理
**Spring设计的初衷**
用于构造Java应用程序的轻量级框架;
1、可以采用Spring来构造任何程序,而不局限于Web程序;
2、**轻量级**:**最少的侵入,与应用程序低耦合,接入成本低**;
3、最直观的感受:基于 POJO,构建出稳健而强大的应用;
**Spring的野心**
为各大技术领域提供支持;
微服务、移动开发、社交API集成、安全管理、云计算等等;
**Spring框架图**
### **Spring基础核心模块预览**
**一等公民:bean。通过把对象包装在bean里,从而达到对这些对象的管理以及一系列额外操作的目的。同时,可以把对象之间的依赖关系通过配置文件或注解进行管理。**
1. spring-core
包含框架基本的**核心工具类**,其他组件都要使用到这个包里的类;
**定义并提供资源的访问方式**;(比如xml、properties等文件读入内存,进行解析)
2. spring-beans: spring主要面向Bean编程(BOP);
**Bean的定义、解析、创建;**(码农只要关心bean的创建)
核心接口:BeanFactory接口(BeanFactory默认延迟加载);
3. spring-context
为Spring提供运行时环境,保存各个对象的状态;(即保存数据;context用来发现bean之间的关系,要维护这种关系,它是bean之间关系的集合)
扩展了BeanFactory:添加bean的生命周期控制,框架事件体系,资源透明化等功能;
(Context还提供了事件监听、远程访问等功能)
核心接口:ApplicationContext接口,它是BeanFactory的超类;
4. spring-aop: 最小化的动态代理实现;
两种代理模式:
JDK动态代理;(基于接口实现)
Cglib;(基于类实现)
spring aop:只能使用**运行时织入**,仅支持**方法级编织**,仅支持方法执行切入点;
AspectJ就强大多了。
**BeanFactory与ApplicationContext区别:**
- 加载策略:**BeanFactory默认延迟加载**;ApplicationContext实例化后会自动对所有的单实例bean初始化,以及相关依赖关系的装配,使之处于待定状态
- ApplicationContext是超类
WebSocket是全双工,长连接的。WebFlux是spring5提供的。

**为了完整而讲的非核心模块**
新型语言AspectJ。 利用AspectJ定义切面。
在Java语言中,从织入切面的方式来看,存在三种织入方式:
**补充:aspectj**
AOP虽然是方法论,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP。目前用得比较火的就是AspectJ了,**它是一种几乎和Java完全一样的语言,而且完全兼容Java**(AspectJ应该就是一种**扩展Java**,但它不是像Groovy[1]那样的拓展。)。当然,除了使用AspectJ特殊的语言外,AspectJ还支持原生的Java,只要加上对应的AspectJ注解就好。所以,使用AspectJ有两种方法:
- 完全使用AspectJ的语言。这语言一点也不难,和Java几乎一样,也能在AspectJ中调用Java的任何类库。AspectJ只是多了一些关键词罢了。
- 或者使用纯Java语言开发,然后使用AspectJ注解,简称@AspectJ。
原文链接:https://blog.csdn.net/vonnie_jade/article/details/68955248
### Spring源码的下载和编译
1. 从 github 上拉下 5.2.0.RELEASE 的 Spring 源码,然后在 build.gradle 文件中的 buildscript 加入 repositories 代码块,以后 gradle 本身的依赖就走这个阿里云镜像去下载
```
buildscript {
repositories {
maven { url'https://maven.aliyun.com/repository/public/' }
maven { url'https://maven.aliyun.com/repository/jcenter/' }
}
dependencies {
classpath 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.16'
classpath 'io.spring.asciidoctor:spring-asciidoctor-extensions:0.1.3.RELEASE'
}
}
```
2. 项目的依赖就走这个地址去下载
```
repositories {
maven { url'https://maven.aliyun.com/repository/public/' }
maven { url'https://maven.aliyun.com/repository/jcenter/' }
mavenCentral()
maven { url "https://repo.spring.io/libs-spring-framework-build" }
}
```
3. 根据 github 上文档要求在 git 命令行执行下面指令
```bash
./gradlew :spring-oxm:compileTestJava
```
结果如下:


4. 将源码项目导入 idea 然后等待 相关 jar 包下载
5. 由于 spring-aspects 编译和其余包编译方法不同,故需要将 spring-aspects 从项目移除
### Demo
1. 新建一个 gradle 模块,然后引入 spring 其他模块作为依赖
由于 spring-context 模块自身也引入了 core、aop、beans 等模块,所以在这里不必引入它们了
```
dependencies {
compile(project(":spring-context"))
testCompile group: 'junit', name: 'junit', version: '4.12'
}
```
2. 实现 WelcomeService 及其实现类
```java
/*WelcomeService.java*/
public interface WelcomeService {
String sayHello(String name);
}
/*WelcomeServiceImpl.java*/
public class WelcomeServiceImpl implements WelcomeService {
@Override
public String sayHello(String name) {
System.out.println("欢迎您:"+ name);
return "success";
}
}
```
3. 通过 xml 文件将 WelcomeService 及其实现类注入 Ioc 容器
```xml
```
5. 实现测试类,调用容器中的 bean 检测是否注入成功,如果成功,同时也佐证了 Spring 源码编译成功
```java
public class Entrance {
public static void main(String[] args) {
System.out.println("Hello world!");
String xmlPath = "E:\\Git\\Gitee-Repository\\spring-framework-5.2.0.RELEASE\\springdemo\\src\\main\\resources\\spring\\spring-config.xml";
ApplicationContext applicationContext = new FileSystemXmlApplicationContext(xmlPath);
WelcomeService welcomeService = applicationContext.getBean("welcomeService", WelcomeService.class);
welcomeService.sayHello("Noah2021");
}
}
```
### 搭建自研框架雏形
1. 依靠骨架新建一个 web 项目
2. 在 pom 文件内导入 jsp 和 servlet 的依赖
```xml
javax.servlet
javax.servlet-api
4.0.1
provided
javax.servlet.jsp
javax.servlet.jsp-api
2.3.3
provided
```
3. 编写 jsp 文件
```jsp
<%@ page pageEncoding="UTF-8" isELIgnored="false" %>
Hello
Hello
厉害了,${name}
```
4. 编写对应的 web 后端代码
```java
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = "我的简易框架";
req.setAttribute("name", name);
req.getRequestDispatcher("/WEB-INF/jsp/hello.jsp").forward(req,resp);
}
}
```
5. 配置 tomcat 并启动
6. 理解 jsp 运行原理
## 业务系统架子的搭建
1. 创建 o2odb 数据库
2. 门面/外观模式(Facade):提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。下面举个门面模式的例子,其中 slf4j-log4j12 (Simple Logging Facade for Java)就是依靠门面模式解决了各个版本 jar 包依赖冲突问题。
```java
public class SubSystem {
public void turnOnTV() {
System.out.println("turnOnTV()");
}
public void setCD(String cd) {
System.out.println("setCD( " + cd + " )");
}
public void startWatching(){
System.out.println("startWatching()");
}
}
public class Facade {
private SubSystem subSystem = new SubSystem();
public void watchMovie() {
subSystem.turnOnTV();
subSystem.setCD("a movie");
subSystem.startWatching();
}
}
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.watchMovie();
}
}
```
3. 导入 slf4j-log4j12 和 lombok 的依赖,引入 log4j.properties 配置文件并测试(包括注解和工厂创建两者方式)
4. 实现 basicobject 相关类(HeadLine 和 ShopCategory)
5. 掌握泛型类、泛型接口、泛型方法的使用
- 泛型类、泛型方法
```java
/*GenericClassExample.java*/
@Data
public class GenericClassExample {
private T member;
public GenericClassExample(T member) {
this.member = member;
}
public T headSomething(T target) {
return target;
}
public String sayHello(String name) {
return "hello, " + name;
}
public static void printArray(T[] inputArray) {
for (T element : inputArray) {
System.out.printf("%s", element);
System.out.print(" ");
}
System.out.println();
}
}
/*GeneraicDemo.java*/
public class GeneraicDemo {
public static void handMember(GenericClassExample super Integer> genericClassExample){
Integer integer = 123 + (Integer) genericClassExample.getMember();
System.out.println("result is " + integer);
}
public static void main(String[] args) {
GenericClassExample integerExample = new GenericClassExample(123);
GenericClassExample stringExample = new GenericClassExample<>("abc");
System.out.println(integerExample.getClass());
System.out.println(stringExample.getClass());
handMember(integerExample);
Integer[] integers = {1, 2, 3, 4, 5, 6};
Double[] doubles = {1.1, 1.2, 1.3, 1.4, 1.5};
Character[] characters = {'A', 'B', 'C'};
stringExample.printArray(integers);
stringExample.printArray(doubles);
stringExample.printArray(characters);
}
/* class demo.generic.GenericClassExample //说明泛型只存在与编译期,详情请移步“泛型擦除”
class demo.generic.GenericClassExample //目的是避免过多的创建类而造成的运行时的过度消耗
result is 246
1 2 3 4 5 6
1.1 1.2 1.3 1.4 1.5
A B C
* */
}
```
- 泛型接口
```java
/*GenericFactory.java*/
public interface GenericFactory {
T nextObject();
N nextNumber();
}
/*GenericFactory.java*/
public class RobotFactory implements GenericFactory{
private String[] stringRobot = new String[]{"Hello","Hi"};
private Integer[] integerRobot = new Integer[]{111,000};
@Override
public String nextObject() {
Random random = new Random();
return stringRobot[random.nextInt(2)];//[0,2)
}
@Override
public Integer nextNumber() {
Random random = new Random();
return integerRobot[random.nextInt(2)];
}
public static void main(String[] args) {
GenericFactory factory = new RobotFactory();
System.out.println(factory.nextObject());
System.out.println(factory.nextNumber());
}
}
```
1. 实现 datatransferobject 相关类(MainPageInfoDTO 和 Result)
2. Service 层代码架子的搭建
- solo 相关类
- combine 相关类:包含多个 solo 相关类,统一对 controller 提供服务
3. Controller 层代码架子的搭建
## 自研框架IOC实现前奏
### 工厂模式
设计模式懂得都懂,不懂在这比划半天还是似懂非懂,可以结合~~[本人博客](https://noah2021.gitee.io/)~~和源码加深理解。
**简单工厂**
略
**工厂方法**
略
**抽象工厂**
略
### 反射
1. Class类的特点
- Class 类也是类的一种,class 则是关键字
- Class 类只有一个私有的构造函数,只有 JVM 能够创建 Class 类的实例
- JVM 中只有唯一一个和类相对应的 Class 对象来描述其类型信息
2. 获取Class对象的方式
```java
public static void main(String[] args) throws ClassNotFoundException {
//第一种方式获取class对象
ReflectTarget reflectTarget = new ReflectTarget();
Class reflectTargetClass1 = reflectTarget.getClass();
System.out.println("1==>"+reflectTargetClass1.getName());
//第二种方式获取class对象
Class reflectTargetClass2 = ReflectTarget.class;
System.out.println("2==>"+reflectTargetClass2.getName());
//第三种方式获取class对象
Class reflectTargetClass3 = Class.forName("demo.reflect.ReflectTarget");
System.out.println("3==>"+reflectTargetClass3.getName());
}
/*在运行期,一个类只有一个与之对应的Class对象产生
1==>demo.reflect.ReflectTarget
2==>demo.reflect.ReflectTarget
3==>demo.reflect.ReflectTarget
* */
```
3. 获取构造方法并调用:通过Class对象可以获取某个类中的构造方法
- 批量的方法
public Constructor[] getConstructors():所有"公有的"构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
- 获取单个的方法,并调用
public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、 公有;
- 调用构造方法(如果构造方法是 private 修饰需要先暴力反射):Constructor-->newInstance(Object... initargs)
4. 获取成员变量并调用
- 批量的
Field[] getFields():获取所有的"公有字段"
Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
- 获取单个的
public Field getField(String fieldName):获取某个"公有的"字段;
public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
- 设置字段的值(如果成员变量是 private 修饰需要先暴力反射)
Field --> public void set(Object obj,Object value):
- 参数说明
obj:要设置的字段所在的对象(非Class对象,是由构造方法生成的)
value:要为字段设置的值
5. 获取成员方法并调用
- 批量的
public Method[] getMethods():获取所有"公有方法"(包含了父类的方法也包含Object类)
public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
- 获取单个的
public Method getMethod(String name,Class>... parameterTypes):
public Method getDeclaredMethod(String name,Class>... parameterTypes)
- 参数:
name : 方法名
Class ... : 形参的Class类型对象
- 调用方法(如果成员方法是 private 修饰需要先暴力反射)
Method --> public Object invoke(Object obj,Object... args):
- 参数说明
obj : 要调用方法的对象(非Class对象,是由构造方法生成的)
args:调用方式时所传递的实参
### 注解
1. 注解的功能
- 作为特定的标记,用于告诉编译器一些信息
- 编译时动态处理,如动态生成代码
- 运行时动态处理,作为额外信息的媒体,如获取注解信息
2. 注解的分类
- 标准注解:@Override、@Deprecated、@SupressWarnings
- 元注解(修饰注解的注解,通常用在注解的定义之上):@Retention、@Target、@Inherited、@Documented
@Target:定义注解的作用目标
@Retention:定义注解的生命周期,用于决定被该元注解修饰的注解是否显示在编译的文件中
@Inherited:是否允许子类继承该注解
@Documented:注解是否应当被包含在 JavaDoc 文档中
- 自定义注解(使用元注解实现)
3. 自定义注解
- 自定义注解格式
```java
public @interface 注解名{
修饰符 返回值 属性名() 默认值;
修饰符 返回值 属性名() 默认值;
...
}
```
- 注解属性支持的类型
- 所有的基本数据类型
- Enum类型
- String类型
- Annotation类型
- Class 类型
- 以上所有类型的数组
4. 注解实现
```java
/*CourseInfoAnnotation.java*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CourseInfoAnnotation {
//课程名称
public String courseName();
//课程标签
public String courseTag();
//课程简介
public String courseProfile();
//课程序号
public int courseIndex() default 303;
}
/*PersonInfoAnnotation.java*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfoAnnotation {
//名字
public String name();
//年龄
public int age() default 20;
//性别
public String gender() default "男";
//开发语言
public String[] language();
}
/*Noah2021Course.java*/
@CourseInfoAnnotation(courseName = "数学",courseTag = "高中",courseProfile = "又难又多")
public class Noah2021Course {
@PersonInfoAnnotation(name = "Noah2021",language = {"Java","c++","python"})
private String author;
@CourseInfoAnnotation(courseName = "化学",courseTag = "理综",courseProfile = "非常难")
public void getCourseInfo(){
}
}
/*AnnotationParser.java*/
public class AnnotationParser {
//解析类的注解
public static void parseTypeAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName("demo.annotation.Noah2021Course");
//这里获取的是class对象的注解,而不是其里面的方法和成员变量的注解
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
CourseInfoAnnotation courseInfoAnnotation = (CourseInfoAnnotation) annotation;
System.out.println("课程名:" + courseInfoAnnotation.courseName() + "\n" +
"课程标签:" + courseInfoAnnotation.courseTag() + "\n" +
"课程简介:" + courseInfoAnnotation.courseProfile() + "\n" +
"课程序号:" + courseInfoAnnotation.courseIndex()+"\n");
}
}
//解析成员变量上的标签
public static void parseFieldAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName("demo.annotation.Noah2021Course");
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
//判断当前成员变量中是否有指定注解类型的注解
boolean hasAnnotation = f.isAnnotationPresent(PersonInfoAnnotation.class);
if (hasAnnotation) {
PersonInfoAnnotation personInfoAnnotation = f.getAnnotation(PersonInfoAnnotation.class);
System.out.println("名字:" + personInfoAnnotation.name() + "\n" +
"年龄:" + personInfoAnnotation.age() + "\n" +
"性别:" + personInfoAnnotation.gender());
for (String language : personInfoAnnotation.language()) {
System.out.println("开发语言:" + language);
}
}
}
}
//解析方法注解
public static void parseMethodAnnotation() throws ClassNotFoundException {
Class clazz = Class.forName("demo.annotation.Noah2021Course");
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
//判断当前成员方法中是否有指定注解类型的注解
boolean hasAnnotation = method.isAnnotationPresent(CourseInfoAnnotation.class);
if (hasAnnotation) {
CourseInfoAnnotation courseInfoAnnotation = method.getAnnotation(CourseInfoAnnotation.class);
System.out.println("\n" + "课程名:" + courseInfoAnnotation.courseName() + "\n" +
"课程标签:" + courseInfoAnnotation.courseTag() + "\n" +
"课程简介:" + courseInfoAnnotation.courseProfile() + "\n" +
"课程序号:" + courseInfoAnnotation.courseIndex() + "\n");
}
}
}
public static void main(String[] args) throws ClassNotFoundException {
parseTypeAnnotation();
parseFieldAnnotation();
parseMethodAnnotation();
}
}
```
5. 注解的工作原理
- 通过键值对的形式为注解属性赋值
- 编译器检查注解的使用范围,将注解信息写入元素属性表
- 运行时 JVM 将 RUNTIME 的所有注解属性取出并最终存入 map 里(单个Class文件内所有的RUNTIME的注解而非整个项目的RUNTIME注解)
- 创建AnnotationInvacationHandler实例并传入前面的map
- JVM使用JDK动态代理为注解生成动态代理类,并初始化处理器
- 调用invoke方法,通过传入方法名返回注解对应的属性值
### 上述学习对自研框架的意义
1. 控制反转 IoC (Inversion of Controller)
- 依托一个类似工厂的 IoC 容器
- 将对象的创建、依赖关系的管理以及生命周期交由IoC容器管理
- 降低系统在实现上的复杂性和耦合度,易于扩展,满足开闭原则(软件中的对象(类、模块、方法等),对于扩展是开放的,对于修改是封闭的)
2. IoC容器的优势
- 避免在各处使用new来创建类 ,并且可以做到统一维护
- 创建实例的时候不需要 了解其中的细节
- 反射+工厂模式的合体,满足开闭原则
3. 依赖注入
- 构造方法实现注入
- setter实现注入
- 接口实现注入
- 注解实现注入
4. 依靠倒置原则、IoC、DI、IoC容器的关系
5. 控制反转的例子
一个行李箱是由轮子-->地盘-->箱体-->行李箱构成,但是当轮子发生改变大小时,上层的结构都需要改变,这无疑是不可接受的
依靠控制反转就可以很好地解决这个问题

### 导图总结

## 自研框架IoC容器的实现
### 框架具备的基本功能
- 解析配置(XML、注解等)
- 定义和注册对象
- 注入对象
- 提供通用的工具类
### IoC容器的实现
创建注解-->提取标记对象-->实现容器-->依赖注入
#### 创建注解
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Resipotory {
}
```
#### 提取标记对象
- 指定范围,获取范围内的所有类
- 遍历所有类,获取被注解标记的类并加载进容器中
- 测试
```java
/**
* 〈通过类加载器可以获得多种资源,我们就是要获取 file 类型的class集合〉
*
* @author Noah2021
* @create 2021/3/7
* @return
*/
public class ClassUtil {
public static final String FILE_PROTOCOL = "file";
/**
* 获取包下类集合
*
* @param packageName 包名
* @return 类集合
*/
public static Set> extractPackageClass(String packageName){
//1.获取到类的加载器。
ClassLoader classLoader = getClassLoader();
//2.通过类加载器获取到加载的资源
URL url = classLoader.getResource(packageName.replace(".", "/"));
if (url == null){
//log.warn("unable to retrieve anything from package: " + packageName);
System.out.println("【WARN】unable to retrieve anything from package: " + packageName);
return null;
}
//3.依据不同的资源类型,采用不同的方式获取资源的集合
Set> classSet = null;
//过滤出文件类型的资源
if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){
classSet = new HashSet>();
File packageDirectory = new File(url.getPath());
extractClassFile(classSet, packageDirectory, packageName);
}
//TODO 此处可以加入针对其他类型资源的处理
return classSet;
}
/**
* 递归获取目标package里面的所有class文件(包括子package里的class文件)
*
* @param emptyClassSet 装载目标类的集合
* @param fileSource 目录
* @param packageName 包名
* @return 类集合
*/
private static void extractClassFile(Set> emptyClassSet, File fileSource, String packageName) {
if(!fileSource.isDirectory()){
return;
}
//如果是一个目录,则调用其listFiles方法获取文件夹下的文件或文件夹
File[] files = fileSource.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
if(file.isDirectory()){
return true;
} else{
//获取文件的绝对值路径
String absoluteFilePath = file.getAbsolutePath();
if(absoluteFilePath.endsWith(".class")){
//若是class文件,则直接加载
addToClassSet(absoluteFilePath);
}
}
return false;
}
//根据class文件的绝对值路径,获取并生成class对象,并放入classSet中
private void addToClassSet(String absoluteFilePath) {
//1.从class文件的绝对值路径里提取出包含了package的类名
//如/Users/baidu/imooc/springframework/sampleframework/target/classes/com/imooc/entity/dto/MainPageInfoDTO.class
//需要弄成com.imooc.entity.dto.MainPageInfoDTO
absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
//去掉.class后缀
className = className.substring(0, className.lastIndexOf("."));
//2.通过反射机制获取对应的Class对象并加入到classSet里
Class targetClass = loadClass(className);
emptyClassSet.add(targetClass);
}
});
if(files != null){
for(File f : files){
//递归调用
extractClassFile(emptyClassSet, f, packageName);
}
}
}
/**
* 获取Class对象
*
* @param className class全名=package + 类名
* @return Class
*/
public static Class> loadClass(String className){
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
//log.error("load class error:", e);
System.out.println("load class error:"+ e);
throw new RuntimeException(e);
}
}
/**
* 获取classLoader
*
* @return 当前ClassLoader
*/
public static ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
}
//测试
public class ClassUtilTest {
@DisplayName("提取目标类方法:extractPackageClassTest")
@Test
public void extractPackageClassTest(){
Set> classSet = ClassUtil.extractPackageClass("com.noah2021.entity");
for (Class clazz:classSet) {
System.out.println(clazz);
}
Assertions.assertEquals(4, classSet.size());
}
}
}
/*控制台打印结果*/
class com.noah2021.entity.dto.MainPageInfoDTO
class com.noah2021.entity.bo.HeadLine
class com.noah2021.entity.bo.ShopCategory
class com.noah2021.entity.dto.Result
```
#### 实现容器
- 初始化
```java
/*BeanContainer.java*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanContainer {
private enum ContainerHolder {
HOLDER;
private BeanContainer instance;
ContainerHolder() {
instance = new BeanContainer();
}
}
//获取bean容器实例
public static BeanContainer getInstance() {
return ContainerHolder.HOLDER.instance;
}
}
```
- 保存Class对象及其实例的载体
```java
/*BeanContainer.java*/
//用于存放所有被配资标记的目标对象的Map
private final Map, Object> beanMap = new ConcurrentHashMap();
```
- 保存Class对象及其实例的载体
1. 注解配置的管理与获取
2. 获取指定范围内的Class对象(上面已完成)
3. 依据配置提取Class对象,连同实例一并存入容器
4. 测试
```java
/*BeanContainer.java*/
//加载bean的注解列表
private static final List> BEAN_ANNOTATION
= Arrays.asList(Component.class, Controller.class, Service.class, Repository.class, Aspect.class);
//是否加载过bean
private boolean loaded = false;
public boolean isLoaded() {
return loaded;
}
//bean实例数量
public int size() {
return beanMap.size();
}
//扫描加载所有的bean
public synchronized void loadBeans(String packageName) {
//判断bean容器是否被加载过
if(isLoaded()){
System.out.println("【WARN】BeanContainer has been loaded");
return;
}
Set> classSet = ClassUtil.extractPackageClass(packageName);
//新建一个验证工具类ValidationUtil用于字符串、数组等的判空校验
if(ValidationUtil.isEmpty(classSet)){
System.out.println("【WARN】extract nothing from packageName" + packageName);
return;
}
for (Class clazz:classSet) {
for (Class extends Annotation> annotation: BEAN_ANNOTATION) {
//如果类上面标记了定义的注解
if(clazz.isAnnotationPresent(annotation))
//将目标类本身作为键,目标类的实例作为值,放入到beanMap中,定义一个newInstance方法用于通过反射初始化对象
beanMap.put(clazz, ClassUtil.newInstance(clazz,true));
}
}
loaded = true;
}
//在com.noah2021包里面加上一些自定义注解,然后进行测试
public class BeanContainerTest {
private static BeanContainer beanContainer;
@BeforeAll
static void init(){
beanContainer = BeanContainer.getInstance();
}
@DisplayName("加载目标类及其实例到BeanContainer:loadBeansTest")
@Test
public void loadBeansTest(){
Assertions.assertEquals(false, beanContainer.isLoaded());
beanContainer.loadBeans("com.noah2021");
Assertions.assertEquals(6, beanContainer.size());
Assertions.assertEquals(true, beanContainer.isLoaded());
}
}
```
- 容器的操作方式,涉及到容器的增删改查
1. 增加、删除操作
2. 通过Class获取对应实例
3. 获取所有的Class和实例
4. 通过注解来获取被注解标注的Class
5. 通过超类获取对应的子类Class
6. 获取容器载体保存Class的数量
7. 在com.noah2021包里添加自定义注解,并进行单元测试
```java
/*BeanContainer.java*/
/**
* 添加一个class对象及其Bean实例
* @param clazz Class对象
* @param bean Bean实例
* @return 原有的Bean实例, 没有则返回null
*/
public Object addBean(Class> clazz, Object bean) {
return beanMap.put(clazz, bean);
}
/*移除一个IOC容器管理的对象*/
public Object removeBean(Class> clazz) {
return beanMap.remove(clazz);
}
/*根据Class对象获取Bean实例*/
public Object getBean(Class> clazz) {
return beanMap.get(clazz);
}
/*获取容器管理的所有Class对象集合*/
public Set> getClasses(){
return beanMap.keySet();
}
/*获取所有Bean集合*/
public Set