SpringMVC底层(一)

笔记参考B站黑马程序员视频:spring原理

1.DispatcherServlet 初始化

加入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
</dependency>

AnnotationConfigServletWebServerApplicationContext 是 Spring Framework 中用于基于注解的配置和 Servlet 环境的应用上下文。这种上下文通常用于 Spring Web 应用中,通过 Java 配置类来启动和配置 Spring 应用。

也就是这个支持内嵌tomcat

package com.dreams.demo20;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;

public class Demo {
    private static final Logger log = LoggerFactory.getLogger(Demo.class);

    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
    }
}

WebConfig 类是一个配置类,用于配置 Spring Web 应用。

package com.dreams.demo20;

import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;

@Configuration
@ComponentScan
public class WebConfig {
    // 内嵌 web 容器工厂
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory() {
        return new TomcatServletWebServerFactory();
    }

    // 创建 DispatcherServlet
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    // 注册 DispatcherServlet, Spring MVC 的入口
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet) {
        return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    }


}

它包含了以下几个关键的配置:

  • TomcatServletWebServerFactory Bean:用于创建内嵌的 Tomcat Web 服务器工厂。通过注入 ServerProperties 对象获取端口号,并将其传递给 TomcatServletWebServerFactory 的构造函数。
  • DispatcherServlet Bean:用于创建 DispatcherServlet 实例。
  • DispatcherServletRegistrationBean Bean:用于注册 DispatcherServlet 到 Spring MVC 中。这是 Spring MVC 的入口。并指定 URL 映射为 “/”,表示所有的请求都将由这个 DispatcherServlet 处理。

输出如下:

那DispatcherServlet什么时候被初始化

当内嵌的 Servlet 容器(例如 Tomcat)启动时,它会初始化所有注册的 Servlet,包括通过 DispatcherServletRegistrationBean 注册的 DispatcherServlet。
在 Servlet 容器初始化过程中,DispatcherServlet 的 init 方法会被调用。

这里我们访问8080端口

可以看到初始化了

 

DispatcherServletRegistrationBean 用来注册 DispatcherServlet 到 Servlet 容器,并配置其属性。配置registrationBean.setLoadOnStartup(1),就是设置 DispatcherServlet 的 load-on-startup 属性为 1。这表示在应用启动时就加载 DispatcherServlet。

// 注册 DispatcherServlet, Spring MVC 的入口
@Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
        DispatcherServlet dispatcherServlet) {
    DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
    registrationBean.setLoadOnStartup(1);
    return registrationBean;
}

可以看到一起启动就加载。

 

registrationBean.setLoadOnStartup(1)应该读取配置文件来配置

默认值是-1.

@EnableConfigurationProperties 注解指定了两个参数:WebMvcProperties.class 和 ServerProperties.class。自动装配并使用这两个类中定义的配置属性。

WebMvcProperties 类通常是用来配置 Spring MVC 的属性,也就是以spring.mvc打头的配置,如:spring.mvc.servlet.load-on-startup=1

ServerProperties 类则通常包含了配置服务器(如内嵌的 Tomcat)的属性,例如端口号、上下文路径等。也就是以server打头的配置,如:server.port=8848

使用代码如下:

package com.dreams.demo20;

import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.DispatcherServlet;

@Configuration
@ComponentScan
@PropertySource("classpath:application.properties")
@EnableConfigurationProperties({WebMvcProperties.class, ServerProperties.class})
public class WebConfig {
    // 内嵌 web 容器工厂
    @Bean
    public TomcatServletWebServerFactory tomcatServletWebServerFactory(ServerProperties serverProperties) {
        return new TomcatServletWebServerFactory(serverProperties.getPort());
    }

    // 创建 DispatcherServlet
    @Bean
    public DispatcherServlet dispatcherServlet() {
        return new DispatcherServlet();
    }

    // 注册 DispatcherServlet, Spring MVC 的入口
    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
            DispatcherServlet dispatcherServlet, WebMvcProperties webMvcProperties) {
        DispatcherServletRegistrationBean registrationBean = new DispatcherServletRegistrationBean(dispatcherServlet, "/");
        registrationBean.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
        return registrationBean;
    }


}

 

DispatcherServlet类的真正逻辑在onRefresh方法

 

