项目地址:https://github.com/gongxianshengjiadexiaohuihui/noobspring
依赖注入
private void doAutowired(){
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>开始注入");
if(ioc.isEmpty()){
return;
}
for(Map.Entry<String,Object> entry : ioc.entrySet()){
Field[] fields = entry.getValue().getClass().getDeclaredFields();
for(Field field : fields){
if(!field.isAnnotationPresent(NBAutowired.class)){
continue;
}
String beanName = field.getAnnotation(NBAutowired.class).value();
if("".equals(beanName)){
beanName = StringUtil.lowerFirstCase(field.getType().getSimpleName());
}
/**
* 在字段是私有变量的时候,也能获得访问权限
*/
field.setAccessible(true);
try {
/**
* 给entry.getValue这个对象的这个字段field赋值(从ioc容器中拿到对应beanName的bean),请注意是对象
*/
field.set(entry.getValue(),ioc.get(beanName));
} catch (Exception e) {
logger.error(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>注入失败");
e.printStackTrace();
throw new RuntimeException("注入失败");
}
}
}
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>注入完成");
}
遍历ioc容器,找到被@NBAutowired标记的字段,开启字段的访问权限,从ioc容器中获取对应类型的实例进行赋值操作。field.getType就是获取类型,来自那个类,将首字母小写就可以对应到ioc中的key
关联映射
private void initHandlerMapping(){
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>开始关联映射");
if(ioc.isEmpty()){
return;
}
for(Map.Entry<String,Object> entry: ioc.entrySet()){
Class<?> clazz = entry.getValue().getClass();
if(!clazz.isAnnotationPresent(NBController.class)){
continue;
}
String baseUrl = "";
/**
* 获得@Controller的父级的url
*/
if(clazz.isAnnotationPresent(NBRequestMapping.class)){
baseUrl = clazz.getAnnotation(NBRequestMapping.class).value();
}
Method[] methods = clazz.getMethods();
for(Method method : methods){
if(!method.isAnnotationPresent(NBRequestMapping.class)){
continue;
}
/**
* 将url和方法关联并存储在handlerMapping中(replaceAll和split支持正则表达式)
*/
String url = ("/" + baseUrl + "/" + method.getAnnotation(NBRequestMapping.class).value()).replaceAll("/+","/");
handlerMapping.put(url,method);
logger.debug("mapped:{},{}",url,method);
}
}
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>关联映射完成");
}
这里主要处理@NBRequestMapping注解标记的类或方法,如果标记的是类,将注解的值作为父路径,添加到路径中,如果修饰的是方法,则在前面的基础上添加路径,并将url和方法关联起来url作为key,method作为value存在类型为hashMap的handlerMapping中。
浏览器请求的方法默认是get方法,我们重写HttpServlet的get方法
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
this.doDispatch(req, resp);
} catch (Exception e) {
resp.getWriter().write("500 Exception,Detail:\r\n" +e.getMessage() + "\r\n" + Arrays.toString(e.getStackTrace()).replaceAll("\\[|\\]","").replaceAll(",\\s","\r\n"));
e.printStackTrace();
}
}
请求转发的实现
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
if(handlerMapping.isEmpty()){
return;
}
String url = req.getRequestURI();
/**
* 返回站点的根目录
* 比如我们访问的地址是http://localhost:8080/demo/data/remove
* demo是我们的项目 对应的contextPath就是http://localhost:8080/demo
*/
String contextPath = req.getContextPath();
url = url.replace(contextPath,"").replaceAll("/+","/");
if(!handlerMapping.containsKey(url)){
resp.getWriter().write("404 Not Found!");
return;
}
/**
* 获取请求参数列表
*/
Map<String,String[]> params = req.getParameterMap();
/**
* 根据url拿到对应的方法
*/
Method method = this.handlerMapping.get(url);
/**
* 获取方法的形参列表
*/
Parameter[] parameters = method.getParameters();
Annotation annotation[][] = method.getParameterAnnotations();
/**
* 保存参数值
*/
Object[] paramValues = new Object[parameters.length];
/**
* 根据形参列表,按顺序获取请求参数列表的值
*/
for(int i =0; i < parameters.length; i++){
if(parameters[i].getType() == HttpServletRequest.class){
paramValues[i] = req;
continue;
}
if(parameters[i].getType() == HttpServletResponse.class){
paramValues[i] = resp;
continue;
}
if(parameters[i].getType() == String.class){
String name = parameters[i].getName();
/**
* 按照@NBRequestParam的value 从请求参数找值
*/
if(annotation[i].length != 0 & annotation[i][0].annotationType() == NBRequestParam.class){
/**
* 类型转换,猜测里面存的值应该是一个注解的泛型
*/
name = ((NBRequestParam)annotation[i][0]).value();
if(!params.containsKey(name)){
throw new RuntimeException("The required parameter does not exist! " + name );
}
}
paramValues[i] = params.get(name)[0];
continue;
}
}
/**
* 通过反射获取实体对象,并通过反射调用方法
*/
String beanName = StringUtil.lowerFirstCase(method.getDeclaringClass().getSimpleName());
method.invoke(this.ioc.get(beanName),paramValues);
}
我们首先拿到请求的路径,去掉站点目录路径,拿到处理后的路径从handlerMapping中得到对应的方法,接着我们仍需要处理最后一个注解@NBRequestParam(目前只支持标记String类型,后续会完善)
主要是对应关系,我们需要把请求的参数,正确的赋给处理请求的方法。
很关键的一点是获取方法的形参列表,这个是有序的一维数组
然后获取方法的注解列表,这个是有序的二维数组。
获取请求参数列表,这个是无序的Map类型。
我的思路是利用有序的形参列表,去遍历,遇到String类型的,就判断其修饰的注解,如果存在@NBRequestParam,就获取值作为key去请求参数列表的找,找到,就保存在参数值中
最后一步通过反射获取实体对象,并调用方法,反射真的很强大,但是发射会影响性能,也不能乱用。
至此,工作已经完成,可以跑起来了
我的项目里有个demo来测试
运行结果