第二部分紧接第一部分。我们在第一部分中讲到了这个mini框架的几个注解,嵌入的tomcat服务器,以及执行类扫描和DispatcherSevlet。需要的话可以根据下面的链接回看一下~
友情链接:
手写简易SpringMVC框架(一):注解、内嵌Tomcat、类扫描
手写简易SpringMVC框架(三):极其简单的应用
1.6 BeanFactory实例化bean并管理
public class BeanFactory {
private static Map<Class<?>, Object> classToBean = new ConcurrentHashMap<>();
public static Object getBean(Class<?> cls) {
return classToBean.get(cls);
}
public static void initBean(List<Class<?>> classList) throws Exception {
//xxxxxxxxxxxxxxx
}
private static boolean notNeedCreateBean(Class<?> aClass) {
//xxxxxxxxxxxxxxxxx
}
private static boolean finishCreateBean(Class<?> aClass, Map<Class<?>, Object> notCreateFinishedBeanMap)
throws IllegalAccessException, InstantiationException {
//xxxxxxxxxxxxxxx
}
}
classToBean
记录了每个类型class及其对应的实例对象,相当于容器。getBean
方法会返回对应的bean实例。initBean
方法(诶?好像在哪见过??对了,是在手写简易SpringMVC框架(一)中1.2节的第3个步骤中出现过,有印象了吗)负责实例化bean并将其放入classToBean
容器中。我们重点来看一下initBean(List<Class<?>> classList)
方法。
initBean
方法中需要进行bean的实例化,1 当实例化一个bean的时候,需要首先实例化它所依赖的bean,例如A中有个字段指向B实例,那么应当首先实例化B,然后在实例化A。2 另外还需要考虑到循环依赖的问题,简单说就是A依赖于B但是B有依赖于A,结果这就导致没法实例化这俩实例。结合这两点,我们的程序如下(代码中做了注释,可以直接看注释,仔细体味一下这段代码):
public static void initBean(List<Class<?>> classList) throws Exception {
//toCreate存放了所有类的类型
List<Class<?>> toCreate = new ArrayList<>(classList);
//用于存放那些还没创建成功的bean,比如按照顺序先要实例化A,但是A依赖B,B却没有被实例化,那么A
//自然就没法实例化,所以会把A对象暂时存放在这里。 class<A> --> 未创建完成的A对象
Map<Class<?>, Object> notCreateFinishedBeanMap = new HashMap<>();
while (toCreate.size() != 0) {
//在这一轮创建之前,先记录列表里面还有多少个类
int size = toCreate.size();
//下面就是遍历toCreate中的每一个class,如果它不需要实例化或者能对其实例化成功就将其移除出队列,
//不行的话就保留并添加到notCreateFinishedBeanMap中。
//注意这里涉及到删除操作,所以就不能简单的使用for循环了。
int i = 0;
while (toCreate.size() > i) {
if (notNeedCreateBean(toCreate.get(i)) || finishCreateBean(toCreate.get(i), notCreateFinishedBeanMap)) {
toCreate.remove(i);
} else {
// 这个i记录了,没有被实例化的class的数量
i++;
}
}
//这一轮创建完成后,查看是否列表中的元素有减少,减少了说明这一轮有bean创建成功,
//没有减少说明没有bean可以被成功创建 (也就是出现了cycle dependency)
if (toCreate.size() == size) {
throw new Exception("cycle dependency");
}
}
}
上面这段代码的最终结束循环的条件toCreate队列为空,也就是不需要实例化的对象移除出了队列,然后需要实例化的对象全部实例化完成。
notNeedCreateBean
方法十分简单:class没有注解的话,我们就不需要实例化这些对象
private static boolean notNeedCreateBean(Class<?> aClass) {
return !(aClass.isAnnotationPresent(MyBean.class) || aClass.isAnnotationPresent(MyController.class));
}
finishCreateBean
方法的主要思路是:如果能够实例化当前对象,则完成实例化并添加到classToBean容器中,返回true。 如果当前类实例依赖于其他对象但是此对象还没有出现在容器中,那么当前类实例添加到notCreateFinishedBeanMap,返回false,等待后续创建完成。
private static boolean finishCreateBean(Class<?> aClass, Map<Class<?>, Object> notCreateFinishedBeanMap)
throws IllegalAccessException, InstantiationException {
//首先检查一下这个类的对象是否出存在于notCreateFinishedBeanMap中
Object instance = notCreateFinishedBeanMap.get(aClass);
if (instance == null) {
instance = aClass.newInstance();
}
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(MyAutoWired.class)) {
Object bean = BeanFactory.getBean(field.getType());
//判断容器中是否已有它所依赖的实例
if (bean == null) {
//there not exist the dependent bean in bean factory, then we can not create bean for this aClass
//put the not completely-created bean(instance) into notCreateFinishedBeanMap
notCreateFinishedBeanMap.put(aClass, instance);
//只要出现依赖的bean还未存在,那么就直接返回false
return false;
}
//如果已经存在它所依赖的bean,那么就注入它
field.setAccessible(true);
field.set(instance, bean);
}
}
//将这个类实例化成功的实例对象放入容器
classToBean.put(aClass, instance);
return true;
}
1.7 MappingHandler和MappingHandlerManager
至此我们的程序还差一部分,那就是目前系统还不知道哪个请求uri应该映射到哪个方法中去执行呢。还记得1.2中提到的springmvc的入口类的执行流程吧,这个十分重要,记得回看一下。在第4步:
MyMappingHandlerManager.resolveMappingHandler(classList);
这里面就建立了请求uri和对应的方法之间的映射关系。我们先点进MyMappingHandlerManager
看一看。
public class MyMappingHandlerManager {
/**
* MappingHandler列表,一个MappingHandler可以认为是一对 请求uri-->执行方法 的封装
*/
public static List<MyMappingHandler> mappingHandlerList = new ArrayList<>();
/**
* 在解析mapping的时候,只会讲controller进行解析
* @param classList 是类扫描时扫描到的所有的类。
*/
public static void resolveMappingHandler(List<Class<?>> classList) {
for (Class<?> aClass : classList) {
if (aClass.isAnnotationPresent(MyController.class)) {
parseMappingHandlerFromController(aClass);
}
}
}
/**
*
* @param controllerClass
*/
private static void parseMappingHandlerFromController(Class<?> controllerClass) {
// get all methods
Method[] methods = controllerClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MyRequestMapping.class)) {
//获取到当前方法上@MyRequestMapping注解中的请求uri
String mappingUri = method.getDeclaredAnnotation(MyRequestMapping.class).value();
List<String> requestParamList = new LinkedList<>();
for (Parameter parameter : method.getParameters()) {
if (parameter.isAnnotationPresent(MyRequestParam.class)) {
//获取到当前方法中@MyRequestParam所需要的请求参数的名字,并将其添加到当前方法的请求参数列表中
requestParamList.add(parameter.getDeclaredAnnotation(MyRequestParam.class).value());
} else {
//如果没有用@MyRequestParam注解,那使用方法参数的名字
requestParamList.add(parameter.getName());
}
}
String[] requestParams = requestParamList.toArray(new String[requestParamList.size()]);
//对每一个uri和method的映射封装一个MyMappingHandler对象(其中还包括了方法所述的controller以及方法需要的参数名列表),
// 并放入mappingHandlerList中。
MyMappingHandler mappingHandler = new MyMappingHandler(mappingUri, method, controllerClass, requestParams);
mappingHandlerList.add(mappingHandler);
}
}
}
}
好的,现在映射关系也已经建立好了,但是还差一丢丢,大家还记得当时讲MyDispatcherServlet的时候,我提到了一句,它只负责分发请求,不负责具体执行的,那么到底是谁在执行请求呢,
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String requestURI = req.getRequestURI();
for (MyMappingHandler mappingHandler : MyMappingHandlerManager.mappingHandlerList) {
if(requestURI.equals(mappingHandler.getUri())){
try {
mappingHandler.handle(req, resp);
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
break;
}
}
}
没错,就是这个MyMappingHandler
这个玩意儿。也就是说它除了负责建立uri和method的映射(mapping)之外,还负责执行请求(handler),大概这也就是为啥给他起了名字叫MappingHandler吧~(我猜的)。那我们就不得不来看一看这个MyMappingHandler
了。
public class MyMappingHandler {
private String uri;
private Method method;
private Class<?> controllerClass;
private String[] requestParams;
public MyMappingHandler(String uri, Method method, Class<?> controllerClass, String[] requestParams) {
this.uri = uri;
this.method = method;
this.controllerClass = controllerClass;
this.requestParams = requestParams;
}
public String getUri() {
return uri;
}
public Method getMethod() {
return method;
}
public Class<?> getControllerClass() {
return controllerClass;
}
public String[] getRequestParams() {
return requestParams;
}
public void handle(HttpServletRequest req, HttpServletResponse resp)
throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
String[] args = new String[this.requestParams.length];
for (int i = 0; i < args.length; i++) {
//根据方法需要的参数的名称去从request中获取对应的参数值
args[i] = req.getParameter(requestParams[i]);
}
Object controller = BeanFactory.getBean(controllerClass);
//反射的方式执行方法时需要方法所处的对象实例,也就是我们已经创建好的Controller bean
Object result = this.method.invoke(controller, (Object[]) args);
String resultString = result.toString();
//将结果返回。这里返回的方式也很简单,直接写入字符串
resp.getWriter().print(resultString);
}
}
2 梳理一下这个简单框架的执行流程
让我们再来看一下MVCApplication
这个入口类的执行流程,唉,我都已经不知道多少次提起它了,应该就结束了了吧~
public class MVCApplication {
public static void run(Class<?> cls) {
System.out.println("=============================start!!!!==========================");
TomcatServer tomcatServer = new TomcatServer();
try {
tomcatServer.startServer();
List<Class<?>> classList = ClassScanner.scanClasses(cls.getPackage().getName());
BeanFactory.initBean(classList);
MyMappingHandlerManager.resolveMappingHandler(classList);
for (MyMappingHandler myMappingHandler : MyMappingHandlerManager.mappingHandlerList) {
System.out.println(myMappingHandler.getUri() + ":" + myMappingHandler.getMethod());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1首先就是要配置(端口,主机名,注册servlet等)并启动tomcat;
2扫描用户的应用程序中的类
3将需要实例化的bean实例化,其中解决了bean的依赖以及注意循环依赖问题。
4MappingHandlerManager
解析请求和method之间的映射,同时解析出每个方法需要哪些参数名称,MappingHandler
对象还具有执行对应的请求的逻辑。
这样整个系统就启动初始化并配置完成了,下面的第三部分,将结合一个十分简单的应用来验证一下这个微小框架的正确性。
最后依然甩一下链接:
·手写简易SpringMVC框架(一):注解、内嵌Tomcat、类扫描
·手写简易SpringMVC框架(三):极其简单的应用