资讯专栏INFORMATION COLUMN

从零开始实现一个简易的Java MVC框架(二)--实现Bean容器

paulquei / 2873人阅读

摘要:容器实际上就是存放所有的地方,即以及相关信息对应其实体的容器,为什么称之为呢,因为在中,定义信息和实例的东西叫。了解到这个以后接下来就可以开始编写容器了,在包下创建一个类叫。获取容器实例至此,这个容器就完成了。

项目准备

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

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.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.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>。等一下在实现Bean容器的时候就会使用这个方法来扫描获取对应包下的所有类文件。

/**
 * 类操作工具类
 */
@Slf4j
public 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容器
 */
@Slf4j
public 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等功能打下基础。

从零开始实现一个简易的Java MVC框架(一)--前言

从零开始实现一个简易的Java MVC框架(二)--实现Bean容器

从零开始实现一个简易的Java MVC框架(三)--实现IOC

从零开始实现一个简易的Java MVC框架(四)--实现AOP

从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点

从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

从零开始实现一个简易的Java MVC框架(七)--实现MVC

从零开始实现一个简易的Java MVC框架(八)--制作Starter

从零开始实现一个简易的Java MVC框架(九)--优化MVC代码

源码地址:doodle

原文地址:从零开始实现一个简易的Java MVC框架--实现Bean容器

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71459.html

相关文章

  • 从零开始实现一个简易Java MVC框架

    摘要:不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的框架。原文地址从零开始实现一个简易的框架 前言 最近在看spring-boot框架的源码,看了源码之后更是让我感受到了spring-boot功能的强大。而且使用了很多的设计模式,让人在看的时候觉得有点难以下手。 不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的...

    neuSnail 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(五)--引入aspectj实现AOP切点

    摘要:接下来就可以把这个切点类加入到我们之前实现的功能中了。实现的切点功能首先改装注解,把之前改成来存储表达式。测试用例在上一篇文章从零开始实现一个简易的框架四实现中的测试用例的基础上修改测试用例。 前言 在上一节从零开始实现一个简易的Java MVC框架(四)--实现AOP中我们实现了AOP的功能,已经可以生成对应的代理类了,但是对于代理对象的选择只能通过指定的类,这样确实不方便也不合理。...

    wupengyu 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(八)--制作Starter

    摘要:服务器相关配置启动类资源目录目录静态文件目录端口号目录目录实现内嵌服务器在上一章文章从零开始实现一个简易的框架七实现已经在文件中引入了依赖,所以这里就不用引用了。 spring-boot的Starter 一个项目总是要有一个启动的地方,当项目部署在tomcat中的时候,经常就会用tomcat的startup.sh(startup.bat)的启动脚本来启动web项目 而在spring-b...

    AprilJ 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(六)--加强AOP功能

    摘要:在前面的文章中实现的功能时,目标类都只能被一个切面代理,如果想要生成第二个代理类,就会把之前的代理类覆盖。改装原有功能现在要改装原来的的实现代码,让的功能加入到框架中为了让切面能够排序,先添加一个注解,用于标记排序。 前言 在前面从零开始实现一个简易的Java MVC框架(四)--实现AOP和从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点这两节文章...

    Loong_T 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(九)--优化MVC代码

    摘要:前言在从零开始实现一个简易的框架七实现中实现了框架的的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 前言 在从零开始实现一个简易的Java MVC框架(七)--实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 优化的目标是1.去除DispatcherServlet请求分发器中的http逻...

    ruicbAndroid 评论0 收藏0

发表评论

0条评论

paulquei

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<