1. 程式人生 > >搭建輕量級Java Web 框架

搭建輕量級Java Web 框架

本章學習

  • 如何快速搭建自己的開發框架
  • 如何載入並讀取配置檔案
  • 如何實現一個簡單的IOC容器
  • 如何載入指定的類
  • 如何初始化框架

3.2 搭建開發環境

3.2.1 建立框架專案

建立一個 smart-framework 的專案

新增Maven 三座標

<groupId>org.smarwn</groupId>

<artifactId>smart-framework</artifactId>

<version>1.0.0</version>

<dependencies>

再新增相關的依賴

<dependencies>

<!--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>

<!-- slf4j日誌檔案管理包版本 -->

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-log4j12</artifactId>

<version>${slf4j.version}</version>

</dependency>

<!-- 匯入Mysql資料庫連結jar包 -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.38</version>

</dependency>

<!-- jackson json序列化工具 -->

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.3</version>

</dependency>

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.5.4</version>

</dependency>

<!--常用工具類-->

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-lang3</artifactId>

<version>3.3.2</version>

</dependency>

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-collections4</artifactId>

<version>4.0</version>

</dependency>

<!--apache Common DBUtils jar :JDBC的封裝-->

<dependency>

<groupId>commons-dbutils</groupId>

<artifactId>commons-dbutils</artifactId>

<version>1.6</version>

</dependency>

<!-- 匯入dbcp的jar包,用來在applicationContext.xml中配置資料庫連線池 -->

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-dbcp2</artifactId>

<version>2.0.1</version>

</dependency>

</dependencies>

至此,smart-framework 的Maven 依賴配置結束

3.2.1 建立例項專案

除了smart-framework這個專案 我們還要再建一個使用該框架的專案,我們命名為 smartx

它只需要依賴 smart-framework 即可,詳細pom.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>org.smarwn</groupId>

<artifactId>smartx</artifactId>

<version>1.0.0</version>

<dependencies>

<dependency>

<groupId>org.smartwn</groupId>

<artifactId>smart-framework</artifactId>

<version>1.0.0</version>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

<groupId>org.apache.tomcat.maven</groupId>

<artifactId>tomcat7-maven-plugin</artifactId>

<version>2.2</version>

<configuration>

<path>/${project.artifactId}</path>

</configuration>

</plugin>

</plugins>

</build>

</project>

開發環境準備好後,那麼我們就實現具體的細節。既然是一個框架,那麼首先要考慮的問題就是配置,需要讓配置儘可能的少,這樣開發者的學習成本才會更低。

3.3定義框架配置項

在smartx專案中的src/main/resources 目錄下,建立一個名為smart.properties的檔案,內容如下:

smart.framework.jdbc.driver=com.mysql.jdbc.Driver

smart.framework.jdbc.url=jdbc:mysql://127.0.0.1:3306/ssm

smart.framework.jdbc.username=root

smart.framework.jdbc.password=root

#定義初始連線數

smart.framework.jdbc.initialSize=0

#定義最大連線數

smart.framework.jdbc.maxActive=20

#定義最大空閒

smart.framework.jdbc.maxIdle=20

#定義最小空閒

smart.framework.jdbc.minIdle=1

#定義最長等待時間

smart.framework.jdbc.maxWait=60000

#基礎包名

smart.framework.app.base_package=org.smartwn.smartx

#定義js檔案路徑

smart.framework.app.jsp=/WEB-INF/view/

#定義靜態資原始檔路徑(js.css.圖片)

smart.framework.app.asset_path=/asset/

3.4 載入配置項

上面的配置檔案有了,那麼我們如何根據配置項的名稱來獲取配置項的取值呢?這是框架需要做的事,因此,我們在smart-farmework 專案中建立一個名為ConfigHelper 的助手類,讓它讀取smart.properties 配置檔案。

先定義介面

package org.smartwn.framework;

/**

* 提供相關配置的常量

* Created by Administrator on 2017/7/11.

*/

