博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从零开始实现一个简易的Java MVC框架(二)--实现Bean容器
阅读量:6915 次
发布时间:2019-06-27

本文共 10472 字,大约阅读时间需要 34 分钟。

项目准备

首先确保你拥有以下环境或者工具

  • idea
  • java 8
  • maven 3.3.X
  • lombok插件

然后我们创建一个maven工程,编写pom.xml引入一些需要的依赖

1.8
1.8
UTF-8
1.7.25
1.16.20
org.slf4j
slf4j-log4j12
${slf4j-api.version}
org.projectlombok
lombok
${lombok.version}
provided
复制代码

目前只需要lombok和log4j两个依赖就可以完成前面几个功能的实现,其他需要的依赖等到后面需要的时候再加。

接着把项目一些基本的包结构创建一下,如下图

resources文件夹下的log4j.properties文件为log4j输出格式化参数,大家可以根据自己的喜好和需求编写,我自己的只是为了方便调试使用的,下面是我自己的。

### 设置###log4j.rootLogger = debug,stdout### 输出信息到控制抬 ###log4j.appender.stdout = org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.Target = System.outlog4j.appender.stdout.layout = org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern = %c %d{ISO8601} -- %p -- %m%n复制代码

创建工具类

为了方便后续代码的编写,我们先创建工具类。

com.zbw.util包下创建两个工具类:ValidateUtilClassUtil

ValidateUtil主要负责属性的验证,这个类的完整代码就不贴了,就是检查各种类型的值是否为空或者是否不为空。

/** * 验证相关工具类 */public final class ValidateUtil {    /**     * Object是否为null     */    public static boolean isEmpty(Object obj) {        return obj == null;    }    /**     * String是否为null或""     */    public static boolean isEmpty(String obj) {        return (obj == null || "".equals(obj));    }        ...    /**     * Object是否不为null     */    public static boolean isNotEmpty(Object obj) {        return !isEmpty(obj);    }    /**     * String是否不为null或""     */    public static boolean isNotEmpty(String obj) {        return !isEmpty(obj);    }    ...}复制代码

ClassUtil主要是Class的一些相关操作。这其中除了一些类常用的实例反射等操作,还有一个重要方法就是getPackageClass(),这个方法会递归遍历传入的包名下的所有类文件,并返回一个Set<Class<?>>。等一下在实现Bean容器的时候就会使用这个方法来扫描获取对应包下的所有类文件。

