springBoot核心原理

一、配置文件

yaml的写法

##map的写法1
friends: {lastName: zhangsan,age: 18}
##map的写法2
friends:
		lastName: zhangsan
		age: 20
###list、set写法1		
pets:
 - cat
 - dog
 - pig		
###list、set写法2
pets: [cat,dog,pig]
##单引号会将\n作为字符串输出,双引号会将\n作为换行输出
##双引号不会转义,单引号会转义

想要代码配置可以提示,添加如下依赖

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

在打包的时候可以exclude相关配置

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

二、Web开发

2.1 静态资源访问

1、静态资源目录
静态资源目录:类路径下如下目录

/static 
/public
/resources
/META-INF/resources

访问:当前项目根路径/+静态资源名

原理:静态映射/**
请求进来,先去controller看能不能处理,不能处理的所有请求又都交给静态资源处理器。

2、静态资源访问前缀
默认无前缀,可以在配置文件中修改
访问路径:当前项目+static-path-pattern+静态资源名=静态资源文件夹下找

spring.mvc.static-path-pattern: /res/**

修改静态资源的存放路径

spring:
  resources:
    static-location: classpath:/haha/

3、webjar
自动映射

WebJars是将web前端资源(js,css等)打成jar包文件,然后借助Maven工具,以jar包形式对web前端资源进行统一依赖管理,保证这些Web资源版本唯一性。WebJars的jar包部署在Maven中央仓库上。

访问地址:webjars/jquery/3.5.1/jquery.js

2.2 欢迎页支持

静态资源路径下 index.html
可以配置静态资源路径;但是不可以配置静态资源的访问前缀,否则导致index.html不能被默认访问。

spring.mvc.static-path-pattern: /res/**

controller能处理/index

2.3 自定义favicon

图标放在静态资源目录下即可

spring.mvc.static-path-pattern: /res/**

也会影响图标的显示

2.4 静态资源配置原理

springBoot启动默认加载xxxAutoConfiguration(自动配置类)
SpringMVC功能的自动配置类 WebMvcAutoConfiguration

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
@AutoConfigureOrder(-2147483638)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
     @Configuration(
        proxyBeanMethods = false
    )
    @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
    @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {

配置文件的相关属性和xxx进行了绑定:WebMvcProperties(spring.mvc)和ResourceProperties(spring.resources)

配置类只有一个有参构造器

//有参构造器所有参数的值都会从容器中确定
//ResourceProperties 获取和spring.resources绑定的所有值的对象
//WebMvcProperties 获取和spring.mvc绑定的所有值的对象
//ListableBeanFactory beanFactory springbean工厂
//HttpMessageConverters 找到所有的HttpMessageConverters
//resourceHandlerRegistrationCustomizer 找到资源处理器的自定义器
//ServletRegistrationBean 给应用注册sevlet,filter...
   @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
    @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
        private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
        private final ResourceProperties resourceProperties;
        private final WebMvcProperties mvcProperties;
        private final ListableBeanFactory beanFactory;
        private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
        private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;
        private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;
        final WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;

2、资源处理的默认规则

        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            if (!this.resourceProperties.isAddMappings()) {
                logger.debug("Default resource handling disabled");
            } else {
                Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
                CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
                //webjars的规则
                if (!registry.hasMappingForPattern("/webjars/**")) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

                String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

            }
        }
##禁用所有静态资源规则
spring:
  resources:
    add-mappings: false
public class ResourceProperties {
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
    private String[] staticLocations;
    private boolean addMappings;
    private final ResourceProperties.Chain chain;
    private final ResourceProperties.Cache cache;

    public ResourceProperties() {
        this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

3、欢迎页的处理规则

//HandlerMapping 处理器映射,保存了每个Handler能处理哪些请求
        @Bean
        public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
            WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
            welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
            welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
            return welcomePageHandlerMapping;
        }
    WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Optional<Resource> welcomePage, String staticPathPattern) {
    //要用欢迎页功能,必须是/**
        if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
            logger.info("Adding welcome page: " + welcomePage.get());
            this.setRootViewName("forward:index.html");
        } else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
           //调用controller /index
            logger.info("Adding welcome page template: index");
            this.setRootViewName("index");
        }

    }

3、请求参数处理
0、请求映射
@xxxmapping
rest风格支持:使用http请求方式动词来表示对资源的操作。
以前:/getUser,/addUser, /deleteUser,/editUser
现在:/user GE
T–请求 POST-新增 PUT–修改 DELETE–删除
核心filter:HiddenHttpMethodFilter
用法:表单method=put,隐藏域_method=put

//springboot默认不开启
   @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"},
        matchIfMissing = false
    )
<form action="/user" method="post">
	<input name="_method" type="hidden" value="delete"/>
	<input value="rest-delete提交" type="submit"/>
</form>

rest原理(表单提交使用rest)
表单提交会带上_method=put
请求过来被HiddenHttpMethodFilter
请求是否正常,并且是post
获取_method的值
兼容以下请求:delete,put,patch
原生request(post),包装模式requestWrapper重写了getMethod方法,返回的是传入的值
过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requestWrapper的。

rest使用客户端工具
如postman直接发送put,delete请求,无需filter

请求映射原理
在这里插入图片描述
1、httpservlet的doGet方法
2、frameworkservlet的processRequest–>doService
3、Dispatchservlet的doService实现 doDispatch(每个方法都会调用)

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 {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    //找到当前请求使用哪个handler(controller)处理
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

所有的请求映射都保存在HandlerMapping(处理器映射)中
	1、springBoot自动配置欢迎页的WelcomePageHandlerMapping,访问/会访问到index.html
	2、RequestMappingHandlerMapping:保存了所有@RequestMapping和handler的映射规则,springBoot自动配置
	3、请求进来挨个尝试所有的handlerMapping看是否有请求信息
	4、我们也可以自定义handlerMapping

一、普通参数与基本注解
1、注解

@PathVariable 路径变量
@RequestParam 请求参数
@RequestHeader 请求头
@CookieValue cookie值
@RequestAttribute request域属性
@RequestBody 请求体
@MatrixVariable 矩阵变量

 @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 Cookie cookie){
                                      
    @PostMapping("/save")
    public Map post(@RequestBody String content){

@Controller
public class RequestController {

    @GetMapping("/goto")
    public String gotoPage(HttpServletRequest request){
        request.setAttribute("msg","成功了。。");
        return "forward:/success";//转发到/success请求
    }

    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute("msg") String msg,
                       HttpServletRequest request){
        Map<String, Object> map = new HashMap<>();
        map.put("annotation_msg",msg);
        map.put("reqMethod_msg",msg);
        return map;
    }
}

/cars/{path}?aaa=bbb&ccc=ddd queryString查询字符串 @RequestParam
/cars/{path;low=24;brand=byd,audi} 矩阵变量
页面开发的时候,如果cookie被禁用了,session里面的内容怎么使用
session.set(a,b)---->jsessionid---->cookie----->每次请求都携带
url重写的方式:/adc;jseesionid=xxx 把cookie的值使用矩阵变量的方式进行传递
如/cars/sell;low=34;brand=byd,audi
springBoot默认禁用了矩阵变量的功能
手动开启的原理:对于路径的处理,urlPathHelper进行解析,setRemoveSemicolonContent设置false来支持矩阵变量
矩阵变量必须有url路径变量才能被解析
  @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age", pathVar = "bossId") Integer bossAge,
                @MatrixVariable(value = "age", pathVar = "empId") Integer empAge){

参数处理的原理
1、HandlerMapping中找到处理请求的Handler(Controller.method())
2、为当前Handler找一个适配器 HandlerAdapter
RequestMappingHandlerAdapter–支持方法上标注@RequestMapping的
HandlerFunctionAdapter–支持函数式编程的

//DispatchServlet中的doDispatch方法中
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

//RequestMappingHandlerAdapter的handleInternal方法中
//执行目标方法
            mav = this.invokeHandlerMethod(request, response, handlerMethod);

ServletInvocableHandlerMethod
        Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);

//InvocableHandlerMethod
//5、如何确定目标方法的每个参数值
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();
		if (ObjectUtils.isEmpty(parameters)) {
			return EMPTY_ARGS;
		}

		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			args[i] = findProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			if (!this.resolvers.supportsParameter(parameter)) {
				throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
			}
			try {
				args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
			}
			catch (Exception ex) {
				// Leave stack trace for later, exception may actually be resolved and handled...
				if (logger.isDebugEnabled()) {
					String exMsg = ex.getMessage();
					if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
						logger.debug(formatArgumentError(parameter, exMsg));
					}
				}
				throw ex;
			}
		}
		return args;
	}

//5.1 遍历判断所有参数解析器哪个支持对应的参数
/**
	 * Find a registered {@link HandlerMethodArgumentResolver} that supports
	 * the given method parameter.
	 */
	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
				if (resolver.supportsParameter(parameter)) {
					result = resolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}
	5.2 解析这个参数的值
	调用各自HandlerMethodArgumentResolver的resolveArgument方法即可
	5.3 自定义类型参数 封装POJO
	ServletModelAttributeMethodProcessor这个参数处理器支持
	6、目标方法执行完成
	将所有的数据放在ModelAndViewContainer;包含要去的页面地址view,还包含Model数据
  7、处理派发方法
  DispatcherServlet中
  			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