public interface ConfigConstant {

String CONFIG_FILE = "smart.properties";

String JDBC_DRIVERD ="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 JDBC_INITIALSIZE ="smart.framework.jdbc.initialSize";

//定義最大連線數

String JDBC_MAXACTIVE ="smart.framework.jdbc.maxActive";

//定義最大空閒

String JDBC_MAXIDLE ="smart.framework.jdbc.maxidle";

//定義最小空閒

String JDBC_MINIDLE ="smart.framework.jdbc.minidle";

//定義最長等待時間

String JDBC_MAXWAIT ="smart.framework.jdbc.maxwait";

//基礎包名

String APP_BASE_PACKAGE ="smart.framework.app.base_package";

//定義js檔案路徑

String APP_JSP ="smart.framework.app.jsp";

//定義靜態資原始檔路徑(js.css.圖片)

String APP_ASSET_PATH ="smart.framework.app.asset_path";

}

在定義助手類

package org.smartwn.framework.helper;

import org.smartwn.framework.ConfigConstant;

import org.smartwn.framework.util.PropsUtil;

import java.util.Properties;

/**

* 屬性檔案助手類

* Created by Administrator on 2017/7/11.

*/

public final 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_DRIVERD);

}

/**

* 獲取JDBC 路勁

* @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);

}

/**

* 獲取JDBC 初始連線數

* @return

*/

public static String getJDBCInitialsize(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_INITIALSIZE);

}

/**

* 獲取JDBC 最大連線數

* @return

*/

public static String getJDBCMaxactive(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_MAXACTIVE);

}

/**

* 獲取最大空閒

* @return

*/

public static String getJDBCMaxidle(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_MAXIDLE);

}

/**

* 獲取最小空閒

* @return

*/

public static String getJDBCMinidle(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_MINIDLE);

}

/**

* 獲取最長等待時間

* @return

*/

public static String getJDBCMaxwait(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.JDBC_MAXWAIT);

}

/**

* 獲取應用基礎包名

* @return

*/

public static String getAppBasePackage(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_BASE_PACKAGE);

}

/**

* 獲取應用jsp路徑

* @return

*/

public static String getAppJsp(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_JSP,"/WEB_INF/view/");

}

/**

* 獲取靜態資源路徑

* @return

*/

public static String getAppAssetPath(){

return PropsUtil.getString(CONFIG_PROPS,ConfigConstant.APP_ASSET_PATH,"/asset/");

}

}

3.5 開發一個類載入器

我們需要開發一個“類載入器”來載入所有該基礎包下的所有類,比如使用了某註解的類或者實現了某介面的類,再或者繼承了某父類的所有子類等

所以有必要寫一個ClassUtil 工具類,提供與類操作相關的方法,比如獲取類載入器、載入類、獲取指定包名下的所有類等。程式碼如下:

package org.smartwn.framework.util;

import org.slf4j.LoggerFactory;

import org.slf4j.Logger;

import java.io.File;

import java.io.FileFilter;

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;

/**

* 載入類

* Created by Administrator on 2017/7/12.

*/

public final class ClassUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);

/**

* 獲取類載入器

*/

public static ClassLoader getClassLoader(){

return Thread.currentThread().getContextClassLoader();

}

/**

* 載入類

*/

public static Class<?> loadClass(String className,boolean isInitialized){

Class<?> cls;

try{

cls = Class.forName(className,isInitialized,getClassLoader());

}catch(ClassNotFoundException e){

LOGGER.error("load class failure:"+className,e);

throw new RuntimeException();

}

return cls;

}

/**

* 獲取指定包名下的所有類

*/

public static Set<Class<?>> getClassSet(String packageName){

Set<Class<?>> classSet = new HashSet<Class<?>>();

try{

Enumeration<URL> urls= getClassLoader().getResources(packageName.replace(".","/"));

while(urls.hasMoreElements()){

URL url = urls.nextElement();

if(url!=null){

String protocol =url.getProtocol();

if(protocol.equals("file")){

String packagePath = url.getPath().replace("%20"," ");

addClass(classSet,packagePath,packageName);

} else if(protocol.equals("jar")){

JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();

if(jarURLConnection != null){

JarFile jarFile = jarURLConnection.getJarFile();

if(jarFile!=null){

Enumeration<JarEntry> jarEntries = jarFile.entries();

while(jarEntries.hasMoreElements()){

JarEntry jarEntry= jarEntries.nextElement();

String jarEntryName = jarEntry.getName();

if(jarEntryName.endsWith(".class")){

String className = jarEntryName.substring(0,jarEntryName.lastIndexOf(".")).replace("/",".");

doAddClass(classSet,className);

}

}

}

}

}

}

}

}catch(Exception e){

LOGGER.error("get class Set failure:"+packageName,e);

throw new RuntimeException();

}

return classSet;

}

public static void addClass(Set<Class<?>> classSet,String packagePath,String packageName ){

File[] files = new File(packagePath).listFiles(new FileFilter(){

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;

}

doAddClass(classSet,className);

}else{

String subPackagePath = filename;

if(StringUtil.isNotEmpty(subPackagePath)){

subPackagePath = packagePath+"/"+subPackagePath;

}

String subPackageName = filename;

if (StringUtil.isNotEmpty(packageName)){

subPackageName= packageName+"."+subPackageName;

}

doAddClass(classSet,subPackageName);

}

}

}

public static void doAddClass(Set<Class<?>> classSet,String className){

Class<?> cls = loadClass(className,false);

classSet.add(cls);

}

}

我們的目標是在控制類上使用Controlle、Service、action、inject註解,所以我們要定義4個註解類

控制器註解程式碼:

package org.smartwn.framework.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 控制器註解

* Created by Administrator on 2017/7/13.

*/

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

public @interface Controller {

}

Action註解程式碼:

package org.smartwn.framework.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 方法註解

* Created by Administrator on 2017/7/13.

*/

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Action {

/**

* 請求型別路勁

*/

String value();

}

服務類註解程式碼:

package org.smartwn.framework.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 服務類註解

* Created by Administrator on 2017/7/13.

*/

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Service {

}

依賴注入類註解程式碼:

package org.smartwn.framework.annotation;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 依賴注入註解

* Created by Administrator on 2017/7/13.

*/

@Target(ElementType.FIELD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Inject {

}

由於我們在smart.properties中定義了base_package,通過classUtil 來記載她,所以有必要提供一個ClassHelper助手類,讓他分別獲取應用包下的所有類,所有service類、所有controller類,此外我們將帶有Controller和Service的註解的類所產生的物件,理解為Smart框架鎖管理的Bean,所以有必要再ClassHelper 類中增加一個獲取應用包名下所有Bean類的方法,細節程式碼如下:

package org.smartwn.framework.helper;

import org.smartwn.framework.ConfigConstant;

import org.smartwn.framework.annotation.Controller;

import org.smartwn.framework.annotation.Service;

import org.smartwn.framework.util.ClassUtil;

import java.util.HashSet;

import java.util.Set;

/**

* 類操作助手類

* Created by Administrator on 2017/7/13.

*/

public final 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;

}

/**

* 獲取應用包名下的所有Service類

*/

public static Set<Class<?>> getServiceClassSet(){

Set<Class<?>> classSet = new HashSet<Class<?>>();

for (Class<?> cls:CLASS_SET){

if(cls.isAnnotationPresent(Service.class)){//如果指定型別的註釋存在於此元素上,則返回 true

classSet.add(cls);

}

}

return classSet;

}

/**

* 獲取應用包名下的所有Controller類

*/

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;

}

/**

* 獲取應用包名下的所有Bean類(包括Service 、Controller)

*/

public static Set<Class<?>> getBeanClassSet(){

Set<Class<?>> classSet = new HashSet<Class<?>>();

classSet.addAll(getServiceClassSet());

classSet.addAll(getControllerClassSet());

return classSet;

}

}

像這樣我們使用ClassHelper 封裝了ClassUtil類,並提供了一系列的助手方法

3.6 實現Bean容器

使用ClassHelper類可以獲取所有載入的類,但無法通過類例項化物件,因此,需要提供一個反射工具類, 我們不妨將該類命名為ReflectionUtil,程式碼如下:

package org.smartwn.framework.util;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;

import java.lang.reflect.Method;

/**

* 反射工具類

* Created by Administrator on 2017/7/14.

*/

public final class ReflectionUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);

/**

* 建立例項

* @param cls

* @return

*/

public static Object newInstance(Class<?> cls){

Object instance;

try {

instance = cls.newInstance();

}catch(Exception e){

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;

try{

method.setAccessible(true);

result = method.invoke(obj,args);

}catch(Exception e){

LOGGER.error("invaoke Method failure",e);

throw new RuntimeException(e);

}

return result;

}

/**

* 設定成員變數的值

* @param obj

* @param field

* @param args

*/

public static void setField(Object obj, Field field,Object...args){

try {

field.setAccessible(true);

field.set(obj,args);//將args賦值給obj的變數

} catch (Exception e) {

LOGGER.error("invaoke Field failure",e);

throw new RuntimeException(e);

}

}

}

我們需要獲取所有被Smart框架管理的類,此時需要呼叫ClassHelper 類的getBeanClassSet方法 隨後需要迴圈呼叫ReflectionUtil類的newInstance方法,根據類來例項化物件,最後放到Map中,so 我們還要建一個BeanHelper,程式碼如下:

package org.smartwn.framework.helper;

import org.smartwn.framework.util.ReflectionUtil;

import java.util.HashMap;

import java.util.Map;

import java.util.Set;

/**

* Created by Administrator on 2017/7/14.

*/

public class BeanHelper {

private static final Map<Class<?>,Object> BEAN_MAP = new HashMap<Class<?>, Object>();

/**

* 用於Bean對映 (存放Bean類與Bean是例項對映關係)

*/

static{

Set<Class<?>> classSet = ClassHelper.getBeanClassSet();

for(Class<?> cls:classSet){

Object obj = ReflectionUtil.newInstance(cls);

BEAN_MAP.put(cls, obj);

}

}

/**

* 獲取Bean對映

* @return

*/

public static Map<Class<?>,Object> getBeanMap(){

return BEAN_MAP;

}

/**

* 獲取Bean例項

* @param cls

* @param <T>

* @return

*/

@SuppressWarnings("unchecked")

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 容器了,so我們只需要呼叫GetBean方法 傳入一個Bean類就可以獲取例項。

3.7 實現依賴注入功能

現在我們實現例項化 Controller/Service 的例項 那麼 我們如何去例項化它們裡面成員變數呢,還記得之前@Inject麼,那麼誰來例項化它們呢,不是用開發者用new來例項,而是要框架自身去例項化它們,想這樣的類例項化過程,我們稱她為IOC(invoersion of Control),控制不是有開發者決定,而是反轉給框架了,一般我們也將控制反轉成為DI(Dependency Injection,依賴注入),那麼如何實現依賴注入?

最簡單的辦法是:先通過BeanHelper 獲取所有BeanMap(一個map<Class<?>,Object>結構,記錄類與物件的對映關係),然後遍歷這個對映關係,分別取出Bean類和Bean例項,進而通過反射獲取類中所有的成員變數。繼續遍歷這些成員變數,在迴圈中判斷當前成員變數是否帶有Inject註解,若帶有著給他賦值,程式碼如下:

package org.smartwn.framework.helper;

import org.smartwn.framework.annotation.Inject;

import org.smartwn.framework.util.ArrayUtil;

import org.smartwn.framework.util.CollectionUtil;

import org.smartwn.framework.util.ReflectionUtil;

import java.lang.reflect.Field;

import java.util.HashMap;

import java.util.Map;

/**

* 依賴注入助手類

* Created by Administrator on 2017/7/14.

*/

public final class IcoHelper {

static{

//獲取所有的Bean類和Bean例項化之間的對映關係(簡稱Bean map)

Map<Class<?>,Object> beanMap = BeanHelper.getBeanMap();

if(CollectionUtil.isNotEmpty(beanMap));

for(Map.Entry<Class<?>,Object> beanEntry:beanMap.entrySet()){

//從BeanMap 中獲取Bean類與Bean例項

Class<?> beanClass = beanEntry.getKey();

Object beanInstance = beanEntry.getValue();

//獲取Bean類定義的所有成員變數(簡稱Bean Field)

Field[] beanFields = beanClass.getFields();

if(ArrayUtil.isNotEmpty(beanFields)){

for(Field beanField:beanFields){

//判斷當前bean field 是否帶 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);

}

}

}

}

}

}

}

可見一個簡單的IOC框架只需要十幾行的程式碼就能搞定。需要注意的是,此時在IOC框架中所管理的物件都是單例的,由於IOC框架還是從BeanHelper中獲取Bean Map的,兒Bean Map 中物件都是事先建立好並放入這個Bean容器的,所以所有物件都是單例。

3.8 載入Controller

我們可以通過ClassHelper 來獲取所有定義了Controller的註解類,可以同過反射獲取該類所有帶Action的註解的方法(簡稱“Action方法”),獲取Action 註解中的請求表示式,進而獲取請求方法與請求路徑,封裝一個請求物件(Request)與處理的物件(Handler),最後Request與Handler建立一個對映關係,放入一個Action Map 中,並提供一個可根據請求方法與請求路徑獲取處理物件的方法。

說先我們要定義一個Request類,程式碼如下:

package org.smartwn.framework.bean;

import org.apache.commons.lang3.builder.EqualsBuilder;

import org.apache.commons.lang3.builder.HashCodeBuilder;

/**

* 封裝請求資訊

* Created by Administrator on 2017/7/17.

*/

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 String getRequestPath() {

return requestPath;

}

public int hashCode(){

return HashCodeBuilder.reflectionHashCode(this);

}

public boolean equal(Object obj){

return EqualsBuilder.reflectionEquals(this,obj);

}

}

然後再編寫一個Handler類 程式碼如下:

package org.smartwn.framework.bean;

import java.lang.reflect.Method;

/**

* 封裝Action類

* Created by Administrator on 2017/7/17.

*/

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.smartwn.framework.helper;

import org.smartwn.framework.annotation.Action;

import org.smartwn.framework.bean.Handler;

import org.smartwn.framework.bean.Request;

import org.smartwn.framework.util.ArrayUtil;

import org.smartwn.framework.util.CollectionUtil;

import java.lang.reflect.Method;

import java.util.HashMap;

import java.util.Map;

import java.util.Set;

/**

* 控制器助手類

* Created by Administrator on 2017/7/17.

*/

public final class ControllerHelper {

/**

* 用於存放請求與處理器的對映關係

*/

private static Map<Request,Handler> ACTION_MAP =new HashMap<Request, Handler>();

/**

* 獲取所有的Controller 類

*/

static{

Set<Class<?>> controllerCalssSet = ClassHelper.getControllerClassSet();

if(CollectionUtil.isNotEmpty(controllerCalssSet)){

for(Class<?> controllerClass:controllerCalssSet){

//獲取Controller中方法定義

Method[] methods = controllerClass.getMethods();

if(ArrayUtil.isNotEmpty(methods)){

//遍歷Cpntroller的方法

for(Method method:methods){

//判斷是否有Action的註解

if(method.isAnnotationPresent(Action.class)){

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.put(request,handler);

}

}

};

}

}

}

}

}

/**

* 獲取 Handler

*/

public static Handler getHandler(String requestMethod,String requestPath){

Request request = new Request(requestMethod,requestPath);

return ACTION_MAP.get(request);

}

}

可見ControllerHelper 中封裝了一個Action Map通過他來存放Request 和 Handler 之間的對映關係,然後通過ClassHelper 獲取所有帶Controller註解的引數,然後遍歷獲取Action 註解中的URL,最後初始化request和heandler

3.9 初始化框架

用過上面的過程,我們建立了ClassHelper、BeanHelper、ControllerHelper、IocHelper,這4個Helper類需要通過一個入口程式來載入他們,實際上是載入他們的靜態塊,所以我們要寫一個HelperLoader類來載入他們,程式碼如下:

package org.smartwn.framework;

import org.smartwn.framework.helper.BeanHelper;

import org.smartwn.framework.helper.ClassHelper;

import org.smartwn.framework.helper.ControllerHelper;

import org.smartwn.framework.helper.IocHelper;

import org.smartwn.framework.util.ClassUtil;

/**

* 載入相應的Helper類

* Created by Administrator on 2017/7/22.

*/

public final 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);

}

}

}

現在我們就可以直接呼叫HelperLoader 的init方法來初始化Helper類了.實際上,當我們第一次訪問類時,就會載入static塊,這裡只是為了讓載入更加集中。

