手写spring mvc-了解spring bean,ioc 容器,spring mvc原理,超级简单(附源码)

修订日期 内容
2021-3-7 初稿

简介

本章目的:

  1. 了解spring mvc中一个请求是如何到达Controller的方法的
  2. 了解spring ioc容器的创建过程
    先看一段Spring的代码
@Controller
@RequestMapping("/order")
public class OrderController {
    
    

    @Autowired
    OrderService orderService;
    
    @RequestMapping(value = "/send")
    @ResponseBody
    public String send(){
    
    
    	return orderService.send();
    }

相信这段代码大家已经非常熟悉了,实现了一个请求在前端(浏览器)调用http://xxxx/order/send,则会进入到send()方法中调用orderService的方法并返回客户端的过程。
这里面有两个问题:

  1. 请求’http://xxxx/order/send’是如何进入到使用了注解@RequestMapping(value = "/send")send()方法中的?
  2. 使用了@Autowired的类OrderService orderService并没有看见创建对象(new OrderService),那么这个orderService.send()是如何能够被调用?

简单实现(手撸一个简单的mvc)

下面介绍思路与实现步骤,项目完整源码地址:https://github.com/jurnea/my-mvc/tree/simple

1.先创建一个普通的web项目,引入相关maven依赖

<!-- web项目必须添加-->
<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
  </dependency>
<!-- 添加日志依赖logback(不需要输出日志可以不用添加该依赖)-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>
  1. 模仿Spring代码,编写我们自己的注解
    这里我们先使用如下注解
  • Autowire
  • Controller
  • RequestMapping
  • Service

分别创建注解类

// 在自己的项目中分别创建如下4个注解
@Target({
    
    ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowire {
    
    
}

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
    
    
}

@Target({
    
    ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    
    
    String value() default "";
}

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
    
    
}

  1. 创建自己的ControllerService类并使用我们刚刚创建的自己的注解
// 这里面的注解都是我们自己定义的
@Controller
@RequestMapping(value = "/order")
public class OrderController {
    
    

    private Logger logger = LoggerFactory.getLogger(OrderController.class);

    @Autowire
    private OrderService orderService;


    @RequestMapping(value = "/query")
    public Object query(){
    
    
        logger.info("request url invoked controller method");
        String result = orderService.queryOrder();
        return result;
    }

// 这里面的注解都是我们自己定义的
@Service
public class OrderService {
    
    

    public String queryOrder(){
    
    
        return "order service 订单查询成功...";
    }
}

  1. 创建我们的核心处理Servlet,并重写init、doGet、doPost方法
    spring的ioc容器创建类,与请求转发将在该类实现,具体实现细节情况核心实现
public class DispatcherServlet extends HttpServlet{
    
    
	@Override
    public void init(ServletConfig config) throws ServletException {
    
    
    }
	@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
    }
	@Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        super.doPost(req, resp);
    }
}
  1. web.xml中配置自定义的servlet,将所有的请求都转发到我们的DispatcherServlet
<servlet>
      <servlet-name>my-mvc</servlet-name>
      <!-- 上面直接定义的DispatcherServlet-->
      <servlet-class>org.chenglj.mvc.servlet.DispatcherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
      <servlet-name>my-mvc</servlet-name>
      <!--拦截所有的请求-->
      <url-pattern>/</url-pattern>
  </servlet-mapping>

DispatcherServlet核心实现

思路

  1. 我们应该扫描包下面所有使用了@Controller@Service的类,并创建实列对象
  2. Controller中使用的@Autowire的属性赋值
  3. 对使用了@RequestMapping的类与方法与前端请求对应起来,使得请求能够一一对应的Controller方法上

实现

0. 初始化

@Override
 public void init(ServletConfig config) throws ServletException {
    
    
      super.init(config);
      //1。扫描包
      scanPackage("");
      //2.创建对象
      newInstance();
      //3.添加依赖注入
      addDependency();
     //4.映射url与方法
      addMapping();

  }

1. 扫描所有的包

	//收集项目下所有的Class-》目的,找出含有`@Controller`、`@Service`的类,并为他们创建对象
    List<String> classNames = new ArrayList<>();

