关闭

2 手写实现SpringMVC,第二节:自定义注解及反射赋值

标签: 手写SpringMVC自己实现SpringMVC
1801人阅读 评论(0) 收藏 举报
分类:

还是回到最终要实现的效果。

可以发现,这里面使用了大量的自定义注解,并且还有autuwire的属性也需要被赋值(Spring的IOC功能)。

先来创建自定义注解



注意,根据不同的注解使用的范围来定义@Target,譬如Controller,Service能注解到类,RequestMapping能注解到类和方法,AutoWired只能注解到属性。

Autowired

/**
 * Created by wuwf on 17/6/27.
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    String value() default "";
}

Controller

import java.lang.annotation.*;

/**
 * Created by wuwf on 17/6/27.
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {

    String value() default "";
}
RequestMapping

import java.lang.annotation.*;

/**
 * Created by wuwf on 17/6/27.
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value() default "";
}
RequestParam
import java.lang.annotation.*;

/**
 * Created by wuwf on 17/6/27.
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    String value() default "";

    boolean required() default true;
}
Service

import java.lang.annotation.*;

/**
 * Created by admin on 17/6/27.
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

创建基本功能类


添加2个最简单的Service和实现类,模拟查询和修改。
ModifyService

public interface ModifyService {

    String add(String name, String addr);
    String remove(Integer id);
}
QueryService

public interface QueryService {
    String search(String name);
}
ModifyServiceImpl

import com.tianyalei.mvc.annotation.Service;
import com.tianyalei.mvc.service.ModifyService;

/**
 * Created by wuwf on 17/6/27.
 */
@Service
public class ModifyServiceImpl implements ModifyService {
    @Override
    public String add(String name, String addr) {
        return "invoke add name = " + name + " addr = " + addr;
    }

    @Override
    public String remove(Integer id) {
        return "remove id = " + id;
    }
}

QueryServiceImpl

package com.tianyalei.mvc.service.impl;

import com.tianyalei.mvc.annotation.Service;
import com.tianyalei.mvc.service.QueryService;

/**
 * Created by admin on 17/6/27.
 */
@Service("myQueryService")
public class QueryServiceImpl implements QueryService {
    @Override
    public String search(String name) {
        return "invoke search name = " + name;
    }
}


WebController

package com.tianyalei.mvc.controller;

import com.tianyalei.mvc.annotation.Autowired;
import com.tianyalei.mvc.annotation.Controller;
import com.tianyalei.mvc.annotation.RequestMapping;
import com.tianyalei.mvc.annotation.RequestParam;
import com.tianyalei.mvc.service.ModifyService;
import com.tianyalei.mvc.service.QueryService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created by wuwf on 17/6/28.
 */
@Controller
@RequestMapping("/web")
public class WebController {
    @Autowired("myQueryService")
    private QueryService queryService;
    @Autowired
    private ModifyService modifyService;

    @RequestMapping("/search")
    public void search(@RequestParam("name") String name, HttpServletRequest request, HttpServletResponse response) {
        String result = queryService.search(name);
        out(response, result);
    }

    @RequestMapping("/add")
    public void add(@RequestParam("name") String name,
                    @RequestParam("addr") String addr,
                    HttpServletRequest request, HttpServletResponse response) {
        String result = modifyService.add(name, addr);
        out(response, result);
    }

    @RequestMapping("/remove")
    public void remove(@RequestParam("name") Integer id,
                       HttpServletRequest request, HttpServletResponse response) {
        String result = modifyService.remove(id);
        out(response, result);
    }

