工程搭建
使用 Maven 创建普通 Web 工程:
修改 pom.xml 添加依赖内容如下图:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.28</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
配置 Tomcat
添加 Tomcat 的 jar 包:
点击上图中的 Add Selected
即可完成添加,添加完成了之后你在创建的时候就会出现 Servlet
选项如下图所示:
创建一个 Servlet 测试一下:
/**
* 控制器
*
* @author BNTang
* @date 2021/09/29
*/
(name = "UserServlet", value = "/UserServlet")
public class UserServlet extends HttpServlet {
public void service(HttpServletRequest req, HttpServletResponse resp) {
System.out.println("test Servlet");
}
}
启动 Tomcat 在浏览器中访问:http://127.0.0.1:8080/UserServlet 即可测试我的测试结果如下图所示:
好了到了这里咱们的工程就已经创建完毕了,回归到我们文章的主要内容。
整体思路
解析配置
将相关配置加载进内存当中,存入定义好的数据结构,要管理哪些目标对象,通过编程语言进行解析,配置可以使用 xml,properties 或注解的方式。
定位与注册对象
查看哪些类当中标记了注解,定位到了目标对象之后,将对象注册到容器当中管理起来。
注入对象
在用户需要用到对象时,从容器中精确的获取对象,返回给用户给对应的属性进行注入(赋值)
提供通用的工具类
通过封装一些通用的工具,能够方便框架或用户方便进行操作。
创建注解
分别创建如下几个注解:
Component.java
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/9/30 030 9:07
* @description
**/
// 作用在类上
(ElementType.TYPE)
// 注解的生命周期为RUNTIME,因为使用反射创建对象
(RetentionPolicy.RUNTIME)
public @interface Component {
}
Controller.java
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/9/30 030 9:07
* @description
**/
// 作用在类上
(ElementType.TYPE)
// 注解的生命周期为RUNTIME,因为使用反射创建对象
(RetentionPolicy.RUNTIME)
public @interface Controller {
}
Repository.java
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/9/30 030 9:07
* @description
**/
// 作用在类上
(ElementType.TYPE)
// 注解的生命周期为RUNTIME,因为使用反射创建对象
(RetentionPolicy.RUNTIME)
public @interface Repository {
}
Service.java
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/9/30 030 9:07
* @description
**/
// 作用在类上
(ElementType.TYPE)
// 注解的生命周期为RUNTIME,因为使用反射创建对象
(RetentionPolicy.RUNTIME)
public @interface Service {
}
获取指定包下所有的类
指定范围,获取范围内的所有类,遍历所有类,获取被注解标记的类并加载进容器里,使用 classLoad 获取资源路径,直接使用 top.it6666
包是无法定位到对应的文件夹所在的路径,必须拿到具体路径,才能获取到该路径下所有 .class
文件。
ClassLoad 的作用:根据一个指定的类的名称,找到或者生成其对应的字节码,加载 Java 应用所需的资源:图片文件、配置文件、文件目录。
获取 URL 中文件与目录
根据文件与目录提取所有的 .class 文件
最终 ClassUtil.java 的内容如下:
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/9/30 030 9:24
* @description 获取指定包下所有的类
**/
public class ClassUtil {
public static final String FILE_PROTOCOL = "file";
public static final String CLASS_SUFFIX = ".class";
/**
* 获取指定包下所有的类
*
* @param packageName 包名
* @return {@link Set}<{@link Class}<{@link ?}>>
*/
("all")
public Set<Class<?>> getPackageClass(String packageName) {
// 1.获取类加载器
ClassLoader classLoader = ClassUtil.getClassLoad();
// 2.通过类加载器获取到加载的资源
// 2.1把top.it6666转成top/it6666
// 2.2通过类加载器获取加载的资源
URL url = classLoader.getResource(packageName.replace(".", "/"));
if (null == url) {
log.error("{}:该包下没有任何内容", packageName);
}
Set<Class<?>> classSet = null;
// 判断url是否为文件
if (Objects.equals(FILE_PROTOCOL, url.getProtocol())) {
// 创建集合
classSet = new HashSet<>();
String path = url.getPath();
try {
path = URLDecoder.decode(path, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 获取资源的实际路径
File packageDir = new File(path);
// 从路径当中提取Class文件,放到Set集合
ClassUtil.getDirClass(classSet, packageDir, packageName);
}
return classSet;
}
ClassLoader getClassLoad() {
return Thread.currentThread().getContextClassLoader();
}
/**
* 根据目录提出所有的class文件
*
* @param classSet Ioc容器
* @param packageDir 文件
* @param packageName 包名
*/
private static void getDirClass(Set<Class<?>> classSet, File packageDir, String packageName) {
// 如果不是一个目录直接结束
if (!packageDir.isDirectory()) {
return;
}
// 如果是目录,对目录里面的内容进行过滤
File[] files = packageDir.listFiles(new FileFilter() {
public boolean accept(File file) {
if (file.isDirectory()) {
return true;
}
String absolutePath = file.getAbsolutePath();
// 判断absolutePath是不是以 .class 后缀结尾
if (absolutePath.endsWith(CLASS_SUFFIX)) {
// 到了这里就代表是 .class 结尾的
addToClassSet(absolutePath);
}
return false;
}
private void addToClassSet(String absolutePath) {
// absolutePath 有可能是:/D:/aa/aaa 或者是:D:\bb\bb 可以使用 File.separator 来统一代替
// 把文件目录转成包的形式
absolutePath = absolutePath.replace(File.separator, ".");
String className = absolutePath.substring(absolutePath.indexOf(packageName));
className = className.substring(0, className.lastIndexOf("."));
try {
Class<?> targetClass = Class.forName(className);
classSet.add(targetClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
});
if (null != files) {
Arrays.stream(files).forEach(childFile -> ClassUtil.getDirClass(classSet, childFile, packageName));
}
}
}
创建 BeanContainer 容器
BeanContainer.java
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/10/08 008 17:24
* @description
**/
(access = AccessLevel.PRIVATE)
("unused")
public class BeanContainer {
public static BeanContainer getInstance() {
return ContainerHolder.HOLDER.instance;
}
/**
* 容器枚举
*
* @author BNTang
* @date 2021/10/08
*/
private enum ContainerHolder {
/**
* 持有人
*/
HOLDER;
/**
* 实例
*/
private final BeanContainer instance;
/**
* 容器
*/
ContainerHolder() {
instance = new BeanContainer();
}
}
}
定义相关属性扫描所有 Bean
修改 BeanContainer.java
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/10/08 008 17:24
* @description
**/
("unused")
(access = AccessLevel.PRIVATE)
public class BeanContainer {
public static BeanContainer getInstance() {
return ContainerHolder.HOLDER.instance;
}
/**
* 容器枚举
*
* @author BNTang
* @date 2021/10/08
*/
private enum ContainerHolder {
/**
* 持有人
*/
HOLDER;
/**
* 实例
*/
private final BeanContainer instance;
/**
* 容器
*/
ContainerHolder() {
instance = new BeanContainer();
}
}
/**
* bean注释
*/
private final List<Class<? extends Annotation>> beanAnnotation =
Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);
/**
* bean映射
*/
private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>();
/**
* 容器实例的个数
*
* @return int
*/
public int size() {
return beanMap.size();
}
/**
* 加载状态
*/
private boolean loaded = false;
/**
* 容器是否被加载过
*
* @return boolean
*/
public boolean isLoaded() {
return loaded;
}
/**
* 加载bean
*
* @param packageName 包名
*/
public synchronized void loadBeans(String packageName) {
// 判断容器是否被加载过
if (isLoaded()) {
log.warn("容器已经被加载!");
return;
}
// 1.获取该包下所有的字节码
Set<Class<?>> packageClass = ClassUtil.getPackageClass(packageName);
if (null == packageClass || packageClass.isEmpty()) {
log.warn("get null from packageName:{}", packageName);
return;
}
// 2.遍历所有的字节码对象
packageClass.forEach(clazz -> beanAnnotation.forEach(annotation -> {
// 2.1.判断当前字节码上是否有指定的注解
if (clazz.isAnnotationPresent(annotation)) {
// 创建对象存放到集合当中
beanMap.put(clazz, BeanContainer.newInstance(clazz, false));
}
}));
loaded = true;
}
private static <T> T newInstance(Class<?> clazz, boolean accessible) {
Constructor<?> declaredConstructor;
try {
declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(accessible);
return (T) declaredConstructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("创建实例失败!");
}
}
}
改造一下我们之前的测试类代码移动目录位置,移动到 test 包当中如下,顺便改一下我们自定义的 IOC 与 DI 功能代码存放包的位置:
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/9/30 030 14:36
* @description
**/
public class MyTest {
private static BeanContainer beanContainer;
public static void init() {
beanContainer = BeanContainer.getInstance();
}
("all")
void testClassUtil() {
ClassUtil.getPackageClass("top.it6666").forEach(System.out::println);
}
void testBeanContainer() {
Assertions.assertFalse(beanContainer.isLoaded());
beanContainer.loadBeans("top.it6666");
Assertions.assertEquals(1, beanContainer.size());
Assertions.assertTrue(beanContainer.isLoaded());
}
}
创建一个 MyController.java 然后在该类上添加 @Controller
注解导包是我们自己定义的那个注解:
然后运行测试类即可查看结果:
容器相关操作
增加删除操作,可以通过 class 获取对应的实例。获取所有的 key 和所有的 value。通过注解来获取被注解标注的 class。通过传入接口,来获取接口实现类的 class 对象。
/**
* 添加bean
*
* @param clazz clazz
* @param obj obj
* @return {@link Object}
*/
public Object addBean(Class<?> clazz, Object obj) {
return beanMap.put(clazz, obj);
}
/**
* 删除bean
*
* @param clazz clazz
* @return {@link Object}
*/
public Object removeBean(Class<?> clazz) {
return beanMap.remove(clazz);
}
/**
* 获取Bean
*
* @param clazz clazz
* @return {@link Object}
*/
public Object getBean(Class<?> clazz) {
return beanMap.get(clazz);
}
/**
* 获取容器所有的classes
*
* @return {@link Set}<{@link Class}<{@link ?}>>
*/
public Set<Class<?>> getClasses() {
return beanMap.keySet();
}
/**
* 获取容器所有的Bean实例
*
* @return {@link Set}<{@link Object}>
*/
public Set<Object> getBeans() {
return new HashSet<>(beanMap.values());
}
添加根据指定注解获取该注解标记的 Bean 的 Class 集合:
/**
* 根据指定注解获取该注解标记的 Bean 的 Class 集合
*
* @param annotation 注解
* @return {@link Set}<{@link Class}<{@link ?}>>
*/
public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation) {
// 1.获取容器当中所有的class
Set<Class<?>> classes = this.getClasses();
if (null == classes || classes.isEmpty()) {
log.info("没有任何信息!");
return null;
}
Set<Class<?>> classSet = new HashSet<>();
classes.forEach(clazz -> {
if (clazz.isAnnotationPresent(annotation)) {
// 保存到集合当中
classSet.add(clazz);
}
});
return classSet.size() > 0 ? classSet : null;
}
添加根据传入的接口,到容器当中找对应的实现类或子类:
/**
* 根据传入的接口,到容器当中找对应的实现类或子类
*
* @param interfaceOrClass 接口或类
* @return {@link Set}<{@link Class}<{@link ?}>>
*/
public Set<Class<?>> getClassBySuper(Class<?> interfaceOrClass) {
// 1.获取容器当中所有的class
Set<Class<?>> classes = getClasses();
if (null == classes || classes.isEmpty()) {
log.info("没有任何信息!");
return null;
}
Set<Class<?>> classSet = new HashSet<>();
classes.forEach(clazz -> {
// 判断当前字节码是否为指定字节码的子类或接口并且指定的字节码不等于本身字节码
if (interfaceOrClass.isAssignableFrom(clazz) && !Objects.equals(clazz, interfaceOrClass)) {
classSet.add(clazz);
}
});
return classSet.size() > 0 ? classSet : null;
}
依赖注入
定义相关的注解标签,实现创建被注解标记的成员变量实例,并将其注入到成员变量里,依赖注入使用:
创建 Autowired.java
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/10/25 025 9:01
* @description
**/
/*
及作用在成员变量上
* */
(ElementType.FIELD)
/*
保留在运行时
* */
(RetentionPolicy.RUNTIME)
public @interface Autowired {
String value() default "";
}
依赖注入实现思路:
依赖注入整体实现
DependencyInjector.java
/**
* 依赖注入
*
* @author BNTang
* @date 2021/10/25
*/
public class DependencyInjector {
/**
* Bean容器
*/
private final BeanContainer beanContainer;
/**
* 依赖注入,构造器
*/
public DependencyInjector() {
beanContainer = BeanContainer.getInstance();
}
/**
* 执行Ioc操作
*/
public void doIoc() {
// 1.遍历Bean容器中所有的Class对象
beanContainer.getClasses().forEach(clazz -> {
// 2.遍历Class对象的所有成员变量
Field[] declaredFields = clazz.getDeclaredFields();
if (declaredFields.length == 0) {
// 当前没有成员变量,继续查找下一个
return;
}
// 有成员变量
Arrays.stream(declaredFields).forEach(field -> {
// 3.找出被Autowired标记的成员变量
if (field.isAnnotationPresent(Autowired.class)) {
// 获取注解实例
// 被 Autowired注解标注
// 4.获取这些成员变量的类型
Class<?> fieldType = field.getType();
// 5.获取成员变量的类型在容器里对应的实例
Object instanceObj = this.getFieldInstance(fieldType, field.getAnnotation(Autowired.class).value());
// 6.通过反射将对象的成员变量实例进行赋值操作
if (null == instanceObj) {
throw new RuntimeException("成员属性注入失败:" + fieldType.getName());
} else {
// 获取当前类实例
// 给当前类实例成员变量赋值
this.setField(field, beanContainer.getBean(clazz), instanceObj, true);
}
}
});
});
}
/**
* 根据Class在BeanContainer里获取其实例或者实现类
*
* @param fieldClass 成员变量类型
* @param autowiredValue autowired注解的内容
* @return {@link Object}
*/
private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
Object fieldValue = beanContainer.getBean(fieldClass);
// 字段是实现类,直接返回
if (null != fieldValue) {
return fieldValue;
} else {
// 字段是接口
Class<?> implClass = this.getImplClass(fieldClass, autowiredValue);
if (implClass != null) {
return beanContainer.getBean(implClass);
} else {
return null;
}
}
}
/**
* 得到impl类
*
* @param fieldClass 接口
* @param autowiredValue autowired的价值
* @return {@link Class}<{@link ?}>
*/
private Class<?> getImplClass(Class<?> fieldClass, String autowiredValue) {
//从容器当中获取该接口的实现类
Set<Class<?>> classSet = beanContainer.getClassBySuper(fieldClass);
if (null != classSet && !classSet.isEmpty()) {
if (Objects.equals(autowiredValue, "")) {
if (classSet.size() == 1) {
return classSet.iterator().next();
} else {
// 如果有多个实现类
throw new RuntimeException("有多个实现类,请指定具体的实现类名称:" + fieldClass.getName());
}
} else {
// 注解当中有设置值
for (Class<?> clazz : classSet) {
if (autowiredValue.equals(clazz.getSimpleName())) {
return clazz;
}
}
}
}
return null;
}
/**
* 设置字段
*
* @param field 字段
* @param targetObject 目标对象
* @param obj obj
* @param accessible 是否可访问
*/
private void setField(Field field, Object targetObject, Object obj, boolean accessible) {
field.setAccessible(accessible);
try {
field.set(targetObject, obj);
} catch (IllegalAccessException e) {
log.error("setField error:" + e);
throw new RuntimeException(e);
}
}
}
测试依赖注入
创建 Service
IUserService.java
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/10/25 025 10:03
* @description
**/
public interface IUserService {
/**
* 显示
*/
void show();
}
IUserServiceImpl.java
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/10/25 025 10:04
* @description
**/
public class IUserServiceImpl implements IUserService {
public void show() {
System.out.println("my is IUserServiceImpl");
}
}
修改 MyController.java
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/10/09 009 11:31
* @description
**/
public class MyController {
private IUserService iUserService;
public void testShow() {
this.iUserService.show();
}
}
修改 MyTest.java 当前 IUserService 只有一个实现类所以 @Autowired 是可以直接标记即可不用指定名称,在 MyTest 添加 testIoc 方法:
void testIoc() {
beanContainer.loadBeans("top.it6666");
MyController myController = (MyController) beanContainer.getBean(MyController.class);
DependencyInjector dependencyInjector = new DependencyInjector();
dependencyInjector.doIoc();
myController.testShow();
}
运行结果如下:
如上是只有一个实现类,紧接着在来新建一个实现类,在 impl 包当中创建 ITaoBaoServiceImpl.java:
/**
* @author BNTang
* @program SpringPro
* @date Created in 2021/10/25 025 10:42
* @description
**/
public class ITaoBaoServiceImpl implements IUserService {
public void show() {
System.out.println("my is ITaoBaoServiceImpl");
}
}
再次运行测试类代码结果如下图:
修改 MyController.java:
再次运行测试类代码运行结果如下图所示: