首先安利一波"架構探險——從零開始寫JavaWeb框架"這本書,想要學習的朋友可以到這裏下載,https://pan.baidu.com/s/1eSEaE10
源碼下載:http://download.csdn.net/detail/qq_31957747/9842676,我的是IDEA項目,順便在給用myeclipse的同志們安利一波IDEA吧:IDEA強無敵!
好,回到主題首先先說說Ioc(控制反轉,又稱依賴注入),
Ioc這個部分定義三個註解:Autowire、Controller以及Service,註解的定義代碼如下:
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,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
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 {
}
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 Service {
}
Controller註解用來標識控制器,學過springMVC的想必很熟悉這個註解,而我們這個Ioc實現的就是在Controller中的被Autowired註解標識的Service依賴注入進去。如下
/**
* 控制層
*/
@Controller
public class XxController {
@Autowired
private XxService xxService;
}
我們這個框架只用到了Controller和Service這兩個Bean,若你想要更多的Bean類型,比如Service中再注入Dao,也可以進行改進,道理都一樣。
Ioc實現思路:我們先在一個properties文件中定義controller層和service層的基礎包名,通過Resource定位獲取到這個包的絕對路徑然後進行掃描,掃描之後將這些Bean類的Class存到一個Set<Class<?>>中(Set中的元素不會重複,所以不必擔心有重複Class進入),然後再根據這個Set集合通過反射構造一個Map<Class<?>,Object>,這個Map的key也就是Set的值,而Map的value是Set的key(也就是Class)執行newInstance出來的對象,這樣預先創建對象並存儲在Map中也就保證了單例,最後就是依賴注入的操作,通過遍歷上面產生的Map集合對key(也就是Class)的判斷其中是否有Autowired註解的字段,然後進行注入。
主要代碼:
package org.smart4j.framework.helper;
import org.smart4j.framework.annotation.Controller;
import org.smart4j.framework.annotation.Service;
import org.smart4j.framework.util.ClassUtil;
import java.util.HashSet;
import java.util.Set;
/**
* 類操作助手類
*/
public final class ClassHelper {
/**
* 定義類集合(用於存放所加載的類)
*/
private static final Set<Class<?>> CLASS_SET;
static{
String basePackage = ConfigHelper.getAppControllerBasePackage();
CLASS_SET = ClassUtil.getClassSet(basePackage); //將Controller包下面的類的Class保存到Set集合
basePackage = ConfigHelper.getAppServiceBasePackage();
CLASS_SET.addAll(ClassUtil.getClassSet(basePackage)); //將Service包下面的類的Class保存到Set集合
}
/**
*獲取應用包名下的所有類
*/
public static Set<Class<?>> getClassSet(){
return null;
}
/**
* 獲取應用包名下所有Service類
*/
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;
}
/**
* 獲取應用包名下所有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<?>> beanClassSet = new HashSet<Class<?>>();
beanClassSet.addAll(getServiceClassSet());
beanClassSet.addAll(getControllerClassSet());
return beanClassSet;
}
}
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 final class BeanHelper {
/***
* 定義Bean映射(用於存放Bean類與Bean實例的映射關係)
*/
private static final Map<Class<?>,Object> BEAN_MAP = new HashMap<Class<?>,Object>();
static{
Set<Class<?>> beanClassSet = ClassHelper.getBeanClassSet();
for(Class<?> beanClass : beanClassSet){
Object obj = ReflectionUtil.newInstance(beanClass);
BEAN_MAP.put(beanClass,obj); //根據存放Class的Set集合構造出Map
}
}
/**
* 獲取Bean映射
*/
public static Map<Class<?>,Object> getBeanMap(){
return BEAN_MAP;
}
/**
* 獲取Bean實例
*/
@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);
}
}
package org.smart4j.framework.helper;
import org.smart4j.framework.annotation.Autowired;
import org.smart4j.framework.util.ArrayUtil;
import org.smart4j.framework.util.CollectionUtil;
import org.smart4j.framework.util.ReflectionUtil;
import java.lang.reflect.Field;
import java.util.Map;
/**
* 依賴注入助手類
*/
public final class IocHelper {
static{
//獲取所有的Bean類和Bean實例之間的映射關係(簡稱 Bean Map)
Map<Class<?>,Object> beanMap = BeanHelper.getBeanMap();
if(CollectionUtil.isNotEmpty(beanMap)){
//遍歷Bean Map
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.getDeclaredFields();
if(ArrayUtil.isNotEmpty(beanFields)){
//遍歷Bean Field
for(Field beanField : beanFields){
//判斷當前Bean Field是否代用Autowired註解
if(beanField.isAnnotationPresent(Autowired.class)){
Class<?> beanFieldClass = beanField.getType();
Object beanFieldInstance = beanMap.get(beanFieldClass);
if(beanFieldInstance != null){
//通過反射初始化BeanField的值
ReflectionUtil.setField(beanInstance,beanField,beanFieldInstance);
}
}
}
}
}
}
}
}
接下來說說Controller層mvc的實現:
我們要實現的控制層是這個樣子的:
package org.smart4j.framework.controller;
import org.smart4j.framework.annotation.Action;
import org.smart4j.framework.annotation.Controller;
import org.smart4j.framework.annotation.Autowired;
import org.smart4j.framework.bean.Data;
import org.smart4j.framework.bean.Param;
import org.smart4j.framework.bean.View;
import org.smart4j.framework.service.HelloService;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 控制層
*/
@Controller
public class HelloController {
@Autowired
private HelloService helloService;
@Action("get:/hello")
public View hello(Param param){
View view = new View("hello.jsp");
view.addModel("currentTime",new Date());
helloService.sout();
return view;
}
@Action("post:/getNum")
public Data getNum(Param param){
Map map = new HashMap();
map.put("name","jqk");
map.put("age",11);
return new Data(map);
}
}
學過struts2和springMVC的同學,應該會感覺很熟悉,這個Controller的service在Ioc那塊已經被我們注入了,所以我們現在關心的就是裏面的Action註解修飾的方法。
這個Controller有hello和getNum兩個方法,其中hello是返回頁面的action,我們用View來表示返回的頁面對象;getNum是返回json數據的action,我們用Data來表示返回的數據對象,還有可以封裝的點在於,Action這個註解的值由“:”分爲左右兩側,左側爲請求方法,右側爲請求路徑,可以封裝爲Request類表示請求信息,請求信息對應的方法也進行封裝由Handler類表示,最後是請求的參數封裝到Param這個類中。
Action註解以及javaBean的代碼如下:
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;
/**
* Action 方法註解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
/**
* 請求類型與路徑
*/
String value();
}
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 String getRequestPath(){
return requestPath;
}
@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this,obj);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
package org.smart4j.framework.bean;
import java.lang.reflect.*;
/**
* 封裝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;
}
}
package org.smart4j.framework.bean;
import org.smart4j.framework.util.CastUtil;
import java.util.Map;
/**
* 請求參數信息
*/
public class Param {
private Map<String,Object> paramMap;
public Param(Map<String,Object> paramMap){
this.paramMap = paramMap;
}
/**
* 根據參數名獲取long型參數值
*/
public long getLong(String name){
return CastUtil.castLong(paramMap.get(name));
}
/**
* 獲取所有字段信息
*/
public Map<String,Object> getMap(){
return paramMap;
}
}
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 String getRequestPath(){
return requestPath;
}
@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this,obj);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
package org.smart4j.framework.bean;
/**
* 返回數據對象
*/
public class Data {
/**
* 模型數據
*/
private Object model;
public Data(Object model){
this.model = model;
}
public Object getModel(){
return model;
}
}
接下來說說Controller的mvc實現思路:在存放類的Class的Set集合中,取出含有Controller註解的Class並對其中含有的方法進行遍歷,判斷是否含有Action註解,對含有Action註解並且註解值(也就是請求方法:請求路徑)滿足格式要求的方法,我們用一個Map<Request,Handler>來存放,map的key表示請求信息,map的value表示請求的方法。再定義一個DispatherServlet,這個Servlet用來請求轉發,將用戶的請求轉發給對應的Controller的對應方法,將用戶的請求參數封裝到Param中,然後通過反射調用controller的對應方法,並對其返回值進行判斷,返回值爲View對象的返回jsp頁面,返回值爲Data對象的,返回JSON數據。
主要代碼如下:
package org.smart4j.framework.helper;
import org.smart4j.framework.annotation.Action;
import org.smart4j.framework.bean.Handler;
import org.smart4j.framework.bean.Request;
import org.smart4j.framework.util.ArrayUtil;
import org.smart4j.framework.util.CollectionUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.lang.reflect.*;
/**
* 控制器助手類
*/
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(CollectionUtil.isNotEmpty(controllerClassSet)){
//遍歷這些Controller類
for(Class<?> controllerClass : controllerClassSet){
//獲取Cntroller類中定義的方法
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
*/
public static Handler getHandler(String requestMethod,String requestPath){
Request request = new Request(requestMethod,requestPath);
return ACTION_MAP.get(request);
}
}
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.*;
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;
/**
* 請求轉發器
*/
@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
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//獲取請求方法與請求路徑
String requestMethod = request.getMethod().toLowerCase();
String requestPath = request.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 = request.getParameterNames();
while(paramNames.hasMoreElements()){
String paramName = paramNames.nextElement();
String paramValue = request.getParameter(paramName);
paramMap.put(paramName,paramValue);
}
String body = CodecUtil.decodeURL(StreamUtil.getString(request.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("/")){
response.sendRedirect(request.getContextPath()+path);
}else{
Map<String,Object> model = view.getModel();
for(Map.Entry<String,Object> entry : model.entrySet()){
request.setAttribute(entry.getKey(),entry.getValue());
}
request.getRequestDispatcher(ConfigHelper.getAppJspPath()+path).forward(request,response);
}
}
}else if(result instanceof Data){
//返回JSON數據
Data data = (Data)result;
Object model = data.getModel();
if(model != null){
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
String json = JsonUtil.toJson(model);
writer.write(json);
writer.flush();
writer.close();
}
}
}
}
}
這個類提供加載的統一入口:
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;
/**
* 加載相應的類
*/
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);
}
}
}
運行項目查看下結果:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Hello</title>
<script src="/asset/jquery.js"></script>
<script src="/asset/hello.js"></script>
</head>
<body>
<h1>Hello!</h1>
<h2>當前時間:${currentTime}</h2>
</body>
</html>
$(function(){
var url="/getNum";
var params = {
timestamp : new Date()
}
$.post(url, params, function(data) {
alert(data.name+" "+data.age);
});
});
可以看到MVC以及IOC都已經實現了。
開發過程中遇到的小BUG:
1、 java.lang.NoClassDefFoundError:這個是遇到的最大的麻煩,這個錯誤不同於java.lang.ClassNotFoundException,這個錯誤發生只在運行時需要加載對應的類不成功,而不是編譯時發生,例如在運行時我們想調用某個類的方法或者訪問這個類的靜態成員的時候,發現這個類不可用,此時Java虛擬機就會拋出NoClassDefFoundError錯誤。在開發過程中,我們給特定的幾個類都加上了靜態成員變量slf4j進行異常日誌的打印。
private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);
但是這個類在動態加載的時候就報出了java.lang.NoClassDefFoundError,當時有點不知所措,經過和同學一下午的排查,並且明確了這個錯誤發生的條件,最終發現問題出在這個成員變量上,出錯的原因,也就是我們的maven少配了slf4j對應的兩個logback的依賴,然後在項目的資源文件目錄加上logback.xml,這個錯誤纔算解決。
2、Class.forName(className,isInitialized,getClassLoader()):書上的isInitialized設置爲false,我也就跟着敲了,但是實際運行當中卻發現加載的四個類有一個類並沒有被初始化,查閱資料發現,class.forName(className)調用的上述方法設置的isInitialized參數爲true,所以class.forName(className)動態加載的類是一定會被初始化的,而inInitialized爲false雖然整個加載的執行速度會變快,但是類不一定會被初始化,比如上面提到的IocHelper,若設置爲false他就會初始化失敗,但是給他增加一個沒有用的成員方法,他又會初始化成功。