文章目录
1. Spring IOC、DI、MVC 的原理图
2. 开始构建自己的SpringMVC
项目目录
2.1 文件配置
web.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>myspring</servlet-name>
<servlet-class>com.gz.myspring.MyDispatchServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>myspring</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
application.properties 文件
scanPackge=com.gz
2.2 先用后写
HelloController.java 文件
package com.gz.controller;
import com.gz.myspring.annotation.MyAutowried;
import com.gz.myspring.annotation.MyController;
import com.gz.myspring.annotation.MyRequestMapping;
import com.gz.myspring.annotation.MyRequestParam;
import com.gz.service.HelloService;
/**
* @Author guozhi
* @Date 2019/7/14 16:36
* @Description TODO
*/
@MyController
@MyRequestMapping("/myspring")
public class HelloController {
@MyAutowried
private HelloService helloService;
@MyRequestMapping("/")
public String hello(){
return helloService.Hello("mySpring");
}
@MyRequestMapping("/hello")
public String hello2(@MyRequestParam("name")String name, @MyRequestParam("age") String age){
return helloService.Hello(name + " your age is " + age);
}
}
HelloService.java 文件
package com.gz.service;
/**
* @Author guozhi
* @Date 2019/7/14 16:27
* @Description TODO
*/
public interface HelloService {
String Hello(String name);
}
HelloServiceImpl.java 文件
package com.gz.service.impl;
import com.gz.myspring.annotation.MyService;
import com.gz.service.HelloService;
/**
* @Author guozhi
* @Date 2019/7/14 16:27
* @Description TODO
*/
@MyService
public class HelloServiceImpl implements HelloService {
public String Hello(String name) {
return "hello "+name;
}
}
2.3 注解
MyAutowried.java 文件
package com.gz.myspring.annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowried {
String value() default "";
}
MyController.java 文件
package com.gz.myspring.annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowried {
String value() default "";
}
MyRequestMapping.java 文件
package com.gz.myspring.annotation;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowried {
String value() default "";
}
MyRequestParam.java 文件
package com.gz.myspring.annotation;
import java.lang.annotation.*;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value() default "";
}
MyService.java 文件
package com.gz.myspring.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
2.4 DispatchServlet的init() 初始化
// 上下文的配置文件
private Properties properties = new Properties();
// 扫描指定包下的类名
private List<String> classNames = new ArrayList<String>();
// ioc 容器
private Map<String, Object> ioc = new HashMap<String, Object>();
// handleMapping url映射
private Map<String, Method> handleMapping = new HashMap<String, Method>();
@Override
public void init(ServletConfig config) throws ServletException {
// 1. 加载配置文件
doLoadProperties(config);
// 2. 扫描相关的类
doScanClass(properties.getProperty("scanPackge"));
// 3.初始化相关的类,并放入ioc容器
doInstance();
// 4. DI 操作
doAutoried();
// 5 handleMapping 的初始化
initHandleMapping();
System.out.println("mySpring started !");
}
/**
* 初始化handleMapping 映射
*/
private void initHandleMapping() {
if (ioc.isEmpty()) return;
for (Map.Entry entry : ioc.entrySet()){
Class clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(MyController.class)) continue;
StringBuilder baseUrl = new StringBuilder("/");
if (clazz.isAnnotationPresent(MyRequestMapping.class)){
MyRequestMapping myRequestMapping = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class);
baseUrl.append(myRequestMapping.value());
}
Method [] methods = entry.getValue().getClass().getMethods();
for (Method method : methods){
if (!method.isAnnotationPresent(MyRequestMapping.class))
continue;
MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
StringBuilder stringBuilder = new StringBuilder(baseUrl);
stringBuilder.append("/");
stringBuilder.append(myRequestMapping.value());
handleMapping.put(stringBuilder.toString().replaceAll("/+", "/"), method);
}
}
}
/**
* 进行Di操作
* 对 被@Autowried注解了的变量 进行初始化
*/
private void doAutoried() {
if (ioc.isEmpty()){
return;
}
for (Map.Entry<String, Object> entry: ioc.entrySet()){
Field[] fields = entry.getValue().getClass().getDeclaredFields();
System.out.println(fields.length);
for (Field field : fields){
// 过滤没有被 @Autowried 注解的变量
if (!field.isAnnotationPresent(MyAutowried.class)) continue;
String beanName = field.getAnnotation(MyAutowried.class).value();
if ("".equals(beanName)){
beanName = toLowerFirst(field.getType().getSimpleName());
}
field.setAccessible(true); // 强制授权
try {
field.set(entry.getValue(), ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 对扫描到的类分析,如果有 MyController 或者 MyService 注解则进行实例化,并放入ioc容器中
*/
private void doInstance(){
if (classNames.isEmpty()) {
return;
}
for (String name: classNames){
try{
Class clazz = Class.forName(name);
Object instance = clazz.newInstance();
if (clazz.isAnnotationPresent(MyController.class)){
// 将类名的首字母小写后,作为key, 其实例作为value,放入ioc容器中
ioc.put(toLowerFirst(clazz.getSimpleName()), instance);
}else if (clazz.isAnnotationPresent(MyService.class)){
/*
* 首先判断 注解的value 值是否为空
* 如果不为空,则将其作为类名,实例化后加入到ioc容器中
* 如果为空: 1 获取这个类实现的所有接口,将接口名称首字母小写后,作为类名,加入到ioc容器中
* 2 将这个类的首字母小写后,作为类名key 加入到ioc容器中
* */
MyService myService = (MyService) clazz.getAnnotation(MyService.class);
String beanName = myService.value();
if ("".equals(beanName.trim())){
for (Class<?> i : clazz.getInterfaces()){
String iname = toLowerFirst(i.getSimpleName());
if (ioc.containsKey(iname)){
throw new Exception(String.format("beanName: %s is exists!", iname));
}
ioc.put(iname, instance);
}
beanName = toLowerFirst(clazz.getSimpleName());
}
ioc.put(beanName, instance);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 首字母小写
* @param string
* @return
*/
private String toLowerFirst(String string){
char [] chars = string.toCharArray();
chars[0] += 32;
return new String(chars);
}
/**
* 扫描包路径下的所有 .class 文件
* @param packagePath 包路径
*/
private void doScanClass(String packagePath) {
URL url = this.getClass().getClassLoader().getResource("/" + packagePath.replaceAll("\\.", "/"));
for (File file : new File(url.getFile()).listFiles()){
if (file.isDirectory()){
this.doScanClass(packagePath + "." + file.getName());
}else if (file.getName().contains(".class")){
classNames.add((packagePath + "." + file.getName().replaceAll(".class", "")).trim());
}
}
}
/**
* 加载配置文件
* @param config 配置文件
*/
private void doLoadProperties(ServletConfig config) {
String contextConfigLocation = config.getInitParameter("contextConfigLocation").replace("classpath:", "");
//从项目下取得配置文件的输入流
InputStream in = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (in != null)
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.5 doPost()进行映射处理
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatch(req, resp);
}
/**
* 将请求映射到指定的方法
* @param req
* @param resp
* @throws IOException
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
if (handleMapping.isEmpty())
return;
String contextPath = req.getContextPath();
String url = req.getRequestURI().replace(contextPath, "").replaceAll("/+", "/");
if (!handleMapping.containsKey(url)){
resp.getWriter().write("404 not found url " + url);
return;
}
Method method = handleMapping.get(url);
Parameter[] me_parameter = method.getParameters();
List param_objs = new ArrayList();
for (Parameter p : me_parameter){
if (p.isAnnotationPresent((MyRequestParam.class))){
param_objs.add(req.getParameterMap().get(p.getAnnotation(MyRequestParam.class).value())[0]);
}
}
String beanName = toLowerFirst(method.getDeclaringClass().getSimpleName());
try {
resp.getWriter().write((String) method.invoke(ioc.get(beanName), param_objs.toArray()));
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
2.6 完整的 MyDispatchServlet 代码
package com.gz.myspring;
import com.gz.myspring.annotation.*;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.*;
/**
* @Author guozhi
* @Date 2019/7/14 16:19
* @Description TODO
*/
public class MyDispatchServlet extends HttpServlet {
// 上下文的配置文件
private Properties properties = new Properties();
// 扫描指定包下的类名
private List<String> classNames = new ArrayList<String>();
// ioc 容器
private Map<String, Object> ioc = new HashMap<String, Object>();
// handleMapping url映射
private Map<String, Method> handleMapping = new HashMap<String, Method>();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatch(req, resp);
}
/**
* 将请求映射到指定的方法
* @param req
* @param resp
* @throws IOException
*/
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
if (handleMapping.isEmpty())
return;
String contextPath = req.getContextPath();
String url = req.getRequestURI().replace(contextPath, "").replaceAll("/+", "/");
if (!handleMapping.containsKey(url)){
resp.getWriter().write("404 not found url " + url);
return;
}
Method method = handleMapping.get(url);
Parameter[] me_parameter = method.getParameters();
List param_objs = new ArrayList();
for (Parameter p : me_parameter){
if (p.isAnnotationPresent((MyRequestParam.class))){
param_objs.add(req.getParameterMap().get(p.getAnnotation(MyRequestParam.class).value())[0]);
}
}
String beanName = toLowerFirst(method.getDeclaringClass().getSimpleName());
try {
resp.getWriter().write((String) method.invoke(ioc.get(beanName), param_objs.toArray()));
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
public void init(ServletConfig config) throws ServletException {
// 1. 加载配置文件
doLoadProperties(config);
// 2. 扫描相关的类
doScanClass(properties.getProperty("scanPackge"));
// 3.初始化相关的类,并放入ioc容器
doInstance();
// 4. DI 操作
doAutoried();
// 5 handleMapping 的初始化
initHandleMapping();
System.out.println("mySpring started !");
}
/**
* 初始化handleMapping 映射
*/
private void initHandleMapping() {
if (ioc.isEmpty()) return;
for (Map.Entry entry : ioc.entrySet()){
Class clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(MyController.class)) continue;
StringBuilder baseUrl = new StringBuilder("/");
if (clazz.isAnnotationPresent(MyRequestMapping.class)){
MyRequestMapping myRequestMapping = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class);
baseUrl.append(myRequestMapping.value());
}
Method [] methods = entry.getValue().getClass().getMethods();
for (Method method : methods){
if (!method.isAnnotationPresent(MyRequestMapping.class))
continue;
MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
StringBuilder stringBuilder = new StringBuilder(baseUrl);
stringBuilder.append("/");
stringBuilder.append(myRequestMapping.value());
handleMapping.put(stringBuilder.toString().replaceAll("/+", "/"), method);
}
}
}
/**
* 进行Di操作
* 对 被@Autowried注解了的变量 进行初始化
*/
private void doAutoried() {
if (ioc.isEmpty()){
return;
}
for (Map.Entry<String, Object> entry: ioc.entrySet()){
Field[] fields = entry.getValue().getClass().getDeclaredFields();
System.out.println(fields.length);
for (Field field : fields){
// 过滤没有被 @Autowried 注解的变量
if (!field.isAnnotationPresent(MyAutowried.class)) continue;
String beanName = field.getAnnotation(MyAutowried.class).value();
if ("".equals(beanName)){
beanName = toLowerFirst(field.getType().getSimpleName());
}
field.setAccessible(true); // 强制授权
try {
field.set(entry.getValue(), ioc.get(beanName));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
/**
* 对扫描到的类分析,如果有 MyController 或者 MyService 注解则进行实例化,并放入ioc容器中
*/
private void doInstance(){
if (classNames.isEmpty()) {
return;
}
for (String name: classNames){
try{
Class clazz = Class.forName(name);
Object instance = clazz.newInstance();
if (clazz.isAnnotationPresent(MyController.class)){
// 将类名的首字母小写后,作为key, 其实例作为value,放入ioc容器中
ioc.put(toLowerFirst(clazz.getSimpleName()), instance);
}else if (clazz.isAnnotationPresent(MyService.class)){
/*
* 首先判断 注解的value 值是否为空
* 如果不为空,则将其作为类名,实例化后加入到ioc容器中
* 如果为空: 1 获取这个类实现的所有接口,将接口名称首字母小写后,作为类名,加入到ioc容器中
* 2 将这个类的首字母小写后,作为类名key 加入到ioc容器中
* */
MyService myService = (MyService) clazz.getAnnotation(MyService.class);
String beanName = myService.value();
if ("".equals(beanName.trim())){
for (Class<?> i : clazz.getInterfaces()){
String iname = toLowerFirst(i.getSimpleName());
if (ioc.containsKey(iname)){
throw new Exception(String.format("beanName: %s is exists!", iname));
}
ioc.put(iname, instance);
}
beanName = toLowerFirst(clazz.getSimpleName());
}
ioc.put(beanName, instance);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 首字母小写
* @param string
* @return
*/
private String toLowerFirst(String string){
char [] chars = string.toCharArray();
chars[0] += 32;
return new String(chars);
}
/**
* 扫描包路径下的所有 .class 文件
* @param packagePath 包路径
*/
private void doScanClass(String packagePath) {
URL url = this.getClass().getClassLoader().getResource("/" + packagePath.replaceAll("\\.", "/"));
for (File file : new File(url.getFile()).listFiles()){
if (file.isDirectory()){
this.doScanClass(packagePath + "." + file.getName());
}else if (file.getName().contains(".class")){
classNames.add((packagePath + "." + file.getName().replaceAll(".class", "")).trim());
}
}
}
/**
* 加载配置文件
* @param config 配置文件
*/
private void doLoadProperties(ServletConfig config) {
String contextConfigLocation = config.getInitParameter("contextConfigLocation").replace("classpath:", "");
//从项目下取得配置文件的输入流
InputStream in = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (in != null)
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3. 运行效果
4. 遗留问题
- 在对请求的URL进行method映射时,只能将参数作为String类型处理,即 @MyRequestParam 注解的形参只能是String类型,这个问题是出在 method.invoke(ioc.get(beanName), objects) 中,其中objects是需要给方法传入的参数,是 Object[ ] 类型,预想的解决在从request中获取请求参数时,判断参数类型并进行强制转型,然后再放入 Objects[ ] 中
- 目前这个项目只对 doGet() 与doPost() 方法进行了处理,其他 put、delete等, 需要额外加代码,预想的解决办法就是像 doGet()中写的那样, 直接调用doPost()