SpringBoot学习

一、注解认识

1、@Import注解

@Import 注解是 可以指定要导入的组件的类,通过无参构造的方式在容器中创建该实例

//@Service 将该注解注释掉,测试@Import注解
public class AService {

    public String sayHello() {
        return "hello world";
    }

}
//在Spring容器中添加myConfig组件,随即便通过无参构造的方式将AService创建到Spring容器中
//组件名为全类名 com.jun.service.AService
@Import(AService.class)
@Configuration
public class MyConfig {
	...
}
@RestController
public class AController {

    private final AService aService;

    //只有一个构造器可以不加@Autowired
    public AController(AService aService) {
        this.aService = aService;
    }

    @GetMapping
    public String test(){
        return aService.sayHello();
    }
}

image.png
使用@Import注解后,Spring会自动地加载和注册被引入的配置类或组件,使其可以在应用程序中使用。因此可见,即使AService没有加 @Service注解 让Spring容器管理,也会因为@Import注解 将该类添加到容器中。

2、@Conditional注解

概念:条件装配,满足Conditional指定的条件,来决定是否创建或注册一个Bean。

@Conditional注解可以添加在@Configuration、@Component、@Service等修饰的类上用于控制对应的Bean是否需要创建,或者添加在@Bean修饰的方法上用于控制方法对应的Bean是否需要创建。

image.png

条件注解 Condition处理类 实例 解释
@ConditionalOnBean OnBeanCondition @ConditionalOnBean(DataSource.class) Spring容器中不存在对应的实例生效
@ConditionalOnMissingBean OnBeanCondition @ConditionalOnMissingBean(name = "redisTemplate") Spring容器中不存在对应的实例生效
@ConditionalOnSingleCandidate OnBeanCondition @ConditionalOnSingleCandidate(FilteringNotifier.class) Spring容器中是否存在且只存在一个对应的实例,或者虽然有多个但 是指定首选的Bean生效
@ConditionalOnClass OnClassCondition @ConditionalOnClass(RedisOperations.class) 类加载器中存在对应的类生效
@ConditionalOnMissingClass OnClassCondition @ConditionalOnMissingClass(RedisOperations.class) 类加载器中不存在对应的类生效
@ConditionalOnExpression OnExpressionCondition @ConditionalOnExpression(“’${server.host}’==’localhost’”) 判断SpEL 表达式成立生效
@ConditionalOnJava OnJavaCondition @ConditionalOnJava(JavaVersion.EIGHT) 指定Java版本符合要求生效
@ConditionalOnProperty OnPropertyCondition @ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true) 应用环境中的属性满足条件生效
@ConditionalOnResource OnResourceCondition @ConditionalOnResource(resources=”mybatis.xml”) 存在指定的资源文件生效
@ConditionalOnWebApplication OnWebApplicationCondition 当前应用是Web应用生效
@ConditionalOnNotWebApplication OnWebApplicationCondition 当前应用不是Web应用生效

上面的扩展注解我们可以简单的分为以下几类:

  • Bean作为条件:@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnSingleCandidate。
  • 类作为条件:@ConditionalOnClass、@ConditionalOnMissingClass。
  • SpEL表达式作为条件:@ConditionalOnExpression。
  • JAVA版本作为条件: @ConditionalOnJava
  • 配置属性作为条件:@ConditionalOnProperty。
  • 资源文件作为条件:@ConditionalOnResource。
  • 是否Web应用作为判断条件:@ConditionalOnWebApplication、@ConditionalOnNotWebApplication。
//若spring容器里没有aService组件,则向spring容器注册user01组件
@ConditionalOnBean(name = "aService")
@Bean
public User user01(){
    return new User("liyuejun",18);
}

也可以直接使用@Conditional注解,但是需要自己自定义一个类去实现Condition接口重新matches方法,然后把该类.class当做注解的入参

@FunctionalInterface
public interface Condition {
	//所有用于匹配的Condition接口(实现该接口的类),只有这些类都返回true才认为是满足条件
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

例子:

server:
  port: 8800

# 自定义环境参数
env: dev
public class MyCondition implements Condition {

