zoukankan      html  css  js  c++  java
  • 架构探险笔记3-搭建轻量级Java web框架

    MVC(Model-View-Controller,模型-视图-控制器)是一种常见的设计模式,可以使用这个模式将应用程序进行解耦。

    上一章我们使用Servlet来充当MVC模式中的Controller。这样做虽然可以实现基本功能,但Servlet的数量会随着业务功能的拓展而不断增加。因此有必要减少Servlet的数量,将某类业务交给Controller来处理,它负责调用Service的相关方法,并将返回值放入Request或Response中。此外,Service不是通过new 的方式来创建的,而是通过一种名为“依赖注入”的方式,让框架为我们来创建所需要的对象。

    目的:

    1.快速搭建开发框架

    2.加载并读取配置文件

    3.实现一个简单的IOC的容器

    4.加载指定的类

    5.初始化框架

    确定目标

    打造一个轻量级MVC框架,而Controller是MVC的核心。我们想要的是如下代码:

    @Controller
    public class CustomerController {
    
        @Inject
        private CustomerService customerService;
    
        /**
         * 进入客户端界面
         */
        @Action("get:/customer")
        public View index(){
            List<Customer> customerList = customerService.getCustomerList();
            return new View("customer.jsp").addModel("customerList",customerList);
        }
    
        /**
         * 显示客户基本信息
         */
        @Action("get:/customer_show")
        public View show(Param param){
            long id = param.getLong(id);
            Customer customer = customerService.getCustomer(id);
            return new View("customer_show.jsp").addModel("customer",customer);
        }
    
        /**
         * 删除客户信息
         * @param param
         * @return
         */
        @Action("delete:/customer_edit")
        public Data delete(Param param){
            long id = param.getLong(id);
            boolean result = customerService.deleteCustomer(id);
            return new Data(result);
        }
    }

    通过Controller注解来定义Controller类,在该类中,可通过Inject注解定义一系列Service成员变量,这就是“依赖注入”。此外,有一系列被Action注解所定义的方法(简称Action方法),在这些Action方法中,调用了Service成员变量的方法来完成具体的业务和逻辑。若返回View对象,则表示JSP页面;若返回Data对象,则表示一个JSON数据。

    Controller代码非常清晰,一个Controller类包含了多个Action方法,可返回View或Data对象,分别对应JSP页面或JSON数据。

    提示:在普通请求的情况下,可返回JSP页面;在Ajax请求的情况下,需要返回JSON数据。

    创建框架项目

    创建一个名为smart-framework的项目,它是一个普通的java项目,在pom.xml中需要添加Maven三坐标:

        <groupId>org.smart4j</groupId>
        <artifactId>smart-framework</artifactId>
        <version>1.0.0</version>

    因为该框架是Java Web框架,所以一定会依赖Servlet、JSP、JSTL。

            <!--servlet-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>
            <!--jsp-->
            <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>jsp-api</artifactId>
                <version>2.2</version>
                <scope>provided</scope>
            </dependency>
            <!--jstl-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
                <scope>runtime</scope>
            </dependency>

    在框架中会大量使用日志输出,最流行的日志框架就是Log4j了,但它只是日志的一种具体实现,如果将来需要使用其他更好的日志框架,那么代码中所有日志输出的地方都要修改。为了解决这个问题,我们使用一个名为SLF4J的日志框架,它实际上是日志框架的接口,而Log4J只是日志框架的一种实现而已。只需要添加以下依赖,就能同时引入SLF4J和Log4J两个依赖。

            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
                <version>1.7.25</version>
            </dependency>

    mysql驱动

            <!--mysql-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.33</version>
            </dependency>

    由于在Controller的Action方法返回值中是可以返回json数据的,因此需要选择一款JSON序列化工具,目前在功能、性能、稳定性各方面表现好的JSON序列化工具就是Jackson了。

            <!--Jackson-->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.4.4</version>
            </dependency>

    常用的Apache Commons工具类

            <!--常用的两个Apache Commons工具类-->
            <!--Apache Commons Lang-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-lang3</artifactId>
                <version>3.3.2</version>
            </dependency>
            <!--Apache Commons Collections-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-collections4</artifactId>
                <version>4.0</version>
            </dependency>

    对于JDBC,我们选择了轻量级的DbUtils,它也是Apache Commons的项目之一。

            <!--Apache Commons DbUtils-->
            <dependency>
                <groupId>commons-dbutils</groupId>
                <artifactId>commons-dbutils</artifactId>
                <version>1.6</version>
            </dependency>

    在框架中需要用到数据库连接池,我们选择了总和能力强的连接池框架DBCP,同样是Apache Commons项目之一。

            <!--Apache DBCP连接池-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-dbcp2</artifactId>
                <version>2.0.1</version>
            </dependency>

    除了smart-framework这个项目外,我们有必要再创建一个使用该框架的项目,名为chapter3,chapter3是一个java web项目,只需要依赖于smart-framework即可,详细的pom.xml如下

    <!--Smart Framework-->
    <dependency>
        <groupId>org.smart4j</groupId>
        <artificatId>smart-framework</artifactId>
        <version>1.0.0</version>
    </dependency>

    定义框架配置项

    在chapter3项目的src/main/resources目录下,创建一个名为smart.properties的文件,内容如下

    smart.framework.jdbc.driver=com.mysql.jdbc.Driver
    smart.framework.jdbc.url=jdbc:mysql://localhost:3306/demo
    smart.framework.jdbc.username=root
    smart.framework.jdbc.password=root
    
    
    smart.framework.app.base_package=org.smart4j.chapter3    --扫描包
    smart.framework.app.jsp_path=/WEB-INF/view/   --视图所在路径
    smart.framework.app.asset_path=/asset/    --静态资源

    在chapter3中添加引用

            <dependency>
                <groupId>org.smart4j</groupId>
                <artifactId>smart-framework</artifactId>
                <version>1.0.0</version>
            </dependency>

    加载配置项

    常用工具类

    配置文件已经有了,下面要根据配置项的名称获取配置项的取值,只是框架需要做的事。我们在smart-framework项目中创建一个名为ConfigHelper的助手类,让它来读取smart.properties配置文件。

    首先,创建一个名为ConfigConstant的常量类,用来维护配置文件中相关的配置项名称。

    package org.smart4j.framework.helper;
    
    /**
     * @program: ConfigConstant
     * @description: 相變量配置
     **/
    public interface ConfigConstant {
        String CONFIG_FILE = "smart.properties";
        String JDBC_DRIVER = "smart.framework.jdbc.driver";
        String JDBC_URL = "smart.framework.jdbc.url";
        String JDBC_USERNAME = "smart.framework.jdbc.username";
        String JDBC_PASSWORD = "smart.framework.jdbc.password";
    
        String APP_BASE_PACKAGE = "smart.framework.app.base_package";
        String APP_JSP_PATH = "smart.framework.app.jsp_path";
        String APP_ASSET_PATH = "smart.framework.app.asset_path";
    }

    然后借助PropsUtil工具类实现ConfigHelper类,此类中是一些静态方法,分别获取smart.properties配置文件中的配置项。

    package org.smart4j.framework.helper;
    
    import org.smart4j.framework.PropsUtil;
    import java.util.Properties;
    
    /**
     * @program: ConfigHelper
     * @description: 属性文件助手类
     **/
    public class ConfigHelper {
    
        private static final Properties CONFIG_PROPS = PropsUtil.loadProps(ConfigConstant.CONFIG_FILE);
    
        /**
         * 獲取JDBC驅動
         * @return
         */
        public static String getJdbcDriver(){
            return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_DRIVER);
        }
    
        /**
         * 獲取JDBC URL
         * @return
         */
        public static String getJdbcUrl(){
            return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_URL);
        }
    
        /**
         * 獲取JDBC 用戶名
         * @return
         */
        public static String getJdbcUsername(){
            return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_USERNAME);
        }
    
        /**
         * 獲取JDBC 密碼
         * @return
         */
        public static String getJdbcPassword(){
            return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_PASSWORD);
        }
    
        /**
         * 獲取應用基礎包名
         * @return
         */
        public static String getAppBasePackage(){
            return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_BASE_PACKAGE);
        }
    
        /**
         * 獲取應用JSP路徑
         * @return
         */
        public static String getAppJspPath(){
            return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_JSP_PATH,"/WEB-INF/view/");
        }
    
        /**
         * 獲取應用靜態資源路徑
         * @return
         */
        public static String getAppAssetPath(){
            return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_ASSET_PATH,"/asset/");
        }
    
    }

    在ConfigHelper类中,为smart.framework.app.jsp_path与smart.framework.app.asset_path配置项提供了默认值。也就是说smart.properties配置文件中这两个配置项是可选的,如果不是特殊要求,可以修改这里两个配置。换句话说,这两个配置会以smart.properties配置文件中所配置的值为优先值。

    开发一个类加载器

    开发一个类加载器用来加载该基础包名下的所有类,例如:使用了注解的类、实现了某接口的类、继承了某父类的所有子类等。

    写一个ClassUtil工具类,提供与类操作相关的方法,比如获取类加载器、加载类、获取指定包名下的所有类。

    package org.smart4j.framework.util;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.smart4j.framework.StringUtil;
    
    import java.io.File;
    import java.io.FileFilter;
    import java.io.IOException;
    import java.net.JarURLConnection;
    import java.net.URL;
    import java.util.Enumeration;
    import java.util.HashSet;
    import java.util.Set;
    import java.util.jar.JarEntry;
    import java.util.jar.JarFile;
    
    /**
     * @program: ClassUtil
     * @description: 類加載器
     * @author: qiuyu
     * @create: 2018-09-11 19:00
     **/
    public class ClassUtil {
        private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);
    
        /**
         * 獲取類加載器
         * 給下面的加載類用
         * @return
         */
        public static ClassLoader getClassLoader(){
            return Thread.currentThread().getContextClassLoader();
        }
    
        /**
         * 加載類
         * 将类加载到方法区中
         * @param className 类名 packageName.className
         * @param isInitialized 是否初始化静态代码块和静态字段
         * @return
         */
        public static Class<?> loadClass(String className,boolean isInitialized){
            Class<?> cls;
            try {
                //className為類全名,isInitialized為是否初始化靜態代碼塊和靜態字段
                cls = Class.forName(className,isInitialized,getClassLoader());
            } catch (ClassNotFoundException e) {
                LOGGER.error("load class failure",e);
                throw new RuntimeException(e);
                // e.printStackTrace();
            }
            return cls;
        }
    
        /**
         * 獲取指定包名下的所有類文件/文件夹和jar包
         * @param packageName
         * @return
         */
        public static Set<Class<?>> getClassSet(String packageName){
            Set<Class<?>> classSet = new HashSet<Class<?>>();
            try {
                //获取资源的url枚举
                Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
                while(urls.hasMoreElements()){    //如果存在该包
                    URL url = urls.nextElement();  //获取包的url
                    if (url!=null){
                        String protocol = url.getProtocol();  //查看url的协议(file、http)
                        if (protocol.equals("file")){ //如果是文件或者文件夹
                            String packagePath = url.getPath().replaceAll("%20"," "); //空格的转义字符替换为空格,这个是java一个历史悠久的bug
                            addClass(classSet,packagePath,packageName);
                        }else if (protocol.equals("jar")){  //如果是jar包
                            JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();  //根据url获取jar的Connection
                            if (jarURLConnection!=null){
                                JarFile jarFile = jarURLConnection.getJarFile();   //根据connection获取jar文件
                                if (jarFile!=null){
                                    Enumeration<JarEntry> jarEntries = jarFile.entries();  //获取jar文件中的实体枚举
                                    while (jarEntries.hasMoreElements()){  //遍历枚举
                                        JarEntry jarEntry = jarEntries.nextElement();
                                        String jarEntryName = jarEntry.getName();
                                        if (jarEntryName.endsWith(".class")){
                                            String className = jarEntryName.substring(0,jarEntryName.lastIndexOf(".")).replaceAll("/",".");
                                            doAddClass(classSet,className);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return classSet;
        }
    
        /**
         * 加载指定包下面的jar和class文件
         * @param classSet 存class的集合
         * @param packagePath 包的真实路径  D:***
         * @param packageName 包名  *.*.*
         */
        private static void addClass(Set<Class<?>> classSet,String packagePath,String packageName){
            //System.out.println(packageName);
            //System.out.println(packagePath);
            //获得包下面的所有文件(.class和文件夹)
            File[] files = new File(packagePath).listFiles(new FileFilter() {
                //文件过滤器,只获取.class结尾的文件和文件将夹
                public boolean accept(File file) {
                    return (file.isFile()&&file.getName().endsWith(".class"))||file.isDirectory();
                }
            });
            for (File file:files){
                String fileName = file.getName();   //获取文件名
                if (file.isFile()){  //如果是文件
                    String className = fileName.substring(0,fileName.lastIndexOf('.'));
                    if (StringUtil.isNotEmpty(packageName)){
                        className = packageName+"."+className;  //根据传进来的包名packageName和获取的.class文件名拼接成packageName.className
                        doAddClass(classSet,className);
                    }
                }else {  //如果是目录
                    String subPackagePath = fileName; //子文件夹名
                    if (StringUtil.isNotEmpty(packagePath)){
                        subPackagePath=packagePath+"/"+subPackagePath;  //子目录路径
                    }
    
                    String subPackageName = fileName;
                    if (StringUtil.isNotEmpty(subPackageName)){
                        subPackageName=packageName+"."+subPackageName;  //子包名
                    }
                    addClass(classSet,subPackagePath,subPackageName);  // 将子包和子包路径传进去
                }
            }
        }
    
        /**
         * 将类加载到方法区,并放入Set集合中
         * @param classSet 存被加载类的集合
         * @param className 类的全名 packageName.className
         */
        private static void doAddClass(Set<Class<?>> classSet,String className){
            Class<?> cls = loadClass(className,false);
            classSet.add(cls);
        }
    
        public static void main(String[] args) {
            //loadClass("org.smart4j.framework.util.Cls",false);
            //Cls cls = new Cls();
            //加載環境中的jar包中的報名
            Set<Class<?>> classSet = getClassSet("org.smart4j.framework.util");
            System.out.println(classSet);
        }
    }

    注解

    为了实现在控制器上使用Controller注解,在控制器类的方法上使用Action注解,在服务类上使用Service注解,在控制器类中使用Inject注解将服务类依赖注入进来。因此需要四个注解类。

    控制器注解代码

    package org.smart4j.framework.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 控制器注解
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Controller {
    
    }

    Action方法注解代码

    /**
     * 控制器注解
     * Action方法注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Action {
        /**
         * 请求类型与路径
         */
        String value();
    }

    服务类注解代码

    /**
     * 服务类注解
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Service {
    }

    依赖注入注解代码

    /**
     * 依赖注入注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Inject {
    }

    在smart.properties配置文件中指定了smart.framework.app.base_package,它是整个应用的基础包名,通过ClassUtil加载的类都需要基于该基础包名。所以有必要提供一个ClassHelper助手类,让它分别获取应用包名下的所有类、应用宝名下所有Service类、应用包名下所有Controller类。此外,我们可以将带有Controller注解与Service注解的类所产生的对象,理解为由Smart框架所管理的Bean,所以有必要在ClassHelper类中增加一个获取应用包名下所有Bean类的方法。ClassHelper代码

    /**
     * @program: ClassHelper
     * @description: 类操作助手
     * 获取所有Controller和Service类的集合
     **/
    public class ClassHelper {
    
        /**
         * 定义类集合
         */
        private static final Set<Class<?>> CLASS_SET;
    
        static {
            String basePackage = ConfigHelper.getAppBasePackage();
            CLASS_SET = ClassUtil.getClassSet(basePackage);
        }
    
        /**
         * 获取应用包名下的所有类
         * @return
         */
        public static Set<Class<?>> getClassSet(){
            return CLASS_SET;
        }
    
        /**
         * 获取所有Controller类
         * @return
         */
        public static Set<Class<?>> getControllerClassSet(){
            Set<Class<?>> classSet = new HashSet<Class<?>>();
            for (Class<?> cls : CLASS_SET){
                if (cls.isAnnotationPresent(Controller.class)){
                    classSet.add(cls);
                }
            }
            return classSet;
        }
    
        /**
         * 获取所有Service类
         * @return
         */
        public static Set<Class<?>> getServiceClassSet(){
            Set<Class<?>> classSet = new HashSet<Class<?>>();
            for (Class<?> cls : CLASS_SET){
                if (cls.isAnnotationPresent(Service.class)){
                    classSet.add(cls);
                }
            }
            return classSet;
        }
    
        /**
         * 获取应用包名下的所有bean类(Controller和Service)
         * @return
         */
        public static Set<Class<?>> getBeanClassSet(){
            Set<Class<?>> beanClassSet = new HashSet<Class<?>>();
            beanClassSet.addAll(getControllerClassSet());
            beanClassSet.addAll(getServiceClassSet());
            return beanClassSet;
        }
    }

    实现Bean容器

    使用ClassHelper类可以获取所加载的类,但是无法通过类来实例化对象。因此,需要提供一个反射工具类,让它封装Java反射相关的API,对外提供更好用的工具方法。命名为ReflectionUtil。

    package org.smart4j.framework.util;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    /**
     * 反射工具
     * 实现bean容器
     */
    public class ReflectionUtil {
        private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);
    
        /**
         * 创建实例
         * @param cls 已经加载的类
         * @return
         */
        public static Object newInstance(Class<?> cls){
            Object instance = null;
            try {
                instance = cls.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
                LOGGER.error("new instance failure",e);
                throw new RuntimeException(e);
            }
            return instance;
        }
    
        /**
         * 调用方法
         * @param obj 调用方法的实例对象
         * @param method 方法
         * @param args 方法参数
         * @return
         */
        public static Object invokeMethod(Object obj, Method method,Object... args){
            Object result = null;
            try {
                method.setAccessible(true);
                result = method.invoke(obj,args);
            } catch (Exception e) {
                //e.printStackTrace();
                LOGGER.error("invoke method failure",e);
                throw new RuntimeException(e);
            }
            return result;
        }
    
        /**
         * 设置成员变量的值
         * @param obj 实例对象
         * @param field 字段
         * @param value 字段值
         */
        public static void setField(Object obj, Field field,Object value){
            try {
                field.setAccessible(true);
                field.set(obj,value);
            } catch (IllegalAccessException e) {
                //e.printStackTrace();
                LOGGER.error("set field failure",e);
                throw new RuntimeException(e);
            }
        }
    }

    要获取所有被框架管理的Bean类,需要调用ClassHelper类的getBeanClassSet方法,然后循环调用ReflectionUtil类的newInstance方法,根据类来实例化对象,然后将每次创建的对象放在一个静态的Map<Class<?>,Object>中。我们需要随时获取该Map,还需要通过该Map的Key(类名)去获取所对应的value(Bean)对象。BeanHelper类代码如下:

    package org.smart4j.framework.helper;
    
    import org.smart4j.framework.util.ReflectionUtil;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    /**
     * Bean助手类
     *
     */
    public class BeanHelper {
        /**
         * 定义bean映射(用于存放Bean类与Bean实例的映射关系)
         */
        private static final Map<Class<?>,Object> BEAN_MAP = new HashMap<Class<?>, Object>();
    
        static{
            //获取所有Controller和Service
            Set<Class<?>> beanClassSet = ClassHelper.getBeanClassSet();
            //遍历所有的Controlller和Service类
            for (Class<?> beanClass:beanClassSet){
                Object obj = ReflectionUtil.newInstance(beanClass);  //将类实例化
                BEAN_MAP.put(beanClass,obj); //将类和实例放入Map中  Map<Class,Obj>
            }
        }
    
        /**
         * 获取Bean映射
         * @return
         */
        public static Map<Class<?>, Object> getBeanMap() {
            return BEAN_MAP;
        }
    
        /**
         * 根据Class获取bean实例
         * @param cls bean实例所属的类
         * @param <T> 类的实例对象
         * @return
         */
        public static <T> T getBean(Class<T> cls){
            if (!BEAN_MAP.containsKey(cls)){
                throw new RuntimeException("can not get bean by class"+cls);
            }
            return (T) BEAN_MAP.get(cls);
        }
    }

    BeanHelper就相当于一个“Bean容器”了,因为在Bean Map中存放了Bean类与Bean实例的映射关系,我们只需通过调用getBean方法,传入一个Bean类,就能获取Bean实例。

    实现依赖注入

    Controller中定义的Service成员变量,在Controller的Action方法中调用Service成员变量的方法。这里Service并没有通过new的方式实例化,而是通过框架自身来实例化,像这类实例化过程,称为IOC(Inversion of Control,控制反转)。控制不是由开发者来决定的,而是反转给框架了。一般地,我们也将反转称为DI(Dependency Injection,依赖注入),可以理解为将某个类需要依赖的成员注入到这个类中。那么,如何来实现依赖注入呢?

    最简单的方式是,先通过BeanHelper获取所有BeanMap(是一个Map<Class<?>>,Object结构,记录了类与对象的映射关系)。然后遍历这个映射关系,分别取出Bean类与Bean实例,进而通过反射获取类中所有的成员变量。继续遍历这些成员变量,在循环中判断当前成员变量是否带有Inject注解,若带有该注解,则从Bean Map 中根据Bean实例。最后通过ReflectionUtil#setField方法来修改当前成员变量的值。

    package org.smart4j.framework.helper;
    
    import org.smart4j.framework.ArrayUtil;
    import org.smart4j.framework.annotation.Inject;
    import org.smart4j.framework.util.ReflectionUtil;
    
    import java.lang.reflect.Field;
    import java.util.Map;
    
    /**
     * 依赖注入助手类
     */
    public class IocHelper {
        static {
            //获取所有bean类与Bean实例类之间的关系的集合(简称BeanMap)
            Map<Class<?>,Object> beanMap = BeanHelper.getBeanMap();
            if (CollectionUtil.isNotEmpty(beanMap)){
                //遍历beanMap
                for (Map.Entry<Class<?>,Object> beanEntry:beanMap.entrySet()){
                    //从Bean中获取Bean类与Bean实例
                    Class<?> beanClass = beanEntry.getKey();
                    Object beanInstance = beanEntry.getValue();
                    //获取bean类定义的所有成员变量(简称Bean Field)
                    Field[] beanFields = beanClass.getDeclaredFields();
                    if (ArrayUtil.isNotEmpty(beanFields)){
                        //遍历Bean Field
                        for (Field beanField:beanFields){
                            //判断是否带有Inject注解
                            if (beanField.isAnnotationPresent(Inject.class)){
                                //在Bean Map中获取Bean Field对应的实例
                                Class<?> beanFieldClass = beanField.getType();
                                Object beanFieldInstance = beanMap.get(beanFieldClass);
                                if (beanFieldInstance!=null){
                                    //通过反射初始化BeanField的值
                                    ReflectionUtil.setField(beanInstance,beanField,beanFieldInstance);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    只需要在IocHelper的静态代码块中实现相关逻辑,就能完成IOC容器的初始化工作。在IOCHelper这个类被加载的时候,就会加载它的静态块。后面我们需要找一个统一的地方来加载这个IocHelper。其中涉及了ArrayUtil类,代码如下

    package org.smart4j.framework;
    
    import org.apache.commons.lang3.ArrayUtils;
    
    /**
     * 数组工具类
     */
    public class ArrayUtil {
        /**
         * 判断数组是否为空
         * @param array
         * @return
         */
        public static boolean isNotEmpty(Object[] array){
            return !isEmpty(array);
        }
    
        /**
         * 判断数组是否非空
         * @param array
         * @return
         */
        public static boolean isEmpty(Object[] array){
            return array==null||array.length==0;
        }
    }

    需要注意的是,此时IOC框架中所管理的对象都是单例的,由于IOC框架底层还是从BeanHelper中获取的Bean Map的,而Bean Map中的对象都是事先创建好并放入这个Bean容器中的,所有的对象都是单例的。

    加载Controller 

    我们需要创建一个ControllerHelper类,让它来处理如下逻辑:

    通过ClassHelper,我们可以获取所有定义了Controller注解的类,可以通过反射获取该类中所有带有Action注解的方法(简称“Action方法”),获取Action注解中的请求表达式,进而获取请求方法与请求路径,封装一个请求对象(Request)与处理对象(Handler),最后将Request与Handler建立一个映射关系,放入一个Action Map中,并提供一个可根据请求方法与请求路径获取处理对象的方法。

    Request类

    package org.smart4j.framework.bean;
    
    import org.apache.commons.lang3.builder.EqualsBuilder;
    import org.apache.commons.lang3.builder.HashCodeBuilder;
    
    /**
     * 封装请求信息
     */
    public class Request {
    
        /**
         * 请求方法
         */
        private String requestMethod;
    
        /**
         * 请求路径
         */
        private String requestPath;
    
        public Request(String requestMethod,String requestPath){
            this.requestMethod = requestMethod;
            this.requestPath = requestPath;
        }
    
        public String getRequestMethod() {
            return requestMethod;
        }
    
        public void setRequestMethod(String requestMethod) {
            this.requestMethod = requestMethod;
        }
    
        public String getRequestPath() {
            return requestPath;
        }
    
        public void setRequestPath(String requestPath) {
            this.requestPath = requestPath;
        }
    
        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this);
        }
    
        @Override
        public boolean equals(Object obj) {
            return EqualsBuilder.reflectionEquals(this,obj);
        }
    }

    Handler类

    package org.smart4j.framework.bean;
    
    import java.lang.reflect.Method;
    
    /**
     * @program: Handler
     * @description: 封装Action信息
     **/
    public class Handler {
        /**
         * Controller类
         */
        private Class<?> controllerClass;
    
        /**
         * Action方法
         */
        private Method actionMethod;
    
        public Handler(Class<?> controllerClass, Method actionMethod) {
            this.controllerClass = controllerClass;
            this.actionMethod = actionMethod;
        }
    
        public Class<?> getControllerClass() {
            return controllerClass;
        }
    
        public Method getActionMethod() {
            return actionMethod;
        }
    }

    ControllerHelper类

    package org.smart4j.framework.helper;
    
    import org.smart4j.framework.ArrayUtil;
    import org.smart4j.framework.annotation.Action;
    import org.smart4j.framework.bean.Handler;
    import org.smart4j.framework.bean.Request;
    
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    
    /**
     * @program: ControllerHelper
     * @description: 控制器助手类
     * @author: QiuYu
     * @create: 2018-09-27 15:34
     **/
    public final class ControllerHelper {
        /**
         * 用于存放请求与处理器的映射关系(简称Action Map)
         */
        private static final Map<Request,Handler> ACTION_MAP = new HashMap<Request, Handler>();
    
        static{
            //获取所有Controller类
            Set<Class<?>> controllerClassSet = ClassHelper.getControllerClassSet();
            if (org.smart4j.framework.helper.CollectionUtil.isNotEmpty(controllerClassSet)){
                //遍历这些Controller类
                for (Class<?> controllerClass : controllerClassSet){
                    //获取Controller类中定义的方法
                    Method[] methods = controllerClass.getDeclaredMethods();
                    if (ArrayUtil.isNotEmpty(methods)){
                        //遍历这些Controller类中的方法
                        for (Method method:methods){
                            //判断当前方法是否带有Action注解
                            if (method.isAnnotationPresent(Action.class)){
                                //从Action注解中获取URL映射规则
                                Action action = method.getAnnotation(Action.class);
                                String mapping = action.value();
                                //验证URL映射规则
                                if (mapping.matches("\w+:/\w*")){
                                    String[] array =mapping.split(":");
                                    if (ArrayUtil.isNotEmpty(array)&&array.length==2){
                                        //获取请求方法与路径
                                        String requestMethod = array[0];
                                        String requestPath = array[1];
                                        Request request = new Request(requestMethod,requestPath);
                                        Handler handler = new Handler(controllerClass,method);
                                        //初始化Action Map
                                        ACTION_MAP.put(request,handler);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    
        /**
         * 获取Handler
         * @param requestMethod
         * @param requestPath
         * @return
         */
        public static Handler getHandler(String requestMethod,String requestPath){
            Request request = new Request(requestMethod,requestPath);
            return ACTION_MAP.get(request);
        }
    }

    在ControllerHelper中封装了一个ActionMap,通过它来存放Request和Handler之间的映射关系,然后通过ClassHelper来获取所有带有Controller注解的类,接着遍历这些Controller类,从Action注解中提取URL,最后初始化Request与Handler之间的映射关系。

    初始化框架

    通过上面的过程,我们创建了ClassHelper、BeanHelper、IOCHelper、ControllerHelper,这四个Helper类需要通过一个入口程序来加载他们。

    package org.smart4j.framework;
    
    import org.smart4j.framework.helper.BeanHelper;
    import org.smart4j.framework.helper.ClassHelper;
    import org.smart4j.framework.helper.ControllerHelper;
    import org.smart4j.framework.helper.IocHelper;
    import org.smart4j.framework.util.ClassUtil;
    
    /**
     * @program: HelperLoader
     * @description: 加载相应的Helper类
     **/
    public class HelperLoader {
        public static void init(){
            Class<?>[] classList={
                    ClassHelper.class,
                    BeanHelper.class,
                    IocHelper.class,
                    ControllerHelper.class
            };
    
            for (Class<?> cls:classList){
                ClassUtil.loadClass(cls.getName(),true);
            }
        }
    }

    当我们第一次访问类时,就会加载其static块。这里只是为了让它们加载更集中,所以才写了一个HelperLoader类。

    请求转发器

    以上所有过程都为这一步做准备,现在需要实现一个Servlet,让它来处理所有的请求。从HttpServletRequest对象中获取请求方法与请求路径,通过ControllerHelper#getHandler方法来获取。

    当拿到Handler对象后,我们可以获取Controller类,进而通过BeanHelper.个体Bean方法获取Controller的实例对象。

    随后可以从HttpServletRequest对象中获取所有请求参数,并将其初始化到一个名为Param的对象中

    Param参数类代码:

    package org.smart4j.framework.bean;
    
    import org.smart4j.framework.CastUtil;
    
    import java.util.Map;
    
    /**
     * @program: Param
     * @description: 请求参数对象
     * @author: Created by Autumn
     * @create: 2018-10-24 10:47
     */
    
    public class Param {
        private Map<String,Object> paramMap;
    
        public Param(Map<String, Object> paramMap) {
            this.paramMap = paramMap;
        }
    
        /**
         * 根据参数名获取long型参数值
         * @param name
         * @return
         */
        public long getLong(String name){
            return CastUtil.castLong(paramMap.get(name));
        }
    
        /**
         * 获取所有字段信息
         * @return
         */
        public Map<String,Object> getMap(){
            return paramMap;
        }
    }

    Param可以通过参数名获取指定类型的参数值,也可以获取所有参数的Map结构。

    还可从Handler对象中获取Action的方法返回值,该返回值可能有两种情况。

    (1)若返回值是View类型的视图对象,则返回一个JSP页面。

    (2)若返回值是Data类型的数据对象,则返回一个JSON数据。

    View类

    package org.smart4j.framework.bean;
    
    import java.util.Map;
    
    /**
     * @program: View
     * @description: 视图对象
     */
    
    public class View {
        /**
         * 视图路径
         */
        private String path;
    
        /**
         * 模型数据
         */
        private Map<String,Object> model = new HashMap<String,Object>();
    
        public View(String path, Map<String, Object> model) {
            this.path = path;
            this.model = model;
        }
    
        public View addModel(String key,Object value){
            model.put(key,value);
            return this;
        }
    
        public String getPath() {
            return path;
        }
    
        public Map<String, Object> getModel() {
            return model;
        }
    }

    Data类

    package org.smart4j.framework.bean;
    
    /**
     * @program: Data
     * @description: 返回数据对象
     */
    
    public class Data {
        /**
         * 模型数据
         */
        private Object model;
    
        public Data(Object model) {
            this.model = model;
        }
    
        public Object getModel() {
            return model;
        }
    }

    返回的Data类型的数据封装了一个Object类型的模型数据,框架会将该对象写入HttpServletResponse对象中,从而直接输出至浏览器。

    MVC框架中核心的DispatcherServlet类

    package org.smart4j.framework;
    
    import org.smart4j.framework.bean.Data;
    import org.smart4j.framework.bean.Handler;
    import org.smart4j.framework.bean.Param;
    import org.smart4j.framework.bean.View;
    import org.smart4j.framework.helper.BeanHelper;
    import org.smart4j.framework.helper.ConfigHelper;
    import org.smart4j.framework.helper.ControllerHelper;
    import org.smart4j.framework.util.CodecUtil;
    import org.smart4j.framework.util.JsonUtil;
    import org.smart4j.framework.util.ReflectionUtil;
    import org.smart4j.framework.util.StreamUtil;
    
    import javax.servlet.*;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.lang.reflect.Method;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @program: DispatcherServlet
     * @description: 请求转发器
     * @author: Created by Autumn
     * @create: 2018-10-24 11:34
     */
    
    @WebServlet(urlPatterns = "/*",loadOnStartup = 0)
    public class DispatcherServlet extends HttpServlet {
    
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {
            //初始化相关Helper类
            HelperLoader.init();
            //获取ServletContext对象(用于注册Servlet)
            ServletContext servletContext = servletConfig.getServletContext();
            //注册处理JSP的Servlet
            ServletRegistration jspServlet = servletContext.getServletRegistration("jsp");
            jspServlet.addMapping(ConfigHelper.getAppJspPath()+"*");
            //注册处理静态资源的默认Servlet
            ServletRegistration defaultServlet = servletContext.getServletRegistration("default");
            defaultServlet.addMapping(ConfigHelper.getAppAssetPath()+"*");
        }
    
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            //获取请求方法与请求路径
            String requestMethod = req.getMethod().toLowerCase();
            String requestPath = req.getPathInfo();
            //获取Action处理器
            Handler handler= ControllerHelper.getHandler(requestMethod,requestPath);
            if(handler!=null){
                //获取Controller类机器Bean实例
                Class<?> controllerClass = handler.getControllerClass();
                Object controllerBean = BeanHelper.getBean(controllerClass);
                //创建请求参数对象
                Map<String,Object> paramMap = new HashMap<String, Object>();
                Enumeration<String> paramNames = req.getParameterNames();
                while(paramNames.hasMoreElements()){
                    String paramName = paramNames.nextElement();
                    String paramValue = req.getParameter(paramName);
                    paramMap.put(paramName,paramValue);
                }
                //获取请求body中的参数
                String body = CodecUtil.dencodeURL(StreamUtil.getString(req.getInputStream()));
                if (StringUtil.isNotEmpty(body)){
                    String[] params = StringUtil.splitString(body,"&");
                    if (ArrayUtil.isNotEmpty(params)){
                        for (String param:params){
                            String[] array = StringUtil.splitString(param,"=");
                            if (ArrayUtil.isNotEmpty(array)&&array.length==2){
                                String paramName = array[0];
                                String paramValue = array[1];
                                paramMap.put(paramName,paramValue);
                            }
                        }
                    }
                }
                Param param = new Param(paramMap);
                //调用Action方法
                Method actionMethod = handler.getActionMethod();
                Object result = ReflectionUtil.invokeMethod(controllerBean,actionMethod,param);
                //处理Action方法返回值
                if (result instanceof View){
                    //返回JSP页面
                    View view = (View) result;
                    String path = view.getPath();
                    if (StringUtil.isNotEmpty(path)){
                        if (path.startsWith("/")){   //这里应该是判断是否有数据
                            resp.sendRedirect(req.getContextPath()+path);
                        } else {
                            Map<String,Object> model = view.getModel();
                            for (Map.Entry<String,Object> entry:model.entrySet()){
                                req.setAttribute(entry.getKey(),entry.getValue());
                            }
                            req.getRequestDispatcher(ConfigHelper.getAppJspPath()+path).forward(req,resp);
                        }
    
                    }
                }else if (result instanceof Data){
                    //返回Json数据
                    Data data = (Data) result;
                    Object model = data.getModel();
                    if (model!=null){
                        resp.setContentType("application/json");
                        resp.setCharacterEncoding("UTF-8");
                        PrintWriter writer = resp.getWriter();
                        String json = JsonUtil.toJson(model);
                        writer.write(json);
                        writer.flush();
                        writer.close();
                    }
                }
            }
        }
    }

    其中涉及到流工具类、编码工具类、Json工具类

    一款简单的MVC框架开发完毕,通过这个DispatcherServlet来处理所有的请求,根据请求信息从ControllerHelper中获取对应的Action方法,然后使用反射技术调用Action方法,同事需要传入具体的方法参数,最后拿到返回值的类型并进行相应的处理。

    安装到Maven本地仓库

    1.用Maven将项目打包成jar

    2.用Maven将jar包安装到仓库

    此种方式的jar包不会把依赖jar包打包进去

    mvn install:install-file -Dfile=smart-framework-1.0.0.jar -DgroupId=org.smart4j -DartifactId=smart-framework -Dversion=1.0.0 -Dpackaging=jar

    此种为IDEA中安装包的代码

    java -Dmaven.multiModuleProjectDirectory=F:intellijIDEAworkspacesmart-framework -Dmaven.home=D:Programmer_QYapache-maven-3.5.0 -Dclassworlds.conf=D:Programmer_QYapache-maven-3.5.0inm2.conf "-javaagent:D:Programmer_QYIntelliJ IDEA 2017.1.1libidea_rt.jar=51430:D:Programmer_QYIntelliJ IDEA 2017.1.1in" -Dfile.encoding=UTF-8 -classpath D:Programmer_QYapache-maven-3.5.0ootplexus-classworlds-2.5.2.jar org.codehaus.classworlds.Launcher -Didea.version=2017.1.1 -s D:Programmer_QYapache-maven-3.5.0confsettings.xml install

    相当于以下两步

    总结

    通过Controller注解来定义Controller类;通过Inject注解来实现依赖注入;通过Action注解来定义Action方法。通过一系列的Helper类来初始化MVC框架;通过DispatcherServlet来处理所有的请求;根据请求方法与请求路径来调用具体的Action方法,判断Action方法的返回值,若为View类型,则跳转到JSP页面,若为Data类型,则返回JSON数据。

    整个框架基本能跑起来了,但里面还存在大量需要优化的地方。此外,还有一些非常好的特性尚未提供,比如AOP(Aspect Oriented Programming,面向方面编程)。我们可以使用这个特性来实现一些横向拦截操作,比如性能分析、日志收集、安全监控等,下一章我们将介绍如何实现AOP特性。

    结果截图

    源码:框架框架使用项目示例 

  • 相关阅读:
    【PHP内存泄漏案例】PHP对象递归引用造成内存泄漏
    【总结】/etc/rc.d/rc.local 与 /etc/profile .bash_profile .bashrc 文件执行顺序
    MySQL数据类型
    PHP通用分页(Pager)类
    【抚琴煮酒】我们的网站压力究竟在哪里?
    Linux/CentOS 服务安装/卸载,开机启动chkconfig命令详解|如何让MySQL、Apache开机启动?
    /etc/rc.d/rc与/etc/rc.d/init.d的关系
    PHP正则表达式30分钟入门教程
    数学之路-分布式计算-disco(4)
    数据库中存储日期的字段类型究竟应该用varchar还是datetime ?
  • 原文地址:https://www.cnblogs.com/aeolian/p/9594312.html
Copyright © 2011-2022 走看看