用于初始化 Servlet 使用的各种策略对象:

  • initMultipartResolver(context)
    初始化用于处理文件上传的 MultipartResolver 对象。
  • initLocaleResolver(context)
    初始化用于解析客户端请求的区域信息(Locale)的 LocaleResolver 对象。
  • initThemeResolver(context)
    初始化用于解析主题(Theme)的 ThemeResolver 对象。
  • initHandlerMappings(context)
    初始化用于映射请求到处理器(Controller)的 HandlerMapping 对象。
  • initHandlerAdapters(context)
    初始化用于执行处理器的 HandlerAdapter 对象。
  • initHandlerExceptionResolvers(context)
    初始化用于处理异常的 HandlerExceptionResolver 对象。
  • initRequestToViewNameTranslator(context)
    初始化用于将请求映射为视图名称的 RequestToViewNameTranslator 对象。
  • initViewResolvers(context)
    初始化用于解析视图的 ViewResolver 对象。
  • initFlashMapManager(context)
    初始化用于管理 Flash Map 的 FlashMapManager 对象。

这些方法的实现大同小异,比如initHandlerMappings方法,如下:

detectAllHandlerMappings 为 true 时获取所有 HandlerMapping 实例,也会从父容器中查找,反之则不会

如果没有就调用getDefaultStrategies方法

这里会默认读取DispatcherServlet.properties

都会看到默认配置

可以看到,没有配置默认实现的就是配置文件的配置

 

2.RequestMappingHandlerMapping基本用途

RequestMappingHandlerMapping 是 Spring MVC 中的一个特定类型的 HandlerMapping 实现,它用于映射 @RequestMapping 注解的方法到具体的处理器(Handler)。也就是路径到控制器方法的映射。

上面的代码没有配置,虽然会默认读取DispatcherServlet.properties,会创建RequestMappingHandlerMapping,但是并不会创建对应Bean到容器中,所以我们在webConfig文件加入以下代码

// 如果用 DispatcherServlet 初始化时默认添加的组件, 并不会作为 bean, 给测试带来困扰
// 1. 加入RequestMappingHandlerMapping
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
    return new RequestMappingHandlerMapping();
}

在当前目录下controller类

package com.dreams.demo20;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.yaml.snakeyaml.Yaml;

@Controller
public class TestController {

    private static final Logger log = LoggerFactory.getLogger(TestController.class);

    @GetMapping("/test1")
    public ModelAndView test1() throws Exception {
        log.debug("test1()");
        return null;
    }

    @PostMapping("/test2")
    public ModelAndView test2(@RequestParam("name") String name) {
        log.debug("test2({})", name);
        return null;
    }

    @PutMapping("/test3")
    public ModelAndView test3(String token) {
        log.debug("test3({})", token);
        return null;
    }

    @RequestMapping("/test4")
    public User test4() {
        log.debug("test4");
        return new User("张三", 18);
    }

    public static class User {
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }

        public void setName(String name) {
            this.name = name;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

    public static void main(String[] args) {
        String str = new Yaml().dump(new User("张三", 18));
        System.out.println(str);
    }
}

 

然后启动类就可以通过getBean获取该Bean了

package com.dreams.demo20;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.Map;

public class Demo {
    private static final Logger log = LoggerFactory.getLogger(Demo.class);

    public static void main(String[] args) throws Exception {
        AnnotationConfigServletWebServerApplicationContext context =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);


        // 作用 解析 @RequestMapping 以及派生注解,生成路径与控制器方法的映射关系, 在初始化时就生成
        RequestMappingHandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class);

        // 获取映射结果
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = handlerMapping.getHandlerMethods();
        handlerMethods.forEach((k, v) -> {
            System.out.println("key= " + k +  " ; value= " + v);
        });
    }
}

调用 handlerMapping.getHandlerMethods() 方法,可以得到一个 Map<RequestMappingInfo, HandlerMethod> 对象,该对象存储了所有请求路径到处理器方法的映射关系。

 

上面已经拿到了所有请求路径到处理器方法的映射关系,那么如何获取每个呢

使用 MockHttpServletRequest 来模拟一个 HTTP GET 请求,指定请求方法为 “GET” 和请求路径为 “/test1″。然后就可以使用 handlerMapping.getHandler(request) 方法来获取与请求对应的 HandlerExecutionChain 对象。

// 请求来了,获取控制器方法  返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test1");
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);

 

 

3.RequestMappingHandlerAdapter基本用途

RequestMappingHandlerAdapter 是 Spring MVC 框架中的一个核心组件之一,用于将处理器方法映射成可执行的对象,并处理请求和响应的转换。也就是调用控制器方法。

同上默认并不会创建对应Bean到容器中,所以我们需要在webConfig文件加入@Bean,不过因为我们需要的方法是私有的,所以我们实现个子类来扩大范围

package com.dreams.demo20;

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
    @Override
    public ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        return super.invokeHandlerMethod(request, response, handlerMethod);
    }
}

 

然后在webConfig配置该Bean