3.10 請求轉發器

以上都是為了這一步做準備,我們需要編寫一個servlet,讓他來處理所有的請求, 從HttpServletRequest物件中獲取請求方法和請求路徑,通過ControllerHelper#getHandler方法來獲取Handler物件。

當拿到Handler物件後,我們就可以獲取Controller的類,進而通過BeanHelper.getBean方法獲取Controller的例項物件,隨後可以從HttpServletRequest物件中獲取所有請求引數,並將器初始哈到一個名為Param的物件中,Param類的程式碼如下:

package org.smartwn.framework.bean;

import org.smartwn.framework.util.CastUtil;

import java.util.Map;

/**

* 請求引數物件

* Created by Administrator on 2017/7/22.

*/

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類中,會有一系列的get方法,可以通過引數名獲取指定型別的引數值,也可以獲取所有引數的Map結構,

還可從Handler物件中獲取Action的方法返回值,該返回值可能有兩種情況:

1、若返回的View型別的檢視物件,則返回jsp頁面。

2、若返回的Data型別的資料物件,則返回一個JSON資料。

我們需要根據以上兩種情況來判斷Action的返回值,並做到不同的處理。

首先,看看View,程式碼如下:

package org.smartwn.framework.bean;

import java.util.HashMap;

import java.util.Map;

/**

* 返回檢視物件

* Created by Administrator on 2017/7/22.

*/

public class View {

/**

* 檢視物件

*/

private String path;

/**

* 模型資料

*/

private Map<String,Object> model;

public View(String path){

this.path=path;

model =new HashMap<String, Object>();

}

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;

}

}

由於檢視中可包含模型資料,因此View中包括了檢視路徑和該檢視中所需的模型資料Map<String,Object>,然後看看Data類,程式碼如下:

package org.smartwn.framework.bean;

/**

* 返回資料物件

* Created by Administrator on 2017/7/22.

*/

public class Data {

/**

* 模型資料

*/

private Object model;

public Object getModel() {

return model;

}

public void setModel(Object model) {

this.model = model;

}

}

返回的Data型別的資料封裝了一個Object型別的模型資料,框架會將該物件寫入HttpServletResponse物件中,從而直接輸入至瀏覽器中,

以下便是MVC框架中最核心的DispatcherServlet類,程式碼如下:

package org.smartwn.framework;

import com.alibaba.fastjson.JSON;

import org.smartwn.framework.bean.Data;

import org.smartwn.framework.bean.Handler;

import org.smartwn.framework.bean.Param;

import org.smartwn.framework.bean.View;

import org.smartwn.framework.helper.BeanHelper;

import org.smartwn.framework.helper.ConfigHelper;

import org.smartwn.framework.helper.ControllerHelper;

import org.smartwn.framework.util.*;

import javax.servlet.ServletConfig;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.ServletRegistration;

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;

/**

* 請求轉發器

* Created by Administrator on 2017/7/22.

*/

@WebServlet(urlPatterns="/*",loadOnStartup = 0)

public class DispatcherServlet extends HttpServlet{

@Override

public void init(ServletConfig config) throws ServletException {

//初始化相關的Helper類

HelperLoader.init();

//獲取ServletContext物件

ServletContext servletConfig = config.getServletContext();

//註冊處理jsp的 Servlet

ServletRegistration jspServlet =servletConfig.getServletRegistration("jsp");

jspServlet.addMapping(ConfigHelper.getAppJsp()+"*");

//註冊處理靜態資源預設Servlet

ServletRegistration defaultServlet =servletConfig.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類和其例項

Class<?> controllerCalss = handler.getControllerClass();

Object controllerBean = BeanHelper.getBean(controllerCalss);

//建立請求引數物件

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);

}

String body= CodecUtil.decodeURL(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){

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(path).forward(req,resp);

}

}

}else if(result instanceof Data){

Data data = (Data)result;

Object model = data.getModel();

if(model != null){

resp.setContentType("application/json");

resp.setCharacterEncoding("UTF-8");

PrintWriter out = resp.getWriter();

String json = JsonUtil.toJson(model);

out.write(json);

out.flush();

out.close();

}

}

}

}

}