    /**
     *
     * @param context 该参数提供了条件判断所需的上下文信息,包括环境变量、Bean定义、Bean工厂等。
     * @param metadata 该参数提供了要进行条件判断的注解元数据信息,可以通过它来获取被注解元素(类、方法、字段等)的注解信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String currentEnvironment = context.getEnvironment().getProperty("env");
        return "dev".equals(currentEnvironment);
    }

}
@Configuration
public class Myconfig {
    //倘若是dev环境,则创建该组件
    @Conditional(MyCondition.class)
    @Bean
    public User user01(){
        return new User("liyuejun",18);
    }

}

3、@ConfigurationProperties注解

概念:用于将配置文件中的属性绑定到一个Java类中

3.1、方式一:@ConfigurationProperties + @Component 注解实现

# 自定义参数
person:
  userName: liyuejun
  age: 18
@Data
//需要将该类注入到Spring容器里,这样才能使用springboot的注解功能
//组件名:user
@Component
//将配置文件中,前缀为person的属性全自动注入到对应的java属性里
@ConfigurationProperties(prefix = "person")
public class User {

    private String userName;

    private Integer age;
}
@SpringBootApplication
public class ApplicationMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(ApplicationMain.class, args);
        //获取user实例
        User user = (User) context.getBean("user");
        System.out.println(user);
    }
}

image.png

3.2、方式二:@EnableConfigurationProperties + @ConfigurationProperties 注解实现

因为在第三方依赖里,某些类可能没有@Component 此注解,因此需要使用该方式去实现

@Data
//因为第三方包的类不一定会有@Component注解,因此需要在配置类里使用注解来将该类注入到Spring容器里
//@Component
//将配置文件中,前缀为person的属性全自动注入到对应的java属性里
@ConfigurationProperties(prefix = "person")
public class User {

    private String userName;

    private Integer age;
}
//1、开启User配置自动绑定
//2、把这个User类自动注入到Spring容器中
//组件名:person-com.jun.bean.User
@EnableConfigurationProperties(User.class)
public class Myconfig {

}
@SpringBootApplication
public class ApplicationMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(ApplicationMain.class, args);
        User user = context.getBean(User.class);
        System.out.println(user);
    }
}

image.png
效果一致

二、自动配置

1、自动配置【源码分析】- 自动包规则原理

Spring Boot应用的启动类:

@SpringBootApplication
public class ApplicationMain {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(ApplicationMain.class, args);
    }
}

点击@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
	...
}

重点分析@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan。

@SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

实际跟@Configuration 没什么区别

@ComponentScan
指定扫描哪些Spring注解

@EnableAutoConfiguration
这是一个复合注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    ...
}

再点击@AutoConfigurationPackage查看

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//这里就使用了@Import注解,给Spring容器中导入Registrar组件
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	...
}

点击AutoConfigurationPackages类的Registrar内部类

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    //metadata 为注解元信息,表示@AutoConfigurationPackage在哪个类上标注着(主程序类)
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        //将标注着@AutoConfigurationPackage的类的包路径下的所有组件导入到Spring容器中
        register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImports(metadata));
    }

}

image.png
然后将标注着@AutoConfigurationPackage的类的包路径下的所有组件导入到Spring容器中

2、自动配置【源码分析】- 初始加载自动配置类

然后我们再看看这个@Import(AutoConfigurationImportSelector.class)注解
点进去

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
	//利用该方法给容器中批量导入一些组件
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

再点击getAutoConfigurationEntry查看

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
	//获取到所有需要导入到容器中的配置类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //过滤操作
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

点击getCandidateConfigurations查看

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //利用工厂加载得到所有组件
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
            getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
            + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

点击loadFactoryNames查看

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

点击loadSpringFactories方法查看

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    result = new HashMap<>();
    try {
        //FACTORIES_RESOURCE_LOCATION = META-INF/spring.factories
        //将默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                String[] factoryImplementationNames =
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                for (String factoryImplementationName : factoryImplementationNames) {
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                            .add(factoryImplementationName.trim());
                }
            }
        }

        // Replace all lists with unmodifiable lists containing unique elements
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}
  • spring.factories文件是以key-value键值对的形式存储文件里,
  • 其中有一个key=EnableAutoConfiguration,它对应的value值就是一个个以AutoConfiguration结尾来命名的 xxxAutoConfiguration 自动配置类

image.png

  • 所以spring boot在整个的启动过程中,其实就是在类路径的META-INF/spring.factories 文件中找到EnableAutoConfiguration对应的所有的自动配置类,然后将所有自动配置类加载到spring容器中。
  • 所有自动配置类都是以AutoConfiguration结尾来命名的,而诸多的xxxAutoConfiguration 其实就是Spring容器的JavaConfig形式,它的作用就是为Spring容器导入bean。

image.png

三、YML语法

@Data
//因为第三方包的类不一定会有@Component注解,因此需要在配置类里使用注解来将该类注入到Spring容器里
@Component
//将配置文件中,前缀为person的属性全自动注入到对应的java属性里
@ConfigurationProperties(prefix = "person")
public class User {

    private String userName;

    private Integer age;

    private Boolean boss;

    private Date birth;

    private Pet pet;

    private String[] interests;

    private List<String> animal;

    private Map<String,Object> score;

    private Set<Double> salarys;

    private Map<String, List<Pet>> allPets;
}
person:
  userName: liyuejun
  age: 18
  boss: true
  birth: 1998/12/07
  interests: [篮球,足球]
  animal: [阿猫,阿狗]
  #score: {english: 80,math: 90}
  score:
    english: 80
    math: 90
  salarys: [1000,2000]
  pet: {petName: 二哈}
  allPets:
    key1: [{petName: 二哈}]

若想在配置文件那有提示补充功能,便加上这个依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

image.png

四、Web场景

1、静态资源规则

只要静态资源放在类路径下: /static (or /public or /resources or /META-INF/resources
image.png
访问 : 当前项目根路径/ + 静态资源名
也可禁用

spring:
  resources:
    add-mappings: false   #禁用所有静态资源规则

例子:
image.png

image.png
image.png
image.png
image.png

把资源放入子目录下的访问方式
image.png
image.png

  • 为什么静态资源能够直接访问呢?

因为静态资源映射默认是 /**,因此可以直接访问。

  • 倘若接口路径跟静态资源的路径一致,那会有什么效果?

请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面。

为了日后方便过滤器或者拦截器,进行静态资源放行的操作,则需要将静态资源的路径稍作修改。当前静态资源路径默认是没有前缀,为/**。因此可以在配置文件稍作修改

spring:
  # 将静态资源路径访问加个前缀
  mvc:
    static-path-pattern: /resources/**
  # 将哪个文件夹定义为静态资源存放处
  web:
    resources:
      static-locations: classpath:/public/
  

image.png
image.png

2、静态资源配置原理

当满足@Conditional注解时,该WebMvcAutoConfiguration便会生效

配置文件的相关属性的绑定:WebMvcPropertiesspring.mvc、ResourcePropertiesspring.resources、WebProperties==spring.web

package org.springframework.boot.autoconfigure.web.servlet;

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
	@Import(EnableWebMvcConfiguration.class)
	@EnableConfigurationProperties({ WebMvcProperties.class,
			org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
	@Order(0)
	public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
        private final WebMvcProperties mvcProperties;

		private final ListableBeanFactory beanFactory;

		private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;

		private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;

		private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;

		final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;


        //WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
        //ListableBeanFactory beanFactory Spring的beanFactory
        //HttpMessageConverters 找到所有的HttpMessageConverters
        //ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。
        //DispatcherServletPath
        //ServletRegistrationBean 给应用注册Servlet、Filter…
        //先将所有配置绑定的数据、初始化数据通过有参构造进行赋值
		public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties,
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
				ObjectProvider<DispatcherServletPath> dispatcherServletPath,
				ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
			this.mvcProperties = mvcProperties;
			this.beanFactory = beanFactory;
			this.messageConvertersProvider = messageConvertersProvider;
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
			this.dispatcherServletPath = dispatcherServletPath;
			this.servletRegistrations = servletRegistrations;
			this.mvcProperties.checkConfiguration();
    }

    
    @Configuration(proxyBeanMethods = false)
	@EnableConfigurationProperties(WebProperties.class)
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
      //资源处理的默认规则
      @Override
		protected void addResourceHandlers(ResourceHandlerRegistry registry) {
			super.addResourceHandlers(registry);
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			ServletContext servletContext = getServletContext();
			addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
        	//相当于获取你配置文件的spring.mvc.static-path.pattern=/**
			addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
				registration.addResourceLocations(this.resourceProperties.getStaticLocations());
				if (servletContext != null) {
					registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION));
				}
			});
		}

		//欢迎页的处理规则
    	@Bean
		public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
				FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
			WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
					new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
					this.mvcProperties.getStaticPathPattern());
			welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
			welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
			return welcomePageHandlerMapping;
    	...
    }
    ...
}

点击 WelcomePageHandlerMapping类

WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders,
			ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
		if (welcomePage != null && "/**".equals(staticPathPattern)) {
			logger.info("Adding welcome page: " + welcomePage);
			setRootViewName("forward:index.html");
		}
		else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
			logger.info("Adding welcome page template: index");
			setRootViewName("index");
		}
	}

image.png
倘若把index页放到了静态资源存放处,并且静态资源映射为 /**时,则直接ip:端口号访问会直接将index.html页渲染出来,前提是该访问路径没有任何接口

想自定义默认页的话,可以使用WebMvcConfigurer接口实现addViewControllers页面跳转

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
spring:
  mvc:
    static-path-pattern: /**

	# 将templates设为静态资源文件
  web:
    resources:
      static-locations: classpath:/templates
@Configuration
public class Myconfig extends WebMvcConfigurerAdapter{

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        //将访问路径跳转到templates下的login.html
        registry.addViewController("/").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
        super.addViewControllers(registry);
    }

}

3、Rest映射原理

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping

上述注解代替了@RequestMapping,并且可以根据相同的请求路径,去处理不同的crud操作,即使用HTTP请求方式动词来表示对资源的操作

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true   #开启页面表单的Rest功能
<form action="/user" method="get">
  <input value="REST-GET提交" type="submit" />
</form>

<form action="/user" method="post">
  <input value="REST-POST提交" type="submit" />
</form>

<form action="/user" method="post">
  <input name="_method" type="hidden" value="DELETE"/>
  <input value="REST-DELETE 提交" type="submit"/>
</form>

<form action="/user" method="post">
  <input name="_method" type="hidden" value="PUT" />
  <input value="REST-PUT提交"type="submit" />
  <form>

@GetMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.GET)
public String getUser(){
    return "GET-张三";
}

@PostMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.POST)
public String saveUser(){
    return "POST-张三";
}

@PutMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.PUT)
public String putUser(){
    return "PUT-张三";
}

@DeleteMapping("/user")
//@RequestMapping(value = "/user",method = RequestMethod.DELETE)
public String deleteUser(){
    return "DELETE-张三";
}

表单提交要使用REST的时候,表单提交会带上_method=PUT

提交之后便会被过滤器给拦截到

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    
    //HttpMethod:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
	private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

	
	public static final String DEFAULT_METHOD_PARAM = "_method";

	private String methodParam = DEFAULT_METHOD_PARAM;


	public void setMethodParam(String methodParam) {
		Assert.hasText(methodParam, "'methodParam' must not be empty");
		this.methodParam = methodParam;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		HttpServletRequest requestToUse = request;
                
    	//先判断前端是否是post请求
		if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
			//获取key为_method的value
            String paramValue = request.getParameter(this.methodParam);
			if (StringUtils.hasLength(paramValue)) {
				String method = paramValue.toUpperCase(Locale.ENGLISH);
				//去判断是否含有GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE等
                if (ALLOWED_METHODS.contains(method)) {
					requestToUse = new HttpMethodRequestWrapper(request, method);
				}
			}
		}
		//过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的
		filterChain.doFilter(requestToUse, response);
	}



	private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {

		private final String method;

		public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
			super(request);
			this.method = method;
		}

		@Override
		public String getMethod() {
			return this.method;
		}
	}

}

4、请求映射原理

SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet -> doDispatch()

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            //找到当前请求使用哪个Handler(Controller的方法)处理
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

进入该方法 getHandler(processedRequest);

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

发送 http://localhost:8800/hello 请求
image.png
image.png

所有的请求映射都在HandlerMapping中:

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html
  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping,请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。如果有就找到这个请求对应的handler,如果没有就是下一个 HandlerMapping。
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping。

5、请求处理常用注解

@PathVariable 路径变量
@RequestHeader 获取请求头
@RequestParam 获取请求参数(指问号后的参数,url?a=1&b=2)
@CookieValue 获取Cookie值
@RequestAttribute 获取request域属性
@RequestBody 获取请求体[POST]
@MatrixVariable 矩阵变量
@ModelAttribute

@RestController
public class ParameterTestController {


    //  car/2/owner/zhangsan
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){

        Map<String,Object> map = new HashMap<>();

        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
    }


    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }
}

原理是

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            //找到当前请求使用哪个Handler(Controller的方法)处理
            //最多的是 RequestMappingHandler
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            //为当前Handler 找一个适配器 HandlerAdapter
            //用的最多的是 RequestMappingHandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }

            applyDefaultViewName(processedRequest, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            // As of 4.3, we're processing Errors thrown from handler methods as well,
            // making them available for @ExceptionHandler methods and other scenarios.
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    }
    finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        }
        else {
            // Clean up any resources used by a multipart request.
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

点击 getHandlerAdapter(mappedHandler.getHandler());方法

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    }
    throw new ServletException("No adapter for handler [" + handler +
            "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

发送 http://localhost:8800/hello 请求
image.png
有四个 HandlerAdapter
0、支持方法上标注@RequestMapping
1、支持函数式编程的
因此会找到RequestMappingHandlerAdapter这个适配器

然后再进入这个方法 ha.handle(processedRequest, response, mappedHandler.getHandler());

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {

    ...
    
    //AbstractHandlerMethodAdapter类的方法,RequestMappingHandlerAdapter继承AbstractHandlerMethodAdapter
	public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {

        return handleInternal(request, response, (HandlerMethod) handler);
    }

}

点击 handleInternal(request, response, (HandlerMethod) handler);

@Override
protected ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav;
    //handleInternal的核心
    mav = invokeHandlerMethod(request, response, handlerMethod);
    //...
    return mav;
}

点击 invokeHandlerMethod(request, response, handlerMethod);

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                       HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    if (this.argumentResolvers != null) {//<-----关注点
        invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }

        ...
}

有27个参数解析器argumentResolvers
image.png

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
        HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);

        //27个解析器
        if (this.argumentResolvers != null) {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.setTaskExecutor(this.taskExecutor);
        asyncManager.setAsyncWebRequest(asyncWebRequest);
        asyncManager.registerCallableInterceptors(this.callableInterceptors);
        asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

        if (asyncManager.hasConcurrentResult()) {
            Object result = asyncManager.getConcurrentResult();
            mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
            asyncManager.clearConcurrentResult();
            LogFormatUtils.traceDebug(logger, traceOn -> {
                String formatted = LogFormatUtils.formatValue(result, !traceOn);
                return "Resume with async result [" + formatted + "]";
            });
            invocableMethod = invocableMethod.wrapConcurrentResult(result);
        }

        //真正执行目标方法在这
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }

        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}

点击 invocableMethod.invokeAndHandle(webRequest, mavContainer); 方法里的 invokeForRequest 方法

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
                           Object... providedArgs) throws Exception {
    //请求接口的参数 args
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    if (logger.isTraceEnabled()) {
    logger.trace("Arguments: " + Arrays.toString(args));
    }
    //通过反射执行接口方法
    return doInvoke(args);
}

点击getMethodArgumentValues(request, mavContainer, providedArgs);方法里,
再找到this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);方法,

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                              NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" +
                                           parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

再找到getArgumentResolver(parameter);方法

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    //现在缓存中找能够解析这个参数的参数解析器,不能找到则下一步
    //因此启动程序第一次调接口会有点慢
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
        //进行27个参数解析器匹配
        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
            //假如接口的参数能够跟该参数解析器匹配,则进行下一步处理,并把解析器放入内存里
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}

6、Servlet API参数解析原理

  • WebRequest
  • ServletRequest
  • MultipartRequest
  • HttpSession
  • javax.servlet.http.PushBuilder
  • Principal
  • InputStream
  • Reader
  • HttpMethod
  • Locale
  • TimeZone
  • ZoneId

ServletRequestMethodArgumentResolver用来处理以上的参数
在再找到this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);方法,

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                              NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" +
                                           parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    //看这里找到ServletRequestMethodArgumentResolver子类
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                              NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    Class<?> paramType = parameter.getParameterType();

    // WebRequest / NativeWebRequest / ServletWebRequest
    if (WebRequest.class.isAssignableFrom(paramType)) {
        if (!paramType.isInstance(webRequest)) {
            throw new IllegalStateException(
                "Current request is not of type [" + paramType.getName() + "]: " + webRequest);
        }
        return webRequest;
    }

    // ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
    if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
        return resolveNativeRequest(webRequest, paramType);
    }

    // HttpServletRequest required for all further argument types
    return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}

7、自定义参数绑定原理

@RestController
public class ParameterTestController {

    /**
     * 数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定
     * @param person
     * @return
     */
    @PostMapping("/saveuser")
    public Person saveuser(Person person){
        return person;
    }
}

/**
 *     姓名: <input name="userName"/> <br/>
 *     年龄: <input name="age"/> <br/>
 *     生日: <input name="birth"/> <br/>
 *     宠物姓名:<input name="pet.name"/><br/>
 *     宠物年龄:<input name="pet.age"/>
 */
@Data
public class Person {
    
    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;
    
}

@Data
public class Pet {