// 2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    return new MyRequestMappingHandlerAdapter();
}

 

Demo类调用

// 请求来了,获取控制器方法  返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test1");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);

System.out.println(">>>>>>>>>>>>>>>>>>>>>");
// HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());

handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler()) 这行代码调用了 invokeHandlerMethod 方法来执行控制器方法。这里传入的chain.getHandler()就是上面的RequestMappingHandlerMapping处理的所有映射了。

可以看到成功调用了该方法。

 

然后如果要传递参数

@PostMapping("/test2")
public ModelAndView test2(@RequestParam("name") String name) {
    log.debug("test2({})", name);
    return null;
}

代码:

// 请求来了,获取控制器方法  返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/test2");
request.setParameter("name", "张三");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);

System.out.println(">>>>>>>>>>>>>>>>>>>>>");
// HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());

那么他是如何获取参数解析器的呢?

导入

import org.springframework.web.method.support.HandlerMethodArgumentResolver;

handlerAdapter.getArgumentResolvers() 返回一个包含所有参数解析器的列表。

System.out.println(">>>>>>>>>>>>>>>>>>>>> 所有参数解析器");
for (HandlerMethodArgumentResolver resolver : handlerAdapter.getArgumentResolvers()) {
    System.out.println(resolver);
}

这里就是他用到的参数解析器

 

每一个请求的返回值也要处理

导入

import org.springframework.web.method.support.HandlerMethodReturnValueHandler;

handlerAdapter.getReturnValueHandlers() 返回一个包含所有返回值解析器的列表

System.out.println(">>>>>>>>>>>>>>>>>>>>> 所有返回值解析器");
for (HandlerMethodReturnValueHandler handler : handlerAdapter.getReturnValueHandlers()) {
    System.out.println(handler);
}

 

 

4.参数解析器

Spring Framework 中的参数解析器(Argument Resolvers)是用于将 HTTP 请求中的参数映射到控制器方法的参数的组件。

常见的解析器:

  • RequestParamMethodArgumentResolver:
    处理 @RequestParam 注解的方法参数,允许参数的必要性为可选(false)或必需(true)。
  • PathVariableMethodArgumentResolver:
    处理 @PathVariable 注解的方法参数,用于从 URI 中获取路径变量的值。
  • RequestHeaderMethodArgumentResolver:
    处理 @RequestHeader 注解的方法参数,用于从 HTTP 请求头中获取数据。
  • ServletCookieValueMethodArgumentResolver:
    处理 @CookieValue 注解的方法参数,用于从 HTTP 请求的 Cookie 中获取数据。
  • ExpressionValueMethodArgumentResolver:
    处理 SpEL 表达式作为方法参数的情况。
  • ServletRequestMethodArgumentResolver:
    处理 HttpServletRequest 和 HttpServletResponse 类型的方法参数。
  • ServletModelAttributeMethodProcessor (false):
    处理带有 @ModelAttribute 注解的方法参数,这里配置为不允许省略 @ModelAttribute 注解。
  • RequestResponseBodyMethodProcessor:
    处理带有 @RequestBody 和 @ResponseBody 注解的方法参数和返回值。此处指定使用 MappingJackson2HttpMessageConverter 来处理 JSON 数据的转换。
  • ServletModelAttributeMethodProcessor (true):
    处理带有 @ModelAttribute 注解的方法参数,这里配置为允许省略 @ModelAttribute 注解。
  • RequestParamMethodArgumentResolver (true):
    处理 @RequestParam 注解的方法参数,允许参数的必要性为可选(false)或省略(true)@RequestParam 注解。

解析器都有对应的注解来配置

在TestController类中我们定义一个请求方法

@PutMapping("/test3")
public ModelAndView test3(@Token String token) {
    log.debug("test3({})", token);
    return null;
}

上面的用到我们定义一个的注解

package com.dreams.demo20;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 例如经常需要用到请求头中的 token 信息, 用下面注解来标注由哪个参数来获取它
// token=令牌
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
}

 

要处理上面的注解,我们定义一个解析器

这个参数解析器(HandlerMethodArgumentResolver),用于从 HTTP 请求中获取特定的参数(在这里是从请求头中获取 token)并注入到控制器方法的参数中。

需要实现HandlerMethodArgumentResolver 接口,这是 Spring MVC 用来解析控制器方法参数的接口。

supportsParameter 方法:supportsParameter 方法决定了是否支持解析某个参数。在这里,通过检查 parameter 参数上是否存在 Token 注解来确定是否支持该参数的解析。如果存在 Token 注解,则返回 true,表示支持解析。