/** * 类操作工具类 */@Slf4jpublic final class ClassUtil {    /**     * file形式url协议     */    public static final String FILE_PROTOCOL = "file";    /**     * jar形式url协议     */    public static final String JAR_PROTOCOL = "jar";    /**     * 获取classLoader     */    public static ClassLoader getClassLoader() {        return Thread.currentThread().getContextClassLoader();    }    /**     * 获取Class     */    public static Class
loadClass(String className) { try { return Class.forName(className); } catch (ClassNotFoundException e) { log.error("load class error", e); throw new RuntimeException(e); } } /** * 实例化class */ @SuppressWarnings("unchecked") public static
T newInstance(String className) { try { Class
clazz = loadClass(className); return (T) clazz.newInstance(); } catch (Exception e) { log.error("newInstance error", e); throw new RuntimeException(e); } } /** * 实例化class */ @SuppressWarnings("unchecked") public static
T newInstance(Class
clazz) { try { return (T) clazz.newInstance(); } catch (Exception e) { log.error("newInstance error", e); throw new RuntimeException(e); } } /** * 设置类的属性值 */ public static void setField(Field field, Object target, Object value) { setField(field, target, value, true); } /** * 设置类的属性值 */ public static void setField(Field field, Object target, Object value, boolean accessible) { field.setAccessible(accessible); try { field.set(target, value); } catch (IllegalAccessException e) { log.error("setField error", e); throw new RuntimeException(e); } } /** * 获取包下类集合 */ public static Set
> getPackageClass(String basePackage) { URL url = getClassLoader() .getResource(basePackage.replace(".", "/")); if (null == url) { throw new RuntimeException("无法获取项目路径文件"); } try { if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)) { // 若为普通文件夹,则遍历 File file = new File(url.getFile()); Path basePath = file.toPath(); return Files.walk(basePath) .filter(path -> path.toFile().getName().endsWith(".class")) .map(path -> getClassByPath(path, basePath, basePackage)) .collect(Collectors.toSet()); } else if (url.getProtocol().equalsIgnoreCase(JAR_PROTOCOL)) { // 若在 jar 包中,则解析 jar 包中的 entry JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); return jarURLConnection.getJarFile() .stream() .filter(jarEntry -> jarEntry.getName().endsWith(".class")) .map(ClassUtil::getClassByJar) .collect(Collectors.toSet()); } return Collections.emptySet(); } catch (IOException e) { log.error("load package error", e); throw new RuntimeException(e); } } /** * 从Path获取Class */ private static Class
getClassByPath(Path classPath, Path basePath, String basePackage) { String packageName = classPath.toString().replace(basePath.toString(), ""); String className = (basePackage + packageName) .replace("/", ".") .replace("\\", ".") .replace(".class", ""); return loadClass(className); } /** * 从jar包获取Class */ private static Class
getClassByJar(JarEntry jarEntry) { String jarEntryName = jarEntry.getName(); // 获取类名 String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", "."); return loadClass(className); }}复制代码

实现Bean容器

现在开始可以实现Bean容器了。

基础注解

在spring中我们总是用各种注解去标注我们的组件,如controller等。所以我们也要先写一些注解来标注一些必要的组件。在zbw.core包下再创建一个annotation包,然后再创建四个最基本的组件.

// Component注解,用于标记组件@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Component {}// Controller注解,用于标记Controller层的组件@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Controller {}// Repository注解,用于标记Dao层的组件@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Repository {}// Service注解,用于标记Service层的组件@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Service {}复制代码

这四个注解都是只能标注在类上的,他们实际上没有任何作用,只是用来标记这个类的,我们在后面的类集合中就可以很方便的获取和区分被这些注解标记的类。

BeanContainer

Bean容器实际上就是存放所有Bean的地方,即Class以及相关信息对应其实体的容器,为什么称之为'Bean'呢,因为在spring中,定义Class信息和实例的东西叫BeanDefinition。这是一个接口,他有一个模板类AbstractBeanDefinition,这里面就有一个beanClass变量存放Class类和propertyValues变量存放类属性,以及很多类相关参数和初始化之类的参数。大家可以去spring中看看,spring的所有都是依赖于这个Bean生成的,可以说这是spring的基石。

了解到这个以后接下来就可以开始编写Bean容器了,在zbw.core包下创建一个类叫BeanContainer

/** * Bean容器 */@Slf4jpublic class BeanContainer {    /**     * 存放所有Bean的Map     */    private final Map
, Object> beanMap = new ConcurrentHashMap<>(); /** * 获取Bean实例 */ public Object getBean(Class
clz) { if (null == clz) { return null; } return beanMap.get(clz); } /** * 获取所有Bean集合 */ public Set
getBeans() { return new HashSet<>(beanMap.values()); } /** * 添加一个Bean实例 */ public Object addBean(Class
clz, Object bean) { return beanMap.put(clz, bean); } /** * 移除一个Bean实例 */ public void removeBean(Class
clz) { beanMap.remove(clz); } /** * Bean实例数量 */ public int size() { return beanMap.size(); } /** * 所有Bean的Class集合 */ public Set
> getClasses() { return beanMap.keySet(); } /** * 通过注解获取Bean的Class集合 */ public Set
> getClassesByAnnotation(Class
annotation) { return beanMap.keySet() .stream() .filter(clz -> clz.isAnnotationPresent(annotation)) .collect(Collectors.toSet()); } /** * 通过实现类或者父类获取Bean的Class集合 */ public Set
> getClassesBySuper(Class
superClass) { return beanMap.keySet() .stream() .filter(superClass::isAssignableFrom) .filter(clz -> !clz.equals(superClass)) .collect(Collectors.toSet()); }}复制代码

我们不需要像spring那样存放很多的信息,所以用一个Map来存储Bean的信息就好了。Map的Key为Class类,Value为这个Class的实例Object。配合getBean(),addBean()等方法就可以很方便的操作Class和它的实例。

然而现在这个Map里还没有存放任何的Bean数据,所以编写一个loadBeans()方法来初始化加载Bean。

首先在BeanContainer中添加一个变量isLoadBean和一个常量BEAN_ANNOTATION

//BeanContainer.../*** 是否加载Bean*/private boolean isLoadBean = false;/*** 加载bean的注解列表*/private static final List
> BEAN_ANNOTATION = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class);...复制代码

然后编写loadBeans()方法去加载被BEAN_ANNOTATION中的注解类注解的类,以及对应的实例。通过刚才的ClassUtil.getPackageClass(basePackage)获取我们项目下所有的Class,然后判断该Class是否被BEAN_ANNOTATION中注解类注解,如果有就说明该Class是一个Bean,对其实例化并且放入Map中。

//BeanContainer.../*** 扫描加载所有Bean*/public void loadBeans(String basePackage) {    if (isLoadBean()) {        log.warn("bean已经加载");        return;    }    Set
> classSet = ClassUtil.getPackageClass(basePackage); classSet.stream() .filter(clz -> { for (Class
annotation : BEAN_ANNOTATION) { if (clz.isAnnotationPresent(annotation)) { return true; } } return false; }) .forEach(clz -> beanMap.put(clz, ClassUtil.newInstance(clz))); isLoadBean = true;}/*** 是否加载Bean*/public boolean isLoadBean() { return isLoadBean;}...复制代码

最后,为了能够保证整个项目全局Bean的唯一性,我们要保证这个BeanContainer是唯一的,将该类单例化。

通过lombok的注解@NoArgsConstructor(access = AccessLevel.PRIVATE)生成私有构造函数,再用内部枚举生成唯一的BeanContainer实例。

@Slf4j@NoArgsConstructor(access = AccessLevel.PRIVATE)public class BeanContainer {	/**     * 获取Bean容器实例     */    public static BeanContainer getInstance() {        return ContainerHolder.HOLDER.instance;    }        ...       private enum ContainerHolder {        HOLDER;        private BeanContainer instance;        ContainerHolder() {            instance = new BeanContainer();        }    }}复制代码

至此,这个Bean容器就完成了。我们可以通过loadBeans()方法初始化Bean,然后可以通过getBean(),addBean()removeBean()等方法去操作这个Bean,为后面的IOC,AOP等功能打下基础。


源码地址:

原文地址:

转载地址:http://eaicl.baihongyu.com/

你可能感兴趣的文章
[置顶] Mybatis技术(四) 从配置读取到打开连接的源码分析
查看>>
约定 大于 验证
查看>>
mysql杀掉睡眠进程
查看>>
迈向高薪的阶梯
查看>>
用GDB调试程序(四)
查看>>
专用链表VS通用链表
查看>>
完美解决delphi7 安装Ehlib4.1.4报错的办法
查看>>
Mac常用快捷键
查看>>
python打包成exe程序
查看>>
Q查询
查看>>
【问题求助】PS调色师要学的东西!
查看>>
AB ABB ABC “ABCDEFG”
查看>>
sleep(),wait()区别
查看>>
自动日志清理脚本程序
查看>>
Block Object
查看>>
[转载] C#——使用RichTextBox做的一个Notepad
查看>>
c语言练习
查看>>
Android下横竖屏切换的处理
查看>>
进击的JAVA(1)
查看>>
PHP整理笔记五目录与文件
查看>>