    private String name;
    private String age;

}

同理再找到this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);方法

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                              NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" +
                                           parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    //看这里找到ModelAttributeMethodProcessor子类
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
@Override
	@Nullable
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
		Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		if (ann != null) {
			mavContainer.setBinding(name, ann.binding());
		}

		Object attribute = null;
		BindingResult bindingResult = null;

		if (mavContainer.containsAttribute(name)) {
			attribute = mavContainer.getModel().get(name);
		}
		else {
			// Create attribute instance
			try {
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
					attribute = Optional.empty();
				}
				else {
					attribute = ex.getTarget();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
                    //web数据绑定器,将请求参数的值绑定到指定的JavaBean里面
					bindRequestParameters(binder, webRequest);
				}
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中,在过程当中,用到GenericConversionService:在设置每一个值的时候,找它里面的所有converter那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型。

8、响应处理

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
		implements BeanFactoryAware, InitializingBean {

    ...
    
	@Nullable
	protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
			HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		try {
			
            ...
            
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
                
			if (this.argumentResolvers != null) {
				invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
			}
			if (this.returnValueHandlers != null) {//<----关注点
				invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
			}

            ...

			invocableMethod.invokeAndHandle(webRequest, mavContainer);//看下块代码
			if (asyncManager.isConcurrentHandlingStarted()) {
				return null;
			}

			return getModelAndView(mavContainer, modelFactory, webRequest);
		}
		finally {
			webRequest.requestCompleted();
		}
	}

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {

    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    
    ...
    
    try {
        //看下块代码
        this.returnValueHandlers.handleReturnValue(
                returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        ...
    }
}

public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
    
    ...
    
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

        //selectHandler()实现在下面
		HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
		if (handler == null) {
			throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
		}
        //开始处理(使用消息转换器进行写出操作)
		handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
	}
    
   	@Nullable
	private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
		boolean isAsyncValue = isAsyncReturnValue(value, returnType);
        //15种HandlerMethodReturnValueHandler返回参数解析处理器
		for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
			if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
				continue;
			}
			if (handler.supportsReturnType(returnType)) {
				return handler;
			}
		}
		return null;
	}
    