AbstractView中
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);

InternalResourceView中
@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// Expose the model object as request attributes.暴露模型作为请求域属性
		exposeModelAsRequestAttributes(model, request);

		// Expose helpers as request attributes, if any.
		exposeHelpers(request);

		// Determine the path for the request dispatcher.
		String dispatcherPath = prepareForRendering(request, response);

		// Obtain a RequestDispatcher for the target resource (typically a JSP).
		RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
		if (rd == null) {
			throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
					"]: Check that the corresponding file exists within your web application archive!");
		}

		// If already included or response already committed, perform include, else forward.
		if (useInclude(request, response)) {
			response.setContentType(getContentType());
			if (logger.isDebugEnabled()) {
				logger.debug("Including [" + getUrl() + "]");
			}
			rd.include(request, response);
		}

		else {
			// Note: The forwarded resource is supposed to determine the content type itself.
			if (logger.isDebugEnabled()) {
				logger.debug("Forwarding to [" + getUrl() + "]");
			}
			rd.forward(request, response);
		}
	}

AbstractView中
protected void exposeModelAsRequestAttributes(Map<String, Object> model,
			HttpServletRequest request) throws Exception {
//model中的所有数据挨个遍历放到请求域中
		model.forEach((name, value) -> {
			if (value != null) {
				request.setAttribute(name, value);
			}
			else {
				request.removeAttribute(name);
			}
		});
	}

