[Spring] 如何实现一个低配版`Spring BeanFactory`?
如何实现一个低配版Spring BeanFactory
?
@TOC
手机用户请
横屏
获取最佳阅读体验,REFERENCES
中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。
平台 | 地址 |
---|---|
CSDN | https://blog.csdn.net/sinat_28690417 |
简书 | https://www.jianshu.com/u/3032cc862300 |
个人博客 | https://yiyuery.club |
结合Spring BeanFactory实例扫描和注入思想进行深入编码实战:工厂化管理运行中实例对象
准备工作
- 包扫描工具类定义
/* * @ProjectName: 编程学习 * @Copyright: 2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved. * @address: http://xiazhaoyang.tech * @date: 2019/5/20 20:57 * @email: https:yiyuery.github.io/NoteBooks * @description: 本内容仅限于编程技术学习使用,转发请注明出处. */ package com.example.swagger.common.component; import lombok.extern.slf4j.Slf4j; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; /** * <p> * 扫描指定路径下class * </p> * * @author xiazhaoyang * @version v1.0.0 * @date 2019-06-05 22:46 * @modificationHistory=========================逻辑或功能性重大变更记录 * @modify By: {修改人} 2019-06-05 * @modify reason: {方法名}:{原因} * ... */ @Slf4j public class ClasspathPackageScanner { private String basePackage; private ClassLoader cl; /** * Construct an instance and specify the base package it should scan. * @param basePackage The base package to scan. */ public ClasspathPackageScanner(String basePackage) { this.basePackage = basePackage; this.cl = getClass().getClassLoader(); } /** * Construct an instance with base package and class loader. * @param basePackage The base package to scan. * @param cl Use this class load to locate the package. */ public ClasspathPackageScanner(String basePackage, ClassLoader cl) { this.basePackage = basePackage; this.cl = cl; } /** * Get all fully qualified names located in the specified package * and its sub-package. * * @return A list of fully qualified names. * @throws IOException */ public List<String> getFullyQualifiedClassNameList() throws IOException { log.info("Start Scan...", basePackage); return doScan(basePackage, new ArrayList<>()); } /** * Actually perform the scanning procedure. * * @param basePackage * @param nameList A list to contain the result. * @return A list of fully qualified names. * * @throws IOException */ private List<String> doScan(String basePackage, List<String> nameList) throws IOException { // replace dots with splashes String splashPath = StringUtil.dotToSplash(basePackage); // get file path URL url = cl.getResource(splashPath); String filePath = StringUtil.getRootPath(url); // Get classes in that package. // If the web server unzips the jar file, then the classes will exist in the form of // normal file in the directory. // If the web server does not unzip the jar file, then classes will exist in jar file. // contains the name of the class file. e.g., Apple.class will be stored as "Apple" List<String> names = null; if (isJarFile(filePath)) { // jar file if (log.isDebugEnabled()) { log.debug("{} fina a jar file", filePath); } names = readFromJarFile(filePath, splashPath); } else { // directory if (log.isDebugEnabled()) { log.debug("{} find a directory", filePath); } names = readFromDirectory(filePath); } for (String name : names) { if (isClassFile(name)) { nameList.add(toFullyQualifiedName(name, basePackage)); } else { // this is a directory // check this directory for more classes // do recursive invocation doScan(basePackage + "." + name, nameList); } } if (log.isDebugEnabled()) { for (String n : nameList) { log.debug("load {}", n); } } return nameList; } /** * Convert short class name to fully qualified name. * e.g., String -> java.lang.String */ private String toFullyQualifiedName(String shortName, String basePackage) { StringBuilder sb = new StringBuilder(basePackage); sb.append('.'); sb.append(StringUtil.trimExtension(shortName)); return sb.toString(); } private List<String> readFromJarFile(String jarPath, String splashedPackageName) throws IOException { if (log.isDebugEnabled()) { log.debug("从JAR包中读取类: {}", jarPath); } JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath)); JarEntry entry = jarIn.getNextJarEntry(); List<String> nameList = new ArrayList<>(); while (null != entry) { String name = entry.getName(); if (name.startsWith(splashedPackageName) && isClassFile(name)) { nameList.add(name); } entry = jarIn.getNextJarEntry(); } return nameList; } private List<String> readFromDirectory(String path) { File file = new File(path); String[] names = file.list(); if (null == names) { return null; } return Arrays.asList(names); } private boolean isClassFile(String name) { return name.endsWith(".class"); } private boolean isJarFile(String name) { return name.endsWith(".jar"); } /** * For test purpose. */ public static void main(String[] args) throws Exception { ClasspathPackageScanner scan = new ClasspathPackageScanner("com.example.swagger"); scan.getFullyQualifiedClassNameList(); } } class StringUtil{ private StringUtil() { } /** * "file:/home/whf/cn/fh" -> "/home/whf/cn/fh" * "jar:file:/home/whf/foo.jar!cn/fh" -> "/home/whf/foo.jar" */ public static String getRootPath(URL url) { String fileUrl = url.getFile(); int pos = fileUrl.indexOf('!'); if (-1 == pos) { return fileUrl; } return fileUrl.substring(5, pos); } /** * "cn.fh.lightning" -> "cn/fh/lightning" * @param name * @return */ public static String dotToSplash(String name) { return name.replaceAll("\\.", "/"); } /** * "Apple.class" -> "Apple" */ public static String trimExtension(String name) { int pos = name.indexOf('.'); if (-1 != pos) { return name.substring(0, pos); } return name; } /** * /application/home -> /home * @param uri * @return */ public static String trimURI(String uri) { String trimmed = uri.substring(1); int splashIndex = trimmed.indexOf('/'); return trimmed.substring(splashIndex); } }
- 自动注入注解定义
AutoRegister
: 类似于Spring
的@Component
注解
/* * @ProjectName: 编程学习 * @Copyright: 2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved. * @address: http://xiazhaoyang.tech * @date: 2019/5/20 20:57 * @email: https:yiyuery.github.io/NoteBooks * @description: 本内容仅限于编程技术学习使用,转发请注明出处. */ package com.example.swagger.api; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * <p> * 自动注入注解定义`AutoRegister`: 类似于`Spring`的`@Component`注解 * </p> * * @author xiazhaoyang * @version v1.0.0 * @date 2019-06-05 21:46 * @modificationHistory=========================逻辑或功能性重大变更记录 * @modify By: {修改人} 2019-06-05 * @modify reason: {方法名}:{原因} * ... */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface AutoRegister { String name() default ""; }
- 配置类属性填充注解定义
PropNameSpace
,读取Properties
前缀自动注入
/* * @ProjectName: 编程学习 * @Copyright: 2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved. * @address: http://xiazhaoyang.tech * @date: 2019/5/20 20:57 * @email: https:yiyuery.github.io/NoteBooks * @description: 本内容仅限于编程技术学习使用,转发请注明出处. */ package com.example.swagger.api; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * <p> * 配置类属性填充注解定义`PropNameSpace` ,读取`Properties`前缀自动注入 * </p> * * @author xiazhaoyang * @version v1.0.0 * @date 2019-06-05 21:43 * @modificationHistory=========================逻辑或功能性重大变更记录 * @modify By: {修改人} 2019-06-05 * @modify reason: {方法名}:{原因} * ... */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface PropNameSpace { /** * 入参前缀 * @return */ String prefix() default ""; }
- 实例工厂定义
/* * @ProjectName: 编程学习 * @Copyright: 2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved. * @address: http://xiazhaoyang.tech * @date: 2019/5/20 20:57 * @email: https:yiyuery.github.io/NoteBooks * @description: 本内容仅限于编程技术学习使用,转发请注明出处. */ package com.example.swagger.common.context; import java.util.HashMap; import java.util.Map; /** * <p> * * </p> * * @author xiazhaoyang * @version v1.0.0 * @date 2019-06-05 22:45 * @modificationHistory=========================逻辑或功能性重大变更记录 * @modify By: {修改人} 2019-06-05 * @modify reason: {方法名}:{原因} * ... */ public class AppBeanContext { private static class SingletonHolder { private static final AppBeanContext INSTANCE = new AppBeanContext(); } public static final AppBeanContext getInstance() { return SingletonHolder.INSTANCE; } private static final Map<String, Object> HOLDER = new HashMap<>(); /** * 注册实例 * @param name * @param bean */ public void registerBean(String name, Object bean) { if (HOLDER.containsKey(name)) { throw new IllegalStateException("bean repetition register!"); } HOLDER.putIfAbsent(name, bean); } /** * 获取实例 * @param name * @param clazz * @param <T> * @return */ public <T> T getBean(String name, Class<T> clazz) { if (!HOLDER.containsKey(name)) { throw new IllegalArgumentException("bean repetition register!"); } return clazz.cast(HOLDER.get(name)); } }
思路
- 首先通过指定
package
路径下的class文件扫描 - 然后通过自定义注解完成,判断是否需要自动注入。
- 如果含有
AutoRegister
注解,则通过反射生成实例并存放到实例工厂中 - 如果含有
PropNameSpace
注解,则自动读取yaml文件中的属性来进行填充
核心源码
- 启动初始化类
/* * @ProjectName: 编程学习 * @Copyright: 2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved. * @address: https:yiyuery.club * @date: 2019/5/20 20:57 * @email: xiazhaoyang@live.com * @description: 本内容仅限于编程技术学习使用,转发请注明出处. */ package com.example.swagger.common.component; import com.example.swagger.api.AutoRegister; import com.example.swagger.api.ITransformPlugin; import com.example.swagger.api.PropNameSpace; import com.example.swagger.common.base.AbstractOfficeTransformPlugin; import com.example.swagger.common.base.AbstractSwaggerTransformPlugin; import com.example.swagger.common.configuration.ApplicationYamlLoader; import com.example.swagger.common.configuration.OfficeTransformConfig; import com.example.swagger.common.context.AppBeanContext; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.lang.reflect.Field; import java.util.List; /** * <p> * 启动初始化类 * </p> * * @author xiazhaoyang * @version v1.0.0 * @date 2019-06-07 05:33 * @modificationHistory=========================逻辑或功能性重大变更记录 * @modify By: {修改人} 2019-06-07 * @modify reason: {方法名}:{原因} * ... */ @Slf4j public class AppStarterInitial { static { log.info("start init..."); beforeStart(); log.info("end init..."); } private static void beforeStart() { try { ClasspathPackageScanner scan = new ClasspathPackageScanner("com.example.swagger"); List<String> classNames = scan.getFullyQualifiedClassNameList(); for (String clazz : classNames) { Class<?> c = Thread.currentThread().getContextClassLoader().loadClass(clazz); if (c.getAnnotations().length > 0) { //获取自动注入对象 if (c.getAnnotationsByType(AutoRegister.class).length > 0) { Object bean = c.newInstance(); //遍历属性中所有Field String prefixKey = ""; if (c.getAnnotationsByType(PropNameSpace.class).length > 0) { PropNameSpace[] annotationsByType = c.getAnnotationsByType(PropNameSpace.class); prefixKey = annotationsByType[0].prefix(); } Field[] declaredFields = c.getDeclaredFields(); for (Field field : declaredFields) { field.setAccessible(Boolean.TRUE); String filedKey = field.getAnnotationsByType(JsonProperty.class).length > 0 ? field.getAnnotationsByType(JsonProperty.class)[0].value() : field.getName(); String propKey = prefixKey + "." + filedKey; field.set(bean, ApplicationYamlLoader.getPropsByKey(propKey,field.getType())); } String beanName = c.getAnnotationsByType(AutoRegister.class)[0].name(); AppBeanContext.getInstance().registerBean( StringUtils.isNotBlank(beanName)?beanName: StringUtils.lowerCase(c.getSimpleName()).substring(0,1)+c.getSimpleName().substring(1), bean); } } } } catch (Throwable e) { log.error("init error", e); } } }
这个主要为了完成实例自动扫描和注入、参数配置类自动扫描填充参数的功能。
项目启动类集成这个类即可在实例化之前触发对应实例并注入到实例工厂中以备使用。
/* * @ProjectName: 编程学习 * @Copyright: 2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved. * @address: http://xiazhaoyang.tech * @date: 2019/5/20 20:57 * @email: https:yiyuery.github.io/NoteBooks * @description: 本内容仅限于编程技术学习使用,转发请注明出处. */ package com.example.swagger; import com.example.swagger.common.component.AppStarterInitial; import com.example.swagger.common.enums.FileTransformEnum; import com.example.swagger.common.enums.SwaggerGenEnum; /** * <p> * 启动类 * </p> * * @author xiazhaoyang * @version v1.0.0 * @date 2019-06-05 22:57 * @modificationHistory=========================逻辑或功能性重大变更记录 * @modify By: {修改人} 2019-06-05 * @modify reason: {方法名}:{原因} * ... */ public class SwaggerTransformApplication extends AppStarterInitial { public static void main(String[] args){ //TODO ... } }
效果
如此一来,我们的低配版Spring BeanFactory
就可以看到如下的效果了。
图片中打印的这个配置类:
/* * @ProjectName: 编程学习 * @Copyright: 2019 HangZhou xiazhaoyang Dev, Ltd. All Right Reserved. * @address: http://xiazhaoyang.tech * @date: 2019/5/20 20:57 * @email: https:yiyuery.github.io/NoteBooks * @description: 本内容仅限于编程技术学习使用,转发请注明出处. */ package com.example.swagger.common.configuration; import com.example.swagger.api.AutoRegister; import com.example.swagger.api.PropNameSpace; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * <p> * * </p> * * @author xiazhaoyang * @version v1.0.0 * @date 2019-06-05 21:50 * @modificationHistory=========================逻辑或功能性重大变更记录 * @modify By: {修改人} 2019-06-05 * @modify reason: {方法名}:{原因} * ... */ @Data @AllArgsConstructor @NoArgsConstructor @AutoRegister @PropNameSpace(prefix = "enhance.config") public class SwaggerEnhanceConfig { @JsonProperty("api-docs-url") private String apiDocsUrl; }
yaml:
enhance: config: api-docs-url: http://localhost:9000/swagger-resources/v2/api-docs?group=UI
通过图片我们可以看到,已经成功实现了配置类实例的内部参数填充了。
总结
本文通过自定义注解和反射模拟了Spring BeanFactory的一个简单的实例动态注入和管理功能。有兴趣的小伙伴可以深入了解下Spring的三大核心思想:IOC(控制反转),DI(依赖注入),AOP(面向切面编程)。
更多
扫码关注“架构探险之道”,获取更多源码和文章资源
知识星球(扫码加入获取源码和文章资源链接)
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
日志服务数据加工:语法框架与样例
概述 目前数据加工全面支持ETL语言, 更简单灵活. 参考ETL语言介绍 1. 全局操作事件 1.1. 字段赋值(set event) 1.1.1. 语法介绍 语法: SET_EVENT_新字段 = 固定值 SET_EVENT_新字段 = 表达式函数 说明 设置单个字段值,字段名为新字段,如果已经存在,则会覆盖现有字段值 新字段的字符约束是:中英文数字_组成,但不能以数字开头。注意:支持中文,但不支持:,因此不能通过这种方式设置日志的tag等,可以参考通用操作完成这类需求。 表达式函数返回无值None时,该操作会被忽略 表达式函数返回的任何类型的值都会被转化成字符串(例如,数字会被格式化为字符串,放回到事件中) 完整的表达式函数信息,请参考表达式函数 1.1.2. 样例 例子1:设置固定值添加一个新字段city值为上海。 SET_EVENT_city = "上
- 下一篇
java单例模式
JAVA单例模式 java使用单例设计模式的方式有很多种,比如饿汉式,懒汉式,静态内部类式,双重检测锁式以及枚举方式,这里主要讲枚举式。 新建一个接口类 /** * @author Gjing **/ public interface MySingleton { void doSomething(); } 新建枚举类,实现上面的接口 /** * @author Gjing **/ public enum Singleton implements MySingleton{ /** * 实例 */ INSTANCE{ @Override public void doSomething() { System.out.println("执行
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL8.0.19开启GTID主从同步CentOS8
- Mario游戏-低调大师作品
- CentOS关闭SELinux安全模块
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Red5直播服务器,属于Java语言的直播服务器
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池