	public void scanPackage(String backPackage) {
    
    
	        logger.info("扫描包:backPackage->{}",backPackage);
	        String path = DispatcherServlet.class.getClassLoader().getResource(backPackage).getPath();
	        File filePath = new File(path);
	        File[] files = filePath.listFiles();
	        for (File file : files) {
    
    
	            if(file.isDirectory()){
    
    
	                scanPackage(backPackage+file.getName()+"/");
	            } else {
    
    
	                if(!file.getName().endsWith(".class")){
    
    
	                    continue;
	                }
	                //不是目录,加载完整类名 simpleName -> OrderController
	                String simpleName = file.getName().substring(0,file.getName().lastIndexOf("."));
	                String className = backPackage.replaceAll("\\/",".")+simpleName;
	                classNames.add(className);
	
	            }
	
	        }
	    }

2. 为Controller@Service创建实列对象,并存入Bean容器

// 创建容器-手机实列对象bean 存储:full className -> instance

private Map<String,Object> beans = new ConcurrentHashMap();

private void newInstance() {
    
    
        try {
    
    
            for (String className : classNames) {
    
    

                try {
    
    
                    Class<?> clazz = Class.forName(className);
                    if(clazz.isAnnotationPresent(Controller.class)
                            || clazz.isAnnotationPresent(Service.class)){
    
    
                        Object obj = clazz.newInstance();
                        beans.put(clazz.getName(),obj);
                        logger.info("init bean :{}",clazz.getName());
                    }
                } catch (ClassNotFoundException e) {
    
    
                    e.printStackTrace();
                } catch (InstantiationException e) {
    
    
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
    
    
                    e.printStackTrace();
                }
            }
            // 初始化bean完成后className销毁
            classNames.clear();
            classNames = null;
        } catch (Exception e) {
    
    
            logger.error("new instance error",e);
        }
    }

3. 对使用的@Autowire添加依赖

private void addDependency() {
    
    

        logger.info("staring add bean dependency");
        try {
    
    
            for (Map.Entry<String, Object> entry : beans.entrySet()) {
    
    
                //获取所有的属性field
                Field[] fields = entry.getValue().getClass().getDeclaredFields();
                for (Field field : fields) {
    
    
                    Autowire annotation = field.getAnnotation(Autowire.class);
                    if(annotation != null){
    
    
                        field.setAccessible(true);
                        String className = field.getType().getName();
                        field.set(entry.getValue(),beans.get(className));
                    }
                }
            }
        } catch (Exception e) {
    
    
            logger.error("add bean dependency error !",e);
        }
    }

4.添加映射

对Controller中类上与方法上的@RequestMapping添加url映射关系

	//收集映射关系 url-> (className,Method)
    private Map<String, Mapping> mappings = new ConcurrentHashMap<>();

	private void addMapping() {
    
    
        try {
    
    
            for (Map.Entry<String, Object> entry : beans.entrySet()) {
    
    
                //获取所有的属性
                Class<?> beanClazz = entry.getValue().getClass();
                boolean controllerPresent = beanClazz.isAnnotationPresent(Controller.class);
                RequestMapping requestMappingAnnotation = beanClazz.getDeclaredAnnotation(RequestMapping.class);
                //既有Controller注解也有RequestMapping注解
                if(controllerPresent && requestMappingAnnotation != null){
    
    
                    //类上的注解@RequestMapping中的值
                    String classMappingValue = requestMappingAnnotation.value();
                    Method[] declaredMethods = beanClazz.getDeclaredMethods();
                    for (Method method : declaredMethods) {
    
    
                        RequestMapping annotation = method.getAnnotation(RequestMapping.class);
                        if(annotation == null){
    
    
                            continue;
                        }
                        //方法中@RequestMapping的值
                        String methodMappingValue = annotation.value();

                        String url = classMappingValue+methodMappingValue;
                        mappings.put(url,new Mapping(entry.getKey(),method));
                        logger.info("初始化[{}}] -> {}",url,method.getName());
                    }


                }
            }
        } catch (Exception e) {
    
    
            logger.error("add mapping error",e);
        }
    }

// 用于存放className 与 Method
class Mapping {
    
    

    private String className;

    private Method method;

    public Mapping(String className, Method method) {
    
    
        this.className = className;
        this.method = method;
    }
    // getting/setting
}

请求转发(完成)

doGet/doPost方法转发请求

	@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        String requestURI = req.getRequestURI();
        // 项目名
        String contextPath = req.getContextPath();
        String url = requestURI.replaceAll(contextPath, "");
        logger.info("request url [{}]",url);
        Mapping mapping = mappings.get(url);
        if( mapping !=null){
    
    
            Object controllerObj = beans.get(mapping.getClassName());
            Method method = mapping.getMethod();
            try {
    
    
                Object invokeResult = method.invoke(controllerObj);
                resp.setContentType("application/json;charset=utf-8");
                resp.getWriter().write((String)invokeResult);
                logger.info("返回内容:{}",invokeResult);
            } catch (IllegalAccessException e) {
    
    
                e.printStackTrace();
            } catch (InvocationTargetException e) {
    
    
                e.printStackTrace();
            }

        }


    }

这样一个简单的Spring mvc项目就完成了,启动项目使用http://xxxx/order/query请求访问吧。
本项目简单实现源码:https://github.com/jurnea/my-mvc/tree/simple

优化

上面的项目已经实现了一个简单的原理介绍,当然里面还有很多不完善的地方,比如

  1. 前端传入参数
  2. 大多Service都是通过接口编程
  3. 有些请求是要转到一个页面的

  4. 大家有兴趣可以继续完善该项目

猜你喜欢

转载自blog.csdn.net/weixin_48470176/article/details/114477037