Environment of 0.

Java 1.8 IDE: IDEA 2019 Build tool: GradleCopy the code

1. The whole idea

1.1 some point

  • Uniformly receive requests using DispatcherServlet
  • Customize @Controller, @requestMapping, and @RequestParam annotations to implement method calls corresponding to different URIs
  • Use reflection to call the corresponding method using HandlerMapping
  • Use Tomcat-embed -core embedded Web container Tomcat.
  • Custom simple BeanFactory implements dependency injection DI, implementing Bean management with @bean annotations and @Controller annotations

1.2 Overall call diagram

1.3 Startup Loading Sequence

2. Implementation

2.1 Overall project Catalog

  • Create a gradle project by yourself.

2.2 Specific Implementation

  1. Create the TomcatServer class under Web. server
  • In simple terms, instantiate a Tomcat service and add a DispatcherServlet to the context to support asynchrony and handle all requests
public class TomcatServer {
    private Tomcat tomcat;
    private String[] args;

    public TomcatServer(String[] args) {
        this.args = args;
    }

    public void startServer(a) throws LifecycleException {
        // instantiated Tomcat
        tomcat = new Tomcat();
        tomcat.setPort(6699);
        tomcat.start();

        Context context = new StandardContext();
        context.setPath("");
        context.addLifecycleListener(new Tomcat.FixContextListener());

        // register Servlet
        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet).setAsyncSupported(true);
        context.addServletMappingDecoded("/"."dispatcherServlet");
        tomcat.getHost().addChild(context);

        Thread awaitThread = new Thread(() -> TomcatServer.this.tomcat.getServer().await(), "tomcat_await_thread");
        awaitThread.setDaemon(false); awaitThread.start(); }}Copy the code
  1. Create DispatcherServlet in web.servlet to realize servlet interface.
  • Because I’m doing a simple MVC, I’m handling all requests directly, not GET and POST, and I can make improvements.
  • All requests need to be processed in the Service method.
  • The simple idea is to use the HandlerManager to retrieve the corresponding MappingHandler object from the Map object through the URI, and then call the Handle method.
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        try {
            MappingHandler mappingHandler = HandlerManager.getMappingHandlerByURI(((HttpServletRequest) req).getRequestURI());
            if (mappingHandler.handle(req, res)) {
                return; }}catch(IllegalAccessException | InstantiationException | InvocationTargetException | ClassNotFoundException e) { e.printStackTrace(); }}Copy the code
  1. Create the MappingHandler and HandlerManager classes in Web. handler.
  • The MappingHandler is used to store URI call information, such as URIs, methods, and call parameters. The following
public class MappingHandler {
    private String uri;
    private Method method;
    privateClass<? > controller;private String[] args;

    public MappingHandler(String uri, Method method, Class
        controller, String[] args) {
        this.uri = uri;
        this.method = method;
        this.controller = controller;
        this.args = args; }}Copy the code
  • The HandlerManager is responsible for matching the URI to the MappingHandler that handles it
  • The implementation is to pass in all the scanned classes with the class scanner defined by you, and find the classes with the Controller annotation
  • Then build a MappingHandler object for each Controller that contains RequestMapping annotation method information and put it into the Map.
public class HandlerManager {
    public static Map<String, MappingHandler> handleMap = new HashMap<>();

    public static void resolveMappingHandler(List
       
        > classList)
       > {
        for(Class<? > cls : classList) {if(cls.isAnnotationPresent(Controller.class)) { parseHandlerFromController(cls); }}}private static void parseHandlerFromController(Class
        cls) {
        Method[] methods = cls.getDeclaredMethods();
        for (Method method : methods) {
            if(! method.isAnnotationPresent(RequestMapping.class)) {continue;
            }

            String uri = method.getDeclaredAnnotation(RequestMapping.class).value();
            List<String> paramNameList = new ArrayList<>();

            for (Parameter parameter : method.getParameters()) {
                if (parameter.isAnnotationPresent(RequestParam.class)) {
                    paramNameList.add(parameter.getDeclaredAnnotation(RequestParam.class).value());
                }
            }

            String[] params = paramNameList.toArray(new String[paramNameList.size()]);
            MappingHandler mappingHandler = newMappingHandler(uri, method, cls, params); HandlerManager.handleMap.put(uri, mappingHandler); }}public static MappingHandler getMappingHandlerByURI(String uri) throws ClassNotFoundException {
        MappingHandler handler = handleMap.get(uri);
        if (null == handler) {
            throw new ClassNotFoundException("MappingHandler was not exist!");
        } else {
            returnhandler; }}}Copy the code
  • The handle method is then added to the MappingHandler to process the request.
    public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
        String requestUri = ((HttpServletRequest) req).getRequestURI();
        if(! uri.equals(requestUri)) {return false;
        }

        // read parameters.
        Object[] parameters = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            parameters[i] = req.getParameter(args[i]);
        }

        // instantiated Controller.
        Object ctl = BeanFactory.getBean(controller);

        // invoke method.
        Object response = method.invoke(ctl, parameters);
        res.getWriter().println(response.toString());
        return true;
    }