@ResponseBody 注解,即RequestResponseBodyMethodProcessor,它实现HandlerMethodReturnValueHandler接口

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {

    ...
    
	@Override
	public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
			ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		mavContainer.setRequestHandled(true);
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

        // 使用消息转换器进行写出操作
		writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
	}

}


返回值处理器ReturnValueHandler原理:

  1. 返回值处理器判断是否支持这种类型返回值 supportsReturnType
  2. 返回值处理器调用 handleReturnValue 进行处理
  3. RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。

利用 MessageConverters 进行处理 将数据写为json

  1. 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
  2. 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
  3. SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
     1. 得到MappingJackson2HttpMessageConverter可以将对象写为json
     2. 利用MappingJackson2HttpMessageConverter将对象转为json再写出去。

五、拦截器

  1. 编写一个拦截器实现HandlerInterceptor接口
  2. 拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors())
  3. 指定拦截规则(注意,如果是拦截所有,静态资源也会被拦截)
public interface HandlerInterceptor {

	/**
     * 目标方法执行之前
     */
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}

	/**
     * 目标方法执行完成以后
     */
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	/**
     * 页面渲染以后
     */
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}

}
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 目标方法执行之前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String requestURI = request.getRequestURI();
        log.info("preHandle拦截的请求路径是{}",requestURI);
        //登录检查逻辑
        HttpSession session = request.getSession();
        Object loginUser = session.getAttribute("loginUser");
        if(loginUser != null){
            //放行
            return true;
        }
        //拦截住。未登录。跳转到登录页
        request.setAttribute("msg","请先登录");
        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

    /**
     * 目标方法执行完成以后
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandle执行{}",modelAndView);
    }

    /**
     * 页面渲染以后
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常{}",ex);
    }
}

@Configuration
public class AdminWebConfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginInterceptor())//拦截器注册到容器中
            .addPathPatterns("/**")  //所有请求都被拦截包括静态资源
            .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**",
                    "/js/**","/aa/**"); //放行的请求
}

原理:
DispatcherServlet中涉及到HandlerInterceptor的地方:

public class DispatcherServlet extends FrameworkServlet {
    
    ...
    
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

            	...
            
                //该方法内调用HandlerInterceptor的preHandle()
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            	...
                //该方法内调用HandlerInterceptor的postHandle()
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}			
        	processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
            //该方法内调用HandlerInterceptor接口的afterCompletion方法
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
            //该方法内调用HandlerInterceptor接口的afterCompletion方法
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			...
		}
	}

	private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception {

		if (mappedHandler != null) {
            //该方法内调用HandlerInterceptor接口的afterCompletion方法
			mappedHandler.triggerAfterCompletion(request, response, ex);
		}
		throw ex;
	}

	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable Exception exception) throws Exception {

        ...

		if (mappedHandler != null) {
            //该方法内调用HandlerInterceptor接口的afterCompletion方法
			// Exception (if any) is already handled..
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}


}

public class HandlerExecutionChain {

...

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        //HandlerInterceptor的preHandle方法
        if (!interceptor.preHandle(request, response, this.handler)) {
            
            triggerAfterCompletion(request, response, null);
            return false;
        }
        this.interceptorIndex = i;
    }
    return true;
}

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
        throws Exception {

    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        
        //HandlerInterceptor接口的postHandle方法
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            //HandlerInterceptor接口的afterCompletion方法
            interceptor.afterCompletion(request, response, this.handler, ex);
        }
        catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}


} 


六、单元测试

@Test:表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
@ParameterizedTest:表示方法是参数化测试。
@RepeatedTest:表示方法可重复执行。
@DisplayName:为测试类或者测试方法设置展示名称。 跟swagger的@ApiOperation注解类似
@BeforeEach:表示在每个单元测试之前执行。
@AfterEach:表示在每个单元测试之后执行。
@BeforeAll:表示在所有单元测试之前执行。
@AfterAll:表示在所有单元测试之后执行。
@Tag:表示单元测试类别,类似于JUnit4中的@Categories。
@Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore。
@Timeout:表示测试方法运行如果超过了指定时间将会返回错误。
@ExtendWith:为测试类或测试方法提供扩展类引用。

//倘若测试类的路径与主程序类的路径不一致,需要加上classes = 主程序类.class
@SpringBootTest(classes = EffortMallApplication.class)
public class TestController {

    @Autowired
    private RedisTemplate redisTemplate;

    @DisplayName("测试redis")
    @Test
    public void test(){
        redisTemplate.opsForValue().set("liyuejun","你好啊");
        redisTemplate.opsForHash().put("key","key1","我好");
        redisTemplate.opsForHash().put("key","key2","你好");
        redisTemplate.opsForList().leftPush("key2","1");
        redisTemplate.opsForList().leftPush("key2","2");
        redisTemplate.opsForList().leftPush("key2","3");
        redisTemplate.opsForList().leftPush("key2","4");
    }
}

七、指标监控

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 访问http://localhost:端口号/actuator/**。
  • 暴露所有监控信息为HTTP。
management:
  endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
      exposure:
        include: '*'  #以web方式暴露

测试例子
http://localhost:8080/actuator/beans
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause
http://localhost:8080/actuator/metrics/endpointName/detailPath

八、环境切换

为了方便多环境适配,Spring Boot简化了profile功能。

  • 默认配置文件application.yaml任何时候都会加载。
  • 指定环境配置文件application-{env}.yaml,env通常替代为test

激活指定环境:

  • 配置文件激活:spring.profiles.active=prod
  • 命令行激活:java -jar xxx.jar --spring.profiles.active=prod --person.name=haha(修改配置文件的任意值,命令行优先)

默认配置与环境配置同时生效,同名配置项,profile配置优先

# 激活生产环境
spring.profiles.active=prod

-resources
-application.yaml
-application-proddb.yaml
-application-prodmq.yaml
-application-prodtest.yaml

# prod为下面的数组
spring.profiles.active=prod

# 可以激活两个配置文件
spring.profiles.group.prod[0]=proddb
spring.profiles.group.prod[1]=prodmq

配置文件加载

  • 外部配置源(优先级最下面是最高的)
    • Java属性文件。(java类)
    • YAML文件。
    • 环境变量。
    • 命令行参数。
  • 配置文件查找位置(优先级最下面是最高的)
    • classpath 根路径。
    • classpath 根路径下config目录。
    • jar包当前目录。
    • jar包当前目录的config目录。
    • /config子目录的直接子目录。

image.png

  • 配置文件加载顺序(优先级最下面是最高的)
    • 当前jar包内部的application.properties和application.yml。
    • 当前jar包内部的application-{profile}.properties 和 application-{profile}.yml。
    • 引用的外部jar包的application.properties和application.yml。
    • 引用的外部jar包的application-{profile}.properties和application-{profile}.yml。

九、starter启动原理

练习:

1、首先先创建一个空项目,空项目里包括两个模块
image.png

  • hello-spring-boot-starter 为场景启动器
  • hello-spring-boot-starter-autoconfigure 为自动配置

2、在场景启动器的依赖引入自动配置包
image.png

自动配置包则引入spring-boot-start依赖
image.png

3、在自动配置包里:
包路径:
image.png

//配置绑定
@ConfigurationProperties("hello")
public class HelloProperties {

    private String prefix;

    private String suffer;

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public String getSuffer() {
        return suffer;
    }

    public void setSuffer(String suffer) {
        this.suffer = suffer;
    }
}

默认不把HelloService放在容器中

/**
 * @author yuejun.li
 * @date 2023/8/4 22:05:55
 * 默认不要放在容器中
 */
