4.1 SpringMVC的大致原理
4.2 步骤
- 先创建一个maven的web工程,引入webapp。
- 引入servlet的依赖,因为mvc就是对原生servlet的封装
- 创建DispatcherServlet类,继承HttpServlet
- 编写springmvc.properties配置文件,用来写mvc的一些配置
- 在web.xml中配置servlet(配置的就是刚刚自己创建的DispatcherServlet类)和servlet-mapping(配置拦截什么url)
- 进行注解的开发,主要是@Controller,@Service,@RequestMapping,@Autowired
- 编写controller类,service类,来用上@Controller,@Service,@RequestMapping,@Autowired等注解。
- 编写DispatcherServlet类的初始化流程:
- 1.加载配置文件 springmvc.properties,把配置文件加载程Propeties 类
- 2.扫描相关的类,扫描注解,把所有的java类的全限定类名存储起来
- 3.初始化bean对象(实现ioc容器,基于注解),通过上一步存的全限定类名,用反射实例化对象并缓存起来
- 4.实现依赖注入,通过遍历ioc容器里存储的类,获取到属性,再判断属性上有没有注解@Autowired,有则利用反射注入该属性的类对象
- 5.构造一个HandlerMapping,将配置好的url和method建立映射关系
- 因为前端请求,然后DispatcherServlet执行doPost方法,通过反射执行method的时候需要传入对象、参数,所以编写一个Handler类来封装这些数据
总结:前端请求经过DispatcherServlet拦截,对请求的url通过HandlerMapping组件去匹配到相应的方法,然后再匹配方法的参数,进行方法执行。
4.3 关键代码:主要是自定义Servlet的初始化流程
加载配置文件,因为web.xml配置了springmvc.properties的路径,获取该路径读取成字节流 ```java private Properties properties = new Properties();
// 加载配置文件 private void doLoadConfig(String configLocation) { // 把配置文件加载到properties当中 InputStream stream = this.getClass().getClassLoader().getResourceAsStream(configLocation); try { properties.load(stream); } catch (IOException e) { e.printStackTrace(); }
}
2. 扫描配置的包所有类,把标有相关注解(controller、service等)的类的全限定类名存储起来。
```java
// 缓存扫描到的类的全限定类名
private List<String> classNames = new ArrayList<>();
// 扫描相关的类,扫描注解 scanPackage:com.atm.demo ->磁盘上的文件夹
private void doScan(String scanPackage) {
// 获取类路径+scanPackage路径=磁盘上的绝对路径
String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath()
+ scanPackage.replace(".", "/");
File pack = new File(scanPackagePath);
// 拿到scanPackage目录的子文件
File[] files = pack.listFiles();
assert files != null;
for (File file : files) {
// 说明是子package
if (file.isDirectory()) {
// 递归
doScan(scanPackage + "." + file.getName());
} else if (file.getName().endsWith(".class")) {
// 说明是个java类,获取全限定类名
String className = scanPackage + "." + file.getName().replaceAll(".class","");
classNames.add(className);
}
}
}
- 初始化Bean对象,加入IOC容器
```java
// ioc容器
private Map
ioc = new HashMap<>();
// 初始化bean对象(实现ioc容器,基于注解) private void doInstance() throws ClassNotFoundException, InstantiationException, IllegalAccessException { if (classNames.size() == 0) { return; } for (String className : classNames) { // 反射创建对象 Class<?> aClass = Class.forName(className); // 先不实例化,区分controller、service,如果是service得判断注解有没有值,有则取该值作 // 为bean的id if (aClass.isAnnotationPresent(AtmController.class)) { // controller的id不做处理,不取value,直接拿类的首字母小写作为id String id = lowerFirst(aClass.getSimpleName()); Object o = aClass.newInstance(); ioc.put(id,o); } else if (aClass.isAnnotationPresent(AtmService.class)) { AtmService annotation = aClass.getAnnotation(AtmService.class); // 获取注解的值 String beanName = annotation.value(); if (!””.equals(beanName.trim())) { ioc.put(beanName,aClass.newInstance()); } else { beanName = lowerFirst(aClass.getSimpleName()); ioc.put(beanName,aClass.newInstance()); } // service层往往是有接口的,此时再以接口为id,放入一份对象到ioc中 Class<?>[] interfaces = aClass.getInterfaces(); for (Class<?> anInterface : interfaces) { ioc.put(anInterface.getName(),aClass.newInstance()); } } } }
// 首字母小写方法 public String lowerFirst(String str) { char[] chars = str.toCharArray(); if (‘A’ <= chars[0] && ‘Z’ >= chars[0]) { chars[0] += 32; } return String.valueOf(chars); }
4. 实现依赖注入,判断每个bean里的属性,如果有Autowire注解则是需要注入的属性
```java
// 实现依赖注入
private void doAutowired() {
if (ioc.isEmpty()) {
return;
}
// 有对象,进行依赖处理
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
// 获取bean对象中的所有字段
Object value = entry.getValue();
Field[] declaredFields = value.getClass().getDeclaredFields();
for (Field field : declaredFields) {
// 查看对象是否有Autowired的注解
if (field.isAnnotationPresent(AtmAutowired.class)) {
AtmAutowired annotation = field.getAnnotation(AtmAutowired.class);
// 需要注入的bean的id
String beanName = annotation.value();
if ("".equals(beanName.trim())) {
beanName = field.getType().getName();
}
// 开启赋值 开启强制访问
field.setAccessible(true);
try {
// 把该类上的field属性设置为ioc.get(beanName)的值
field.set(value,ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
- 构造一个HandlerMapping,将配置好的url和method建立映射关系 ```java /**
- 构造一个HandlerMapping,将配置好的url和method建立映射关系
- 最关键的环节
*/
private void initHandlerMapping() {
if (ioc.isEmpty()) {
} for (Map.Entryreturn;
entry : ioc.entrySet()) {
} } ```// 遍历ioc,获取当前遍历对象的class对象
Class<?> aClass = entry.getValue().getClass();
// 如果不是controller类就没必要处理
if (!aClass.isAnnotationPresent(AtmController.class)) {
continue;
}
String baseUrl = "";
if (aClass.isAnnotationPresent(AtmRequestMapping.class)) {
// 如果类上标了注解@RequestMapping,那么url的前缀就是该注解填写的值
AtmRequestMapping annotation = aClass.getAnnotation(AtmRequestMapping.class);
baseUrl = annotation.value();
}
Method[] methods = aClass.getMethods();
for (Method method : methods) {
// 如果类中的方法标识注解@RequestMapping就进行处理
if (method.isAnnotationPresent(AtmRequestMapping.class)) {
AtmRequestMapping annotation = method.getAnnotation(AtmRequestMapping.class);
String methodUrl = annotation.value();
String url = baseUrl + methodUrl;
// 把method的所有信息以及url封装成Handler
Handler handler = new Handler(entry.getValue(),method, Pattern.compile(url));
// 处理计算参数位置信息
Parameter[] parameters = method.getParameters();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
// 如果参数是request或者response对象,则参数名称写HttpServletRequest和HttpServletResponse
handler.getParamIndexMapping().put(parameter.getType().getSimpleName(),i);
} else {
handler.getParamIndexMapping().put(parameter.getName(),i);
}
}
handlerMapping.add(handler);
}
}
方法执行的时候,都会经过自定义DisapatcherServlet的doGet或者doPost方法。 ```java @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 真正处理请求:根据url找到对应的method方法,进行调用 // 获取url // String requestUrl = req.getRequestURI(); // 反射调用 需要传入对象、参数 // method.invoke() // 根据url获取到当前请求的handler Handler handler = getHandler(req); if (handler == null) {
resp.getWriter().write("404 not found");
return;
} // 参数绑定 Class<?>[] parameterTypes = handler.getMethod().getParameterTypes(); Object[] paramValues = new Object[parameterTypes.length]; // 往参数数组塞值,并且保证参数顺序与方法中一致 Map
parameterMap = req.getParameterMap(); for (Map.Entry param : parameterMap.entrySet()) { String value = StringUtils.join(param.getValue(), ",");
if (handler.getParamIndexMapping().containsKey(param.getKey())) {
// 方法形参确实有该参数
Integer index = handler.getParamIndexMapping().get(param.getKey());
paramValues[index] = value;
}
} Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName()); paramValues[requestIndex] = req;
Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName()); paramValues[responseIndex] = resp;
// 执行handler中的method方法 try {
handler.getMethod().invoke(handler.getController(), paramValues);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
} }
private Handler getHandler(HttpServletRequest req) { if (handlerMapping.isEmpty()) { return null; } String url = req.getRequestURI(); for (Handler handler : handlerMapping) { Matcher matcher = handler.getPattern().matcher(url); if (matcher.matches()) { return handler; } } return null; } ```
4.4 发现的问题
- 在java中接口类是不能被实例化的,因为实例化的对象放在堆内存中,而接口只允许静态属性和方法声明,分别存在代码块和静态数据块,所以开辟一块堆内存来存放接口对象没有意义。