Copy the code
  • Several annotations are in the web.mvc package, defined as follows:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestMapping {
    String value(a);
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface RequestParam {
    String value(a);
}
Copy the code
  1. Create the ClassScanner ClassScanner
  • The idea is also simple, using the Java class loader, read the class information, put it in a List and return it.
  • I only dealt with jar package types.
public class ClassScanner {
    public staticList<Class<? >> scanClasses(String packageName)throwsIOException, ClassNotFoundException { List<Class<? >> classList =new ArrayList<>();
        String path = packageName.replace("."."/");
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources = classLoader.getResources(path);

        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement();

            if (resource.getProtocol().contains("jar")) {
                // get Class from jar package.
                JarURLConnection jarURLConnection = (JarURLConnection) resource.openConnection();
                String jarFilePath = jarURLConnection.getJarFile().getName();
                classList.addAll(getClassesFromJar(jarFilePath, path));
            } else {
                // todo other way.}}return classList;
    }

    private staticList<Class<? >> getClassesFromJar(String jarFilePath, String path)throwsIOException, ClassNotFoundException { List<Class<? >> classes =new ArrayList<>();
        JarFile jarFile = new JarFile(jarFilePath);
        Enumeration<JarEntry> jarEntries = jarFile.entries();

        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            String entryName = jarEntry.getName();
            if (entryName.startsWith(path) && entryName.endsWith(".class")) {
                String classFullName = entryName.replace("/".".").substring(0, entryName.length() - 6); classes.add(Class.forName(classFullName)); }}returnclasses; }}Copy the code
  1. Create the BeanFactory class and @AutoWired @bean annotation under the Beans package
  • Annotations to define
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Bean {
}
Copy the code
  • The Handle method in MappingHandler is actually a getBean called by the BeanFactory.
  • The implementation of BeanFactory is actually quite simple. The idea is to pass in classes scanned by the class scanner, put classes with Controller and Bean annotations into the map, and use internal dependency injection if you use Autowired annotations internally. There is only singleton mode.
public class BeanFactory {
    private staticMap<Class<? >, Object> classToBean =new ConcurrentHashMap<>();
    public static Object getBean(Class
        cls) {
        return classToBean.get(cls);
    }
    public static void initBean(List
       
        > classList)
       > throws Exception { List<Class<? >> toCreate =new ArrayList<>(classList);

        while(toCreate.size() ! =0) {
            int remainSize = toCreate.size();
            for (int i = 0; i < toCreate.size(); i++) {
                if(finishCreate(toCreate.get(i))) { toCreate.remove(i); }}if (toCreate.size() == remainSize) {
                throw new Exception("cycle dependency!"); }}}private static boolean finishCreate(Class
        cls) throws IllegalAccessException, InstantiationException {
        if(! cls.isAnnotationPresent(Bean.class) && ! cls.isAnnotationPresent(Controller.class)) {return true;
        }
        Object bean = cls.newInstance();
        for (Field field : cls.getDeclaredFields()) {
            if(field.isAnnotationPresent(Autowired.class)) { Class<? > fieldType = field.getType(); Object reliantBean = BeanFactory.getBean(fieldType);if (null == reliantBean) {
                    return false;
                }
                field.setAccessible(true);
                field.set(bean, reliantBean);
            }
        }
        classToBean.put(cls, bean);
        return true; }}Copy the code
  1. Start the class
public class IlssApplication {
    public static void run(Class
        cls, String[] args) {
        TomcatServer tomcatServer = new TomcatServer(args);
        try{ tomcatServer.startServer(); List<Class<? >> classList = ClassScanner.scanClasses(cls.getPackage().getName()); BeanFactory.initBean(classList); HandlerManager.resolveMappingHandler(classList); }catch(Exception e) { e.printStackTrace(); }}}Copy the code

2.3 About the test module

  • To do internal test packaging, add the following configuration to build.gradle in the test project
jar {
    manifest {
        attributes "Main-Class": "io.ilss.framework.Application"
    }

    from {
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
}
Copy the code
  1. Create a Service class
@Bean
public class NumberService {
    public Integer calNumber(Integer num) {
        returnnum; }}Copy the code
  1. Create a Controller class

@Controller
public class TestController {

    @Autowired
    private NumberService numberService;
    @RequestMapping("/getNumber")
    public String getSalary(@RequestParam("name") String name, @RequestParam("num") String num) {
        return numberService.calNumber(11111) + name + num ; }}Copy the code
  1. Create the Application launcher class
public class Application {
    public static void main(String[] args) { IlssApplication.run(Application.class, args); }}Copy the code
  1. The console
Gradle clean install java-jar mvc-test/build/libs/ mvc-test-1.0-snapshot.jarCopy the code
  1. Access to web sites
  • http://localhost:6699/getNumber?name=aaa&num=123

Some points for improvement

  • Exception processing, the framework inside the exception I have a lot of are directly allowed stack information, and did not deal with.
  • BeanFactory is very simple, because it’s simple, so it’s really simple. Multiple examples are not supported. You can try adding
  • The expansibility is very poor, the younger brother’s ability is limited, hope the big guy to spray lightly.
  • .

Write in the last

  • Project on Github:Github.com/imyiren/sim…
  • The project is very simple, there are a lot of shortcomings, we can work together to improve. I will fight it again when MY internal skills are strong.

About me

  • Majoring in Computer Science and technology, general university, Hangzhou.
  • Graduated in 20 years, mainly engaged in back-end development of Java technology stack.
  • GitHub: github.com/imyiren
  • Blog : imyi.ren