在dispatcherServlet 中用了幾個新的工具類:

Stream類程式碼如下:

package org.smartwn.framework.util;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import javax.servlet.ServletInputStream;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

/**

* 流操作工具類

* Created by Administrator on 2017/7/23.

*/

public final class StreamUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(StreamUtil.class);

//從輸入流中獲取字串

public static String getString(ServletInputStream inputStream) {

StringBuilder sb = new StringBuilder();

try{

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

String line;

while((line =reader.readLine())!=null){

sb.append(line);

}

}catch (IOException e){

LOGGER.error("get String Stram failure",e);

throw new RuntimeException(e);

}

return sb.toString();

}

}

CodecUtil 類用於編碼與解碼操作,程式碼如下:

package org.smartwn.framework.util;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.net.URLDecoder;

import java.net.URLEncoder;

/**

* 編碼與解碼操作工具類

* Created by Administrator on 2017/7/23.

*/

public final class CodecUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(CodecUtil.class);

/**

* 將URL 編碼

* @param source

* @return

*/

public static String encodeURL(String source){

String target;

try{

target = URLEncoder.encode(source,"UTF-8");

}catch (Exception e){

LOGGER.error("encode url failure",e);

throw new RuntimeException(e);

}

return target;

}

/**

* 將URl 解碼

* @param source

* @return

*/

public static String decodeURL(String source){

String target;

try {

target = URLDecoder.decode(source, "UTF-8");

}catch (Exception e){

LOGGER.error("decode url failure",e);

throw new RuntimeException(e);

}

return target;

}

}

Json類用於處理JSON 與POJO之間轉換,基礎Jackson實現,程式碼如下:

package org.smartwn.framework.util;

import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

/**

* Created by Administrator on 2017/7/23.

*/

public final class JsonUtil {

private static final Logger LOGGER = LoggerFactory.getLogger(CodecUtil.class);

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

/**

* 將POPJ轉化為JSON

* @param obj

* @return

*/

public static <T> String toJson(T obj){

String json;

try{

json = OBJECT_MAPPER.writeValueAsString(obj);

}catch(JsonProcessingException e){

LOGGER.error("convert POJO to JSON failure",e);

throw new RuntimeException(e);

}

return json;

}

/**

* 將json 轉為 Pojo

* @param json

* @param type

* @param <T>

* @return

*/

public static <T> T fromJson(String json,Class<T> type){

T pojo;

try{

pojo = OBJECT_MAPPER.readValue(json,type);

}catch (Exception e){

LOGGER.error("convert JSON to POJO failure",e);

throw new RuntimeException(e);

}

return pojo;

}

}

至此,一款簡單的MVC框架就開發完畢了,通過這個DispatcherServlet來處理所有請求,根據請求資訊從ControllerHelper中獲取對應的Action方法,然後使用反射技術呼叫Action 方法,同時需要具體的傳入

方法引數,最後拿到返回值判斷並判斷返回值的型別,進行相應的處理。

3.11 總結

通過Controller註解來定義Controller類,通過Inject註解來實現依賴注入,通過Action來註解定義Action方法

通過一系列的Helper類來初始化MVC框架,通過DispatcherServlet 來處理所有請求,根據請求方法與請求路徑呼叫具體的Action的返回型別,若為View 則返回調到Jsp頁面,若為Data型別則返回JSON資料。

整個框架基本能跑起來了,但裡面還存在大量需要優化的地方,此外還有一些非常好的特性未提供,比如AOP,

我們可以使用這個特性來實現橫向攔截操作,比如效能分析,比如日誌收集,安全控制。

相關推薦

搭建輕量級Java Web 框架

本章學習 如何快速搭建自己的開發框架 如何載入並讀取配置檔案 如何實現一個簡單的IOC容器 如何載入指定的類 如何初始化框架 3.2 搭建開發環境 3.2.1 建立框架專案 建立一個 smart-framework 的專案 新增Maven 三座標 <g

輕量級 Java Web 框架技術選型

前面已對該 Java Web 框架做了一些簡要描述,目標就是打造一個輕量級的 Java Web 開發框架。我們不考慮使用 Struct、Spring、Hibernate 以及 MVC 模式,我們只是取其精華、去其糟粕,我們不是要重造輪子,而是要改造輪子,努力打造一款輕巧

基於Jersey框架搭建Restful Java Web Service的基本步驟

本文由Markdown語法編輯器編輯完成。 1. Restful Web Service 2. Restful的基本框架 3. 基於Jersey框架搭建Restful Web Service的基本步驟 Jersey框架不僅實現了JAX-RS規

神級Java程序員 開車教你基礎開發,最簡單 微型Java Web框架

padding ont 目的 簡單 json 破解 apach java學習 web 介紹: 它是Java中的開放源代碼(Apache License)微型Web框架,具有最小的依賴性和快速的學習曲線。 該項目的目標是在Java中創建一個易於使用和破解的微型Web框架。

搭建一個java web服務端

des tin chm rate web項目 initial 安裝目錄 網上 mil   最近也是做了一個簡單的java web 項目,由於以前也是沒接觸過,在這裏記錄下搭建一個web服務端的過程。   一般我們做一個服務端要麽在本地自己的電腦上先安裝環境,一般是windo

搭建Linux-java web運行環境之二:安裝mysql

navi sql glibc x86 卸載 系統服務 依賴 host mys 環境 OS:Red Hat Enterprise Linux Server release 7.3 (Maipo) JDK:jdk-7u80-linux-x64.tar.gz Tomcat:ap

從零寫一個Java WEB框架(七)Controller層轉換器

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

從零寫一個Java WEB框架(六)Controller層優化

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

從零寫一個Java WEB框架(五)IOC建立

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

從零寫一個Java WEB框架(四)框架的演進

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

從零寫一個Java WEB框架(三)Dao層優化

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

從零寫一個Java WEB框架(二)Server層 優化

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github 上一篇地

從零寫一個Java WEB框架(一)

該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github

Python基礎入門,利用Django搭建第一個web框架!(文末福利)

  本文面向:有Python基礎,剛接觸web框架的Django初學者。 環境:windows7 python3.5.1 pycharm Django 1.10版 pip3 一、Django簡介 百度百科:一個開放原始碼的Web框架,由Python語言編寫......

自定義Java web框架(二)

接續上一章自定義Java web框架(一) 的內容,這章主要是講解如何實現類載入器,實現個Bean容器,用於生產Bean例項。 先看檔案目錄如下: 做開發思路是很重要的。首先,我們的目的是要獲取Bean的例項,這樣就需要一個Bean的容器,通過這個容器動態的獲

自定義Java web框架(三)

接續上一篇文章自定義Java web框架(二) 本章內容主要是講解如何實現IOC功能。 所謂的IOC,控制反轉,bean的例項化通過框架自身來實現,也叫依賴注入。平時我們自己程式設計的例項化,都是通過new的方式實現,IOC把這個過程交給框架去處理,不需要手動程

自定義Java web框架(四)

接續上一篇文章自定義Java web框架(三) 本章主要講解如何處理Controller類,請求進入Controller類之後,根據請求的方法和路徑找到Controller類中的Action方法執行業務邏輯處理。 實現思路如下: 獲取所有定義了Controlle

自定義Java web框架(五)

接續上一篇文章自定義Java web框架(四) 本章主要講解框架的初始化。 實現思路如下: 主要是把前幾篇文章中定義的相關幫助類初始化,類載入器助手類、Bean助手類、IOC助手類、Controller助手類。 核心程式碼如下: public final cla

自定義Java web框架(六)

接續上一篇文章自定義Java web框架(五) 本章主要講解請求轉發到後端之後如何處理的請求轉發,也叫路由。 實現思路如下: 我們需要編寫一個Servlet,讓它來處理所有的請求,從HttpServletRequest物件中獲取請求方法與請求路徑,通過Contr

Java Web 框架 Latke v2.4.39,重寫控制器層

   簡介 Latke('lɑ:tkə,土豆餅)是一個簡單易用的 Java Web 應用開發框架,包含 MVC、IoC、事件通知、ORM、外掛等元件。 在實體模型上使用 JSON 貫穿前後端,使應用開發更加快捷。這是 Latke 不同於其他框架的地方,非常適合小型應用的快速開發