参数解析器(argumentResolver):确定将要执行的目标方法的每一个参数的值是什么 。
SpringMVC目标方法能写多少种参数类型,取决于参数解析器。
在这里插入图片描述
argumentResolver的工作机制
a)判断解析器是否支持解析这种参数
b)支持就调用resolveArgument

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

ServletRequestMethodArgumentResolver解析器支持以上类型


	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
				Principal.class.isAssignableFrom(paramType) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

3、复杂参数
Map
Errors/BindingResult
Model
RedirectAtrributes
ServletResponse
SessionStatus
UriComponentBuilder
ServletUriComponentBuilder

Map、Model里面的数据会被放在request的请求域中,相当于request.setAttribute,可以通过request.getAttribute获取
RedirectAttributes 重定向携带数据
ServletResponse Response

Map、Model类型的参数,会返回mavContainer.getModel();—>BindingAwareModelMap 是Model也是map
Model和map的处理对象相同

1.4、自定义对象参数
可以自动类型转换与格式化,可以级联封装

数据绑定:页面提交的请求数据(GET、POST)都可以和对象属性进行绑定

POJO封装过程
自定义封装类型由ServletModelAttributeMethodProcessor来封装解析。
是否为简单类型

BeanUtils
public static boolean isSimpleValueType(Class<?> type) {
		return (Void.class != type && void.class != type &&
				(ClassUtils.isPrimitiveOrWrapper(type) ||
				Enum.class.isAssignableFrom(type) ||
				CharSequence.class.isAssignableFrom(type) ||
				Number.class.isAssignableFrom(type) ||
				Date.class.isAssignableFrom(type) ||
				Temporal.class.isAssignableFrom(type) ||
				URI.class == type ||
				URL.class == type ||
				Locale.class == type ||
				Class.class == type));
	}

ModelAttributeMethodProcessor
	@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();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
					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:web数据绑定器,将请求参数的值绑定到指定的javaBean里面
WebDataBinder利用它里面的converters将请求数据转成指定的数据类型,再次封装到JavaBean中
GenericConversionService:在设置每一个值的时候,找它里面的所有converter,哪个可以将这个数据类型转换(request带来参数的字符串)到指定的类型(比如JavaBean中的Integer)
也有非字符串类型,比如流byte转成file类型

//可以给WebDataBinder里面放自己的Converter
    private static final class StringToNumber<T extends Number> implements Converter<String, T> {
//Converter总接口
public interface Converter<S, T> {

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值