public class HelloService {

    @Autowired
    private HelloProperties helloProperties;

    public String sayHello(String userName){
        return helloProperties.getPrefix() + " " + userName + " " + helloProperties.getSuffer();
    }
}
@Configuration
//容器中没有 HelloService 类型的 bean 才添加进容器中
@ConditionalOnMissingBean(HelloService.class)
//自动配置绑定,并向容器中添加 HelloProperties 类型的 bean
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {

    //添加 HelloService 组件
    @Bean
    public HelloService helloService(){
        return new HelloService();
    }
}
# 需要springboot 的另一种方式扫描该配置文件才知道要注入哪些配置类到容器里
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.jun.hello.auto.HelloServiceAutoConfiguration

在场景启动器:
包路径:
image.png

4、在自动配置包和场景启动器的maven菜单里各自把clean与install命令执行,把它们的依赖放入maven中
image.png

5、去新的项目去搭建springboot,并且把场景启动器的依赖引进来

  • 引进来,等主程序一启动,便会扫描到自动配置包里的spring.factories文件,把里面的配置类全注入到spring容器里
  • 当该项目没有 HelloService 组件,则会默认添加到spring容器里

image.png

6、配置文件配好与上述自动配置包的信息
image.png

7、控制类写好
把HelloService注入进来
image.png

8、结果
image.png

posted @ 2023-08-09 14:03  啊俊同学  阅读(60)  评论(0)    收藏  举报