resolveArgument 方法:resolveArgument 方法负责实际的参数解析工作。在这里,通过 webRequest.getHeader(“token”) 方法从 HTTP 请求的头部获取名为 “token” 的值,并将其作为方法参数的值返回。

package com.dreams.demo20;

import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    // 是否支持某个参数
    public boolean supportsParameter(MethodParameter parameter) {
        Token token = parameter.getParameterAnnotation(Token.class);
        return token != null;
    }

    @Override
    // 解析参数
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return webRequest.getHeader("token");
    }
}

 

对于这个解析器我们需要加入RequestMappingHandlerAdapter中,所以在webConfig.java修改Bean,设置自定义的参数解析器,传入。

// 2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
    MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
    handlerAdapter.setCustomArgumentResolvers(Collections.singletonList(tokenArgumentResolver));
    return handlerAdapter;
}

 

接下来在启动类Demo加入请求test3方法

// 请求来了,获取控制器方法  返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test3");
request.setParameter("name", "张三");
request.addHeader("token", "某个令牌");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
System.out.println(">>>>>>>>>>>>>>>>>>>>>");
// HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());

可以看到成功了

 

对于不同的注解,有不同的解析器去处理

static class Controller {
    public void test(
            
            @RequestParam("name1") String name1, // name1=张三
            String name2,                        // name2=李四
            @RequestParam("age") int age,        // age=18
            
            @RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
            @RequestParam("file") MultipartFile file, // 上传文件
            
            @PathVariable("id") int id,               //  /test/124   /test/{id}
            
            @RequestHeader("Content-Type") String header,
            
            @CookieValue("token") String token,
            
            @Value("${JAVA_HOME}") String home2, // spring 获取数据  ${} #{}
            
            HttpServletRequest request,          // request, response, session ...
            
            @ModelAttribute("abc") User user1,          // name=zhang&age=18
            User user2,                          // name=zhang&age=18
            
            @RequestBody User user3              // json
    ) {
    }
}

 

全部代码:

package com.dreams.demo21;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

public class Demo {

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
        // 准备测试 Request
        HttpServletRequest request = mockRequest();

        // 要点1. 控制器方法被封装为 HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));

        // 要点2. 准备对象绑定与类型转换
        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);

        // 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
        ModelAndViewContainer container = new ModelAndViewContainer();

        // 要点4. 解析每个参数值
        for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
            // 多个解析器组合
            HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
            composite.addResolvers(
                    //                                          false 表示必须有 @RequestParam,否则可以省略
                    new RequestParamMethodArgumentResolver(beanFactory, false),
                    new PathVariableMethodArgumentResolver(),
                    new RequestHeaderMethodArgumentResolver(beanFactory),
                    new ServletCookieValueMethodArgumentResolver(beanFactory),
                    new ExpressionValueMethodArgumentResolver(beanFactory),
                    new ServletRequestMethodArgumentResolver(),
                    new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
                    new RequestResponseBodyMethodProcessor(Collections.singletonList(new MappingJackson2HttpMessageConverter())),
                    new ServletModelAttributeMethodProcessor(true), // 省略了 @ModelAttribute
                    new RequestParamMethodArgumentResolver(beanFactory, true) // 省略 @RequestParam
            );

            String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
            String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
            parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

            if (composite.supportsParameter(parameter)) {
                // 支持此参数
                Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
//                System.out.println(v.getClass());
                System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
                System.out.println("模型数据为:" + container.getModel());
            } else {
                System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
            }
        }


    }

    private static HttpServletRequest mockRequest() {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name1", "zhangsan");
        request.setParameter("name2", "lisi");
        request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
        Map<String, String> map = new AntPathMatcher().extractUriTemplateVariables("/test/{id}", "/test/123");
        System.out.println(map);
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
        request.setContentType("application/json");
        request.setCookies(new Cookie("token", "123456"));
        request.setParameter("name", "张三");
        request.setParameter("age", "18");
        // 设置请求内容为 JSON 字符串
        String jsonContent = "{\n" +
                "    \"name\": \"李四\",\n" +
                "    \"age\": 20\n" +
                "}";
        request.setContent(jsonContent.getBytes(StandardCharsets.UTF_8));

        return new StandardServletMultipartResolver().resolveMultipart(request);
    }

    static class Controller {
        public void test(
                @RequestParam("name1") String name1, // name1=张三
                String name2,                        // name2=李四
                @RequestParam("age") int age,        // age=18
                @RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
                @RequestParam("file") MultipartFile file, // 上传文件
                @PathVariable("id") int id,               //  /test/124   /test/{id}
                @RequestHeader("Content-Type") String header,
                @CookieValue("token") String token,
                @Value("${JAVA_HOME}") String home2, // spring 获取数据  ${} #{}
                HttpServletRequest request,          // request, response, session ...
                @ModelAttribute("abc") User user1,          // name=zhang&age=18
                User user2,                          // name=zhang&age=18
                @RequestBody User user3              // json
        ) {
        }
    }

    static class User {
        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "User{" +
                   "name='" + name + '\'' +
                   ", age=" + age +
                   '}';
        }
    }
}

解析代码:

这里我们不需要web环境,使用AnnotationConfigWebApplicationContex即可

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
// 准备测试 Request
HttpServletRequest request = mockRequest();

 

这里我们需要准备一个控制器方法,之前我们使用RequestMappingHandlerMapping,他会帮我们封装成控制器方法,这里直接创建一个。传入controller类和对应的方法即可。

// 要点1. 控制器方法被封装为 HandlerMethod
HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));

 

对于解析出来的参数比如age=8,解析出来的是String类型,所以我们需要准备对象绑定与类型转换

// 要点2. 准备对象绑定与类型转换
ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);

 

ModelAndViewContainer 的主要作用是存储控制器方法处理后的模型数据。它充当了一个容器,可以暂时保存控制器方法处理中间结果的数据。在请求处理过程中,ModelAndViewContainer 通常会与视图解析器(ViewResolver)协作。最终,视图解析器将根据 ModelAndViewContainer 中的数据选择合适的视图来呈现最终的响应结果。后面会用到

// 要点3. 准备 ModelAndViewContainer 用来存储中间 Model 结果
ModelAndViewContainer container = new ModelAndViewContainer();

 

通过 handlerMethod.getMethodParameters() 获取了控制器方法的参数列表,对于每个参数对象 parameter,调用 initParameterNameDiscovery() 方法,并传入 DefaultParameterNameDiscoverer 的实例。就是设置一个参数名的解析器

然后获取:

  • parameter.getParameterIndex():获取参数在方法参数列表中的索引。
  • parameter.getParameterType().getSimpleName():获取参数的类型的简单名称,比如 String、int 等。
  • parameter.getParameterName():获取参数的名称,如果编译时启用了参数名称保存,将显示原始的参数名称;否则可能显示为默认的形式,比如 arg0 等。
// 要点4. 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
    parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
    System.out.println("[" + parameter.getParameterIndex() + "] " + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
}

加上注解,使用parameter.getParameterAnnotations() 返回参数上的所有注解数组。再拼接上参数名。

String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() );

 

RequestParamMethodArgumentResolver

org.springframework.web.method.annotation.RequestParamMethodArgumentResolver 是 Spring Framework 中用于解析处理请求参数的类之一, 可以处理 @RequestParam 注解

比如处理@RequestParam:

@RequestParam("name1") String name1, // name1=张三
String name2,                        // name2=李四
@RequestParam("age") int age,        // age=18
@RequestParam(name = "home", defaultValue = "${JAVA_HOME}") String home1, // spring 获取数据
@RequestParam("file") MultipartFile file, // 上传文件

 

这里使用RequestParamMethodArgumentResolver去处理

创建了一个 HandlerMethodArgumentResolverComposite 对象 composite,并向其中添加了一个 RequestParamMethodArgumentResolver 实例。这个 composite 组合了多个参数解析器,以支持不同类型的方法参数处理。

composite.supportsParameter(parameter) 检查当前的参数是否被 composite 支持解析。
如果支持,通过 composite.resolveArgument() 方法解析参数,并打印参数类型、参数名及其解析后的值。

Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);我们传入了factory,就是我们上面准备的对象绑定与类型转换的ServletRequestDataBinderFactory,还有传入container,也就是我们上面的用来存储中间 Model 结果的ModelAndViewContainer。

// 要点4. 解析每个参数值
for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
    // 多个解析器组合
    HandlerMethodArgumentResolverComposite composite = new HandlerMethodArgumentResolverComposite();
    composite.addResolvers(
            //                                          false 表示必须有 @RequestParam,否则可以省略
            new RequestParamMethodArgumentResolver(null, false)
    );

    String annotations = Arrays.stream(parameter.getParameterAnnotations()).map(a -> a.annotationType().getSimpleName()).collect(Collectors.joining());
    String str = annotations.length() > 0 ? " @" + annotations + " " : " ";
    parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

    if (composite.supportsParameter(parameter)) {
        // 支持此参数
        Object v = composite.resolveArgument(parameter, container, new ServletWebRequest(request), factory);
        System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName() + "->" + v);
        System.out.println("模型数据为:" + container.getModel());
    } else {
        System.out.println("[" + parameter.getParameterIndex() + "] " + str + parameter.getParameterType().getSimpleName() + " " + parameter.getParameterName());
    }
}

