首先安利一波"架构探险——从零开始写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他就会初始化失败,但是给他增加一个没有用的成员方法,他又会初始化成功。