    private void out(HttpServletResponse response, String str) {
        try {
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


至此,项目的基本结构就是这样了。下面需要做的就是正文了,如何让上面的注解生效,如何让请求根据地址进到对应的方法。

扫描项目类,并实例化参数

这一步的目的是得到一个类名和类实例的映射对象,如 "webController"->WebController的实例,"searchService"->SearchServiceImpl的实例,类似于Spring的BeanFactory,将对应的实例赋给对应的beanName。譬如,searchService在Controller中被定义了,它并不需要去做new这个对象的处理,而是由我们主动注入进去。前提就是我们需要保存一个beanName->bean对象的映射关系。这种处理就是ioc,也就是早期在spring配置文件application.xml经常配的bean id="XXX" class="XXX".
下面来看怎么实例化参数。
思路就是扫描目录下所有需要被我们的山寨Spring托管的类,并将其实例化,然后保存映射关系。在Spring里就是所有标注了@Component注解的类,需要被托管。我们这里就只处理@Controller和@Service注解的类,然后实例化。

扫描所有需要被映射的类

修改一下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_3_1.xsd"
         version="3.1">

    <servlet>
        <servlet-name>dispatchServlet</servlet-name>
        <servlet-class>com.tianyalei.mvc.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>scanPackage</param-name>
            <param-value>com.tianyalei.mvc</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatchServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>
这里添加了一个init-param,属性名为scanPackage(可任意起名),value为包名。

然后在DispatcherServlet的init方法中,接收并处理。
package com.tianyalei.mvc;

import com.tianyalei.mvc.annotation.Controller;
import com.tianyalei.mvc.annotation.Service;

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.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by wuwf on 17/6/28.
 * 入口Sevlet
 */
public class DispatcherServlet extends HttpServlet {
    private List<String> classNames = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("我是初始化方法");
        scanPackage(config.getInitParameter("scanPackage"));
        System.out.println(classNames);
    }

    /**
     * 扫描包下的所有类
     */
    private void scanPackage(String pkgName) {
        //获取指定的包的实际路径url,将com.tianyalei.mvc变成目录结构com/tianyalei/mvc
        URL url = getClass().getClassLoader().getResource("/" + pkgName.replaceAll("\\.", "/"));
        //转化成file对象
        File dir = new File(url.getFile());
        //递归查询所有的class文件
        for (File file : dir.listFiles()) {
            //如果是目录,就递归目录的下一层,如com.tianyalei.mvc.controller
            if (file.isDirectory()) {
                scanPackage(pkgName + "." + file.getName());
            } else {
                //如果是class文件,并且是需要被spring托管的
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                //举例,className = com.tianyalei.mvc.controller.WebController
                String className = pkgName + "." + file.getName().replace(".class", "");
                //判断是否被Controller或者Service注解了,如果没注解,那么我们就不管它,譬如annotation包和DispatcherServlet类我们就不处理
                try {
                    Class<?> clazz = Class.forName(className);
                    if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)) {
                        classNames.add(className);
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }

            }
        }
    }

    @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 {
        out(resp, "请求到我啦");
    }

    private void out(HttpServletResponse response, String str) {
        try {
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().print(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

这里定义了一个scanPackage的方法,是用于递归处理web.xml定义的需要被扫描的包下的所有等待被接管的类。我们将所有需要被托管的类,保存到一个list中。供下一步实例化时使用。
写完后,执行看一下被扫描的托管类:

这样就取到了所有被Controller和Service标注的类了。

实例化bean

我们取到了所有被托管的类,下一步就是要实例化这些类了,也就是像Spring的ioc一样,按规则给bean注入实例值。
像如果在任何地方定义webController,那么我们就默认给他赋值为WebController的实例,定义了modifyService,那么就默认给它注入ModifyServiceImpl的实例。倘若用户自定义了beanName,那么就给beanName注入值,如果不定义,就走上面默认的。如果在ModifyServiceImpl上写了@Service("abc"),那么我们就保存一个"abc"->ModifyServiceImpl的映射,如果没有定义,就保存一个"modifyService"->ModifyServiceImpl的映射。
保存映射的目的是在将来给AutoWired注解的属性注入值时,好根据beanName来判断该注入什么实例。

添加一个map保存beanName和实例的映射。
private Map<String, Object> instanceMapping = new HashMap<>();
添加doInstance

@Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("我是初始化方法");
        scanPackage(config.getInitParameter("scanPackage"));

        doInstance();

        System.out.println(instanceMapping);
    }

 /**
     * 实例化
     */
    private void doInstance() {
        if (classNames.size() == 0) {
            return;
        }
        //遍历所有的被托管的类,并且实例化
        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                //如果是Controller
                if (clazz.isAnnotationPresent(Controller.class)) {
                    //举例:webController -> new WebController
                    instanceMapping.put(lowerFirstChar(clazz.getSimpleName()), clazz.newInstance());
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    //获取注解上的值
                    Service service = clazz.getAnnotation(Service.class);
                    //举例:QueryServiceImpl上的@Service("myQueryService")
                    String value = service.value();
                    //如果有值,就以该值为key
                    if (!"".equals(value.trim())) {
                        instanceMapping.put(value.trim(), clazz.newInstance());
                    } else {//没值时就用接口的名字首字母小写
                        //获取它的接口
                        Class[] inters = clazz.getInterfaces();
                        //此处简单处理了,假定ServiceImpl只实现了一个接口
                        for (Class c : inters) {
                            //举例 modifyService->new ModifyServiceImpl()
                            instanceMapping.put(lowerFirstChar(c.getSimpleName()), clazz.newInstance());
                            break;
                        }
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

private String lowerFirstChar(String className) {
        char[] chars = className.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

重新启动执行,来看看映射的结果:

可以看到,已经按照我们的规则保存好了beanName和实例之间的映射关系了。

下一步就是处理AutoWired了,真正的ioc赋值。请看下一篇。

2
0
查看评论

3 手写实现SpringMVC,第三节:通过反射给属性和参数注入值

在上一篇已经完成了读取beanName->Object映射关系的功能,这一篇就是把读取到的映射注入到属性中。 在WebController里定义了需要被Autowired的两个Service,myQueryService和modifyService,下面来给他们赋值。 通过反射给属性赋值
  • tianyaleixiaowu
  • tianyaleixiaowu
  • 2017-06-30 12:11
  • 1113

Spring中的反射机制浅析

1  反射的源头Class类 对类的概念已经非常熟悉,有Student类,Person类,还有一个叫Class的类,这是反射的源头。   正常方式:通过完整的类名 > 通过new实例化 > 取得实例化对象 反射方式:实例化对象 > getClass()方法 &g...
  • woshixuye
  • woshixuye
  • 2012-06-28 19:30
  • 17761

SpringMVC拦截器中通过反射得到Controller方法注解时ClassCastException解决方案

错误应用场在Controller中,我们自定义了一个@Auth注解来实现权限控制功能,如:@Auth(verifyLogin=false,verifyURL=false) @RequestMapping("/login") public ModelAndView ...
  • tracker_w
  • tracker_w
  • 2015-06-11 16:19
  • 7025

如何在SpringMVC框架中利用Java反射机制和Javassist实现Java对象、属性、注解的动态创建生成

简单介绍Java的反射原理Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在。Java程序要能够运行,java虚拟机需要事先加载java类,目前我们的程序在编译期就已经确定哪些java类需要被加载。Java的反射机制是在编译时并不确定哪个类需要被加载,而是在程序运行时才加载、探知...
  • qq_27954949
  • qq_27954949
  • 2016-12-23 13:25
  • 540

Spring IOC原理之Java反射机制

1、反射概念以及为什么要使用反射 我们考虑一个场景,如果我们在程序运行时,一个对象想要检视自己所拥有的成员属性,该如何操作? 那再考虑这样另一个场景,如果我们想要在运行期获得某个类Class的信息如它的属性、构造方法、一般方法 后再考虑是否创建它的对象,这种情况该怎么办呢?这就
  • zhangliangzi
  • zhangliangzi
  • 2016-03-26 10:17
  • 10194

Spring注解@Value,动态取值

用法如下 @Value("${uname}") private String name; 我们想动态从配置文件中读取, 使用@Value注解即可; 那可定需要在上下文中有uname这个配置啦, 咱可以用个spring来加载资源(properties); clas...
  • mxj588love
  • mxj588love
  • 2016-11-09 14:51
  • 2438

JAVAssist---动态修改注解

​ITOOV3.0开始了,需要研究一些技术难点,先来说一个我觉得比较有趣的技术点吧,需求是这样的,我们需要动态的切换数据源,又由于我们是通过实体单元的注入来完成的,实体单元是通过注解的形式注入的,这样如果我们想修改数据源那么必然就要动态的修改注解(当然可能还有其他的解决方式,但是我觉得动态修改注解还...
  • jly4758
  • jly4758
  • 2015-03-31 09:26
  • 12305

Spring MVC 通过@Value注解读取.properties配置

第一步: 在applicationContext.xml配置: ? 1 2 3 4 5 6 7 8 9 10 bean id="configProperties&qu...
  • white__cat
  • white__cat
  • 2014-12-23 15:17
  • 37065

纯手写SpringMVC框架,用注解实现springmvc过程

1、第一步,首先搭建如下架构,其中,annotation中放置自己编写的注解,主要包括service controller qualifier RequestMapping 第二步:完成对应的annotation: package com.chaoyue.annotation; impor...
  • chaoyueygw
  • chaoyueygw
  • 2016-11-29 15:59
  • 3385

SpringMvc的手写版(PS:只是闲来无事写的简化版,仅供大家理解SpringMvc的运作原理)

最近手头正好有些时间,想着写点什么好呢?后来看到了一篇帖子说面试的时候有面试官问他能不能手写一套SpringMvc出来,不拉不拉的….不多说了。所以想着就写写试试,捋了捋思路,无非就是三点(大神勿喷!): 1. 实例化 2. 注入 3. url映射连起来说就是对加了@Controller、@S...
  • sun5769675
  • sun5769675
  • 2017-08-31 20:10
  • 905
    个人资料
    • 访问:350046次
    • 积分:4389
    • 等级:
    • 排名:第8156名
    • 原创:100篇
    • 转载:39篇
    • 译文:0篇
    • 评论:148条
    博客专栏
    友情链接
    最新评论