其他正常输出,但是defaultValue = “${JAVA_HOME}”直接输出了

只要在new RequestParamMethodArgumentResolver(beanFactory, false)传入beanFactory,即DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();我们需要传入一个工厂,他才能处理defaultValue = “${JAVA_HOME}”

 

PathVariableMethodArgumentResolver

PathVariableMethodArgumentResolver()处理@PathVariable注解

这里我们对参数需要进行处理,使用 Spring Framework 提供的 AntPathMatcher 类来解析 REST 风格的 URL,将 URL 中的路径变量提取出来,并将其存储在 request 对象的属性中,属性名为 HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE。

这个步骤是RequestMappingHandlerMapping完成的,这里没有使用RequestMappingHandlerMapping,所以我们需要以上配置

这样加入composite即可

composite.addResolvers(
        //                                          false 表示必须有 @RequestParam,否则可以省略
        new RequestParamMethodArgumentResolver(beanFactory, false),
        new PathVariableMethodArgumentResolver()
);

 

RequestHeaderMethodArgumentResolver

RequestHeaderMethodArgumentResolver 是 Spring MVC 中负责解析使用 @RequestHeader 注解标记的方法参数的类。

composite.addResolvers(
        //                                          false 表示必须有 @RequestParam,否则可以省略
        new RequestParamMethodArgumentResolver(beanFactory, false),
        new PathVariableMethodArgumentResolver(),
        new RequestHeaderMethodArgumentResolver(beanFactory)
);

 

ServletCookieValueMethodArgumentResolver

ServletCookieValueMethodArgumentResolver是负责处理 @CookieValue 注解的方法参数解析器

当调用带有 @CookieValue 注解的控制器方法时,Spring MVC 会使用 ServletCookieValueMethodArgumentResolver 来获取指定 Cookie 的值,并将其传递给方法的参数。

composite.addResolvers(
        //                                          false 表示必须有 @RequestParam,否则可以省略
        new RequestParamMethodArgumentResolver(beanFactory, false),
        new PathVariableMethodArgumentResolver(),
        new RequestHeaderMethodArgumentResolver(beanFactory),
        new ServletCookieValueMethodArgumentResolver(beanFactory)
);

 

ExpressionValueMethodArgumentResolver

ExpressionValueMethodArgumentResolver 是 Spring MVC 中负责处理 @Value 注解的方法参数解析器,当调用带有 @Value 注解的控制器方法时,Spring MVC 会使用 ExpressionValueMethodArgumentResolver 来计算 SpEL 表达式,并将其结果作为方法的参数。

 

ServletRequestMethodArgumentResolver

ServletRequestMethodArgumentResolver 是 Spring MVC 中负责处理直接从 Servlet API(如 HttpServletRequest 或 HttpServletResponse)中获取参数的方法参数解析器之一。它的作用是允许控制器方法直接声明 HttpServletRequest 或 HttpServletResponse 参数,从而直接访问底层的 HTTP 请求或响应对象。当调用控制器方法时,Spring MVC 会使用 ServletRequestMethodArgumentResolver 来判断参数类型是否是 HttpServletRequest 或 HttpServletResponse,并将对应的实例传递给方法。

composite.addResolvers(
        //                                          false 表示必须有 @RequestParam,否则可以省略
        new RequestParamMethodArgumentResolver(beanFactory, false),
        new PathVariableMethodArgumentResolver(),
        new RequestHeaderMethodArgumentResolver(beanFactory),
        new ServletCookieValueMethodArgumentResolver(beanFactory),
        new ExpressionValueMethodArgumentResolver(beanFactory),
        new ServletRequestMethodArgumentResolver()
);

打开这个类的supportsParameter,可以看到也不止支持HttpServletRequest 或 HttpServletResponse

 

ServletModelAttributeMethodProcessor

ServletModelAttributeMethodProcessor 是 Spring MVC 中负责处理带有 @ModelAttribute 注解的方法参数的方法参数解析器之一。它的作用是在处理控制器方法时,解析和处理 @ModelAttribute 注解,并将其注入到方法的参数中。当调用带有 @ModelAttribute 注解的控制器方法时,Spring MVC 使用 ServletModelAttributeMethodProcessor 来解析该注解,并创建或更新模型对象。

composite.addResolvers(
        //                                          false 表示必须有 @RequestParam,否则可以省略
        new RequestParamMethodArgumentResolver(beanFactory, false),
        new PathVariableMethodArgumentResolver(),
        new RequestHeaderMethodArgumentResolver(beanFactory),
        new ServletCookieValueMethodArgumentResolver(beanFactory),
        new ExpressionValueMethodArgumentResolver(beanFactory),
        new ServletRequestMethodArgumentResolver(),
        new ServletModelAttributeMethodProcessor(false) // 必须有 @ModelAttribute
);

这里看到模型数据输出了,也就是我们上面配置的
ModelAndViewContainer container = new ModelAndViewContainer()的用处了。

 

RequestResponseBodyMethodProcessor

RequestResponseBodyMethodProcessor 是 Spring MVC 中负责处理带有 @ResponseBody 和 @RequestBody 注解的方法参数和返回值的关键组件之一。它的作用是将请求的数据(如 JSON、XML 等格式的请求体)转换为方法参数,并将方法返回的数据转换为响应体。当控制器方法的参数或返回值被 @RequestBody 注解修饰时,RequestResponseBodyMethodProcessor 负责将请求体中的数据(通常是 JSON 或 XML 格式)转换为对应的 Java 对象。它通过消息转换器(Message Converters)实现请求体和响应体的转换。

composite.addResolvers(
        //                                          false 表示必须有 @RequestParam,否则可以省略
        new RequestParamMethodArgumentResolver(beanFactory, false),
        new PathVariableMethodArgumentResolver(),
        new RequestHeaderMethodArgumentResolver(beanFactory),
        new ServletCookieValueMethodArgumentResolver(beanFactory),
        new ExpressionValueMethodArgumentResolver(beanFactory),
        new ServletRequestMethodArgumentResolver(),
        new ServletModelAttributeMethodProcessor(false), // 必须有 @ModelAttribute
        new RequestResponseBodyMethodProcessor(Collections.singletonList(new MappingJackson2HttpMessageConverter()))
);

 

 

5.返回值处理器

返回值处理器(Return Value Handler)指的是处理控制器方法返回结果的组件。这些处理器负责将控制器方法的返回值转换为适当的响应格式,比如将对象转换为 JSON 格式或者渲染模板视图等。

比如要处理返回yml格式

@RequestMapping("/test4")
@Yml
public User test4() {
    log.debug("test4");
    return new User("张三", 18);
}

自定义注解

package com.dreams.demo20;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Yml {
}

同样的我们需要自定义一个处理器

package com.dreams.demo20;

import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.yaml.snakeyaml.Yaml;

import javax.servlet.http.HttpServletResponse;

public class YmlReturnValueHandler implements HandlerMethodReturnValueHandler {
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        Yml yml = returnType.getMethodAnnotation(Yml.class);
        return yml != null;
    }

    @Override                   //  返回值
    public void handleReturnValue(Object returnValue, MethodParameter returnType,
                                  ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        // 1. 转换返回结果为 yaml 字符串
        String str = new Yaml().dump(returnValue);

        // 2. 将 yaml 字符串写入响应
        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
        response.setContentType("text/plain;charset=utf-8");
        response.getWriter().print(str);

        // 3. 设置请求已经处理完毕
        mavContainer.setRequestHandled(true);
    }
}

supportsReturnType 方法:supportsReturnType 方法用于确定是否支持处理特定类型的返回值。在这里,它通过检查 MethodParameter 对象中的方法注解 @Yml 来判断是否支持处理该返回类型。如果方法被 @Yml 注解修饰,则返回 true,表示当前的处理器可以处理该返回值类型。

handleReturnValue 方法:handleReturnValue 方法实际上执行了将返回值转换为 YAML 格式并写入 HTTP 响应的操作。首先,它使用 SnakeYAML 库将 returnValue 对象转换为 YAML 格式的字符串。
然后,从 NativeWebRequest 中获取 HttpServletResponse 对象,设置响应的内容类型为 text/plain;charset=utf-8,并将转换后的 YAML 字符串写入响应的输出流中。不过,springMVC默认还会视图解析,所以通过 ModelAndViewContainer 的 setRequestHandled(true) 方法标记请求处理完成,告知 Spring MVC 框架已经处理了响应。

 

同样需要在WebConfig.java配置

// 2. 继续加入RequestMappingHandlerAdapter, 会替换掉 DispatcherServlet 默认的 4 个 HandlerAdapter
@Bean
public MyRequestMappingHandlerAdapter requestMappingHandlerAdapter() {
    TokenArgumentResolver tokenArgumentResolver = new TokenArgumentResolver();
    YmlReturnValueHandler ymlReturnValueHandler = new YmlReturnValueHandler();
    MyRequestMappingHandlerAdapter handlerAdapter = new MyRequestMappingHandlerAdapter();
    handlerAdapter.setCustomArgumentResolvers(Collections.singletonList(tokenArgumentResolver));
    handlerAdapter.setCustomReturnValueHandlers(Collections.singletonList(ymlReturnValueHandler));
    return handlerAdapter;
}

 

启动类里调用test4方法

// 请求来了,获取控制器方法  返回处理器执行链对象
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/test4");
MockHttpServletResponse response = new MockHttpServletResponse();
HandlerExecutionChain chain = handlerMapping.getHandler(request);
System.out.println(chain);
System.out.println(">>>>>>>>>>>>>>>>>>>>>");
// HandlerAdapter 作用: 调用控制器方法
MyRequestMappingHandlerAdapter handlerAdapter = context.getBean(MyRequestMappingHandlerAdapter.class);
handlerAdapter.invokeHandlerMethod(request, response, (HandlerMethod) chain.getHandler());

// 检查响应
byte[] content = response.getContentAsByteArray();
System.out.println(new String(content, StandardCharsets.UTF_8));

运行如下:

!! YAML 库中用于标识数据类型,也就是成功了

 

 

6.获取方法的参数名称

如果我们想要获取方法参数名

for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
    System.out.println(parameter.getParameterName());
}

可以看到为null

在前面,我们获取方法的参数名称时,是这样获取的

for (MethodParameter parameter : handlerMethod.getMethodParameters()) {
    parameter.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
    System.out.println(parameter.getParameterName());
}

对于每个方法参数 parameter,通过调用 initParameterNameDiscovery(new DefaultParameterNameDiscoverer()) 方法来初始化参数名的发现器。才能获取到参数名,在这里使用的是 DefaultParameterNameDiscoverer,它是 Spring 提供的默认实现,用于发现方法参数的名称。

可以看到参数名的获取不是直接拿到的

我们新建一个普通类

package com.dreams.demo;

public class Bean {
    public void test(String name, int age){

    }
}

javac命令编译

javac .\Bean.java

idea的src目录下默认不显示class文件,我们直接打开该目录,然后使用idea打开编译的class文件,可以看到是没有参数名的。

 

如果想要参数的话,就要添加参数去编译

javac -parameters .\Bean.java

这样就有了

原理是编译在在class文件添加了个了个MethodParameters

反编译一下:

javap -c -v .\Bean.class

 

还有一种方法就是使用以下方式去编译

javac -g .\Bean.java

而原理就是编译在class文件上添加了一个LocalVariableTable。

不过,-g参数编译接口时是不会生成局部变量表的

 

idea的src目录下自带的编译会影响实验,所以新建个demo目录,与src目录平级

添加到java中,让idea能够被检测到,但是不会被自动编译

Bean类代码

package com.dreams.demo;

public class Bean {
    public void test(String name, int age) {

    }
}

对他进行编译

可以看到这里编译出来并没有保留参数名

在src目录下获取上面编译出来的class类,注意这个启动类要与Bean的目录结构一致。

package com.dreams.demo;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;


public class Demo {

    public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
        // 1. 反射获取参数名
        Method foo = com.dreams.demo.Bean.class.getMethod("test", String.class, int.class);
        for (Parameter parameter : foo.getParameters()) {
            System.out.println(parameter.getName());
        }
    }

}

 

加上-g编译参数

可以看到class里有了参数名

但是Demo类是获取不到的,因为-g参数这种方法无法通过参数获取

 

使用-parameters 参数可以通过反射获取

 

java中有对-g参数生成的局部变量表的获取的类

package com.dreams.demo;

import org.springframework.core.LocalVariableTableParameterNameDiscoverer;

import java.lang.reflect.Method;
import java.util.Arrays;


public class Demo {

    public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {

        Method foo = Bean.class.getMethod("test", String.class, int.class);

        // 2. 基于 LocalVariableTable 本地变量表
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(foo);
        System.out.println(Arrays.toString(parameterNames));
    }

}

解释:

  • LocalVariableTableParameterNameDiscoverer 类: 这个类利用 Java 的反射能力和额外的字节码分析,从已编译的类中获取参数名。这基于编译时是否包含了调试信息(例如在 Java 编译时使用了 -g 选项)。
  • getParameterNames(Method method) 方法: 这个方法尝试从给定的 Method 对象中获取参数名。如果字节码中包含了调试信息(例如编译时启用了调试信息),它可以将字节码索引映射到源代码中定义的参数名。
  • -parameters参数没有生成局部变量表,就获取不了

而在spring中-g参数和-parameters参数都是支持的

 

7.参考

黑马程序员:spring

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