# spring-cloud-rest-demo **Repository Path**: flaya/spring-cloud-rest-demo ## Basic Information - **Project Name**: spring-cloud-rest-demo - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-01-06 - **Last Updated**: 2022-01-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #1.1 REST课程 springboot版本:2.0.8 rest是一种风格、规范,不是技术 https://github.com/fankun1245067282/spring-cloud-rest-demo.git ##幂等 PUT 初始状态:0 修改状态:1*N(N次操作之后) 最终状态:1 DELETE 初始状态:1 修改状态:0*N(N次操作之后) 最终状态:0 ## 非幂等 POST 初始状态:1 修改状态:1+1=2 N次修改:1+N=N+1 最终状态:N+1 幂等/非幂等依赖于服务端实现,这种方式是一种契约 post方法也可以实现幂等性,这需要服务端加以控制 ##maven导入 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-web ``` 若想搜索使用的类,需要导入源码 springmvc有一个@EnableWebmvc ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { } ``` 点击:DelegatingWebMvcConfiguration 进去查看 ```java @Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { ``` 点进去:WebMvcConfigurationSupport 里面可以看到: ```java public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware { private static final boolean romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", WebMvcConfigurationSupport.class.getClassLoader()); private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", WebMvcConfigurationSupport.class.getClassLoader()); private static final boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", WebMvcConfigurationSupport.class.getClassLoader()) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", WebMvcConfigurationSupport.class.getClassLoader()); private static final boolean jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", WebMvcConfigurationSupport.class.getClassLoader()); ......略,还有其他类型 ``` 选择 `jackson2Present` 里面的字符串,`com.fasterxml.jackson.databind.ObjectMapper` 点击shift,shift搜索类是存在,搜索`jackson2XmlPresent`对应的字符串的类是不存在 它们都是`boolean`类型的,表示这些字符串对应的类是否存在,如果存在才会支持对应的`mediaType`,即是才会对应的`contentType`或者`accept` WebMvcConfigurationSupport ```java protected Map getDefaultMediaTypes() { Map map = new HashMap<>(4); if (romePresent) { map.put("atom", MediaType.APPLICATION_ATOM_XML); map.put("rss", MediaType.APPLICATION_RSS_XML); } if (jaxb2Present || jackson2XmlPresent) { map.put("xml", MediaType.APPLICATION_XML); } if (jackson2Present || gsonPresent || jsonbPresent) { map.put("json", MediaType.APPLICATION_JSON); } if (jackson2SmilePresent) { map.put("smile", MediaType.valueOf("application/x-jackson-smile")); } if (jackson2CborPresent) { map.put("cbor", MediaType.valueOf("application/cbor")); } return map; } ``` 可以获取默认支持的`mediaType`,就是对应的`contentType`, `contentType`表示请求或者响应的数据类型; `accept`表示请求时可以接受的返回数据类型; 示例: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 q=0.8是权重的意思,权重高的优先返回 java代码示例: ```java public class Person { private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + '}'; } } ``` ```java @RestController public class PersonRestController { //测试URL:http://localhost:8080/person/1?name=%E6%A8%8A%E5%9D%A4 @GetMapping("/person/{id}") public Person person(@PathVariable Long id, @RequestParam(required = false) String name){ Person person = new Person(); person.setId(id); person.setName(name); return person; } } ``` ```java import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RestOnSpringWebmvc { public static void main(String[] args) { SpringApplication.run(RestOnSpringWebmvc.class,args); } } ``` 使用url:http://localhost:8080/person/3?name=樊坤 进行测试,返回的是: ```json { "id": 3, "name": "樊坤" } ``` 为什么返回的是json格式,不是其他类型的格式,例如:xml格式???? 因为:`jackson2Present` 是true,对应的类`com.fasterxml.jackson.databind.ObjectMapper`存在 `jackson2XmlPresent`是false,对应的类`com.fasterxml.jackson.dataformat.xml.XmlMapper`不存在 现在我们导入`com.fasterxml.jackson.dataformat.xml.XmlMapper`的包,在maven仓库中搜索 ```xml com.fasterxml.jackson.dataformat jackson-dataformat-xml ``` 再进行测试:http://localhost:8080/person/3?name=樊坤 ```xml 3 樊坤 ``` 为什么这次返回xml格式的了??? ##自描述信息 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 q=0.8是权重的意思,权重高的优先返回 这是客户端要求返回的吧?? 第一优先顺序: text/html-->application/xhtml+xml-->application/xml 第二优先顺序:image/webp-->image/apng 使用postman测试,返回结果还是json格式; 可以在postman中指定请求头,headers[Accept:application/xml] 这样就可以了 ##查看源代码 @EnableWebmvc--> ​ DelegatingWebMvcConfiguration--> ​ WebMvcConfigurationSupport.addDefaultHttpMessageConverters ```java protected final void addDefaultHttpMessageConverters(List> messageConverters) { StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316 messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(stringHttpMessageConverter); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new ResourceRegionHttpMessageConverter()); messageConverters.add(new SourceHttpMessageConverter<>()); messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { messageConverters.add(new AtomFeedHttpMessageConverter()); messageConverters.add(new RssChannelHttpMessageConverter()); } if (jackson2XmlPresent) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build())); } else if (jaxb2Present) { messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); } if (jackson2Present) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build())); } else if (gsonPresent) { messageConverters.add(new GsonHttpMessageConverter()); } else if (jsonbPresent) { messageConverters.add(new JsonbHttpMessageConverter()); } if (jackson2SmilePresent) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build())); } if (jackson2CborPresent) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build())); } } ``` 返回的是一个List,是按顺序服务的 `jackson2XmlPresent`在j`ackson2Present`之前 查看所有方法的快捷键:ctrl+F12 为什么什么都不写,返回的是json,客户端accept中没有json类型呀?? WebMvcConfigurationSupport.getMessageConverters,是addDefaultHttpMessageConverters使用的地方 所有的Http自描述信息都在messageConverters中,这个集合会传递到RequestMappingHandlerAdapter,最终控制写出。 以application/json为例,Spring Boot中默认使用jackson2序列化方式 媒体类型,application/json,它的处理类是: ``` org.springframework.http.converter.json.MappingJackson2HttpMessageConverter 提供两种方法: 1、read*,通过http请求内容转化成对应的Bean 2、write*,通过序列化成对应内容作为响应内容 ``` ```java protected final List> getMessageConverters() { if (this.messageConverters == null) { this.messageConverters = new ArrayList<>(); configureMessageConverters(this.messageConverters);//这个是配置的 if (this.messageConverters.isEmpty()) { addDefaultHttpMessageConverters(this.messageConverters); } extendMessageConverters(this.messageConverters);//这个是扩展的,自己写的,本类中的方法 } return this.messageConverters; } ``` WebMvcConfigurationSupport.requestMappingHandlerAdapter,调用 ```java @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); adapter.setContentNegotiationManager(mvcContentNegotiationManager());/////搜索ContentNegotiationManager这个类 adapter.setMessageConverters(getMessageConverters()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer()); adapter.setCustomArgumentResolvers(getArgumentResolvers()); adapter.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice())); adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice())); } AsyncSupportConfigurer configurer = new AsyncSupportConfigurer(); configureAsyncSupport(configurer); if (configurer.getTaskExecutor() != null) { adapter.setTaskExecutor(configurer.getTaskExecutor()); } if (configurer.getTimeout() != null) { adapter.setAsyncRequestTimeout(configurer.getTimeout()); } adapter.setCallableInterceptors(configurer.getCallableInterceptors()); adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors()); return adapter; } ``` 打开这个类: org.springframework.web.accept.ContentNegotiationManager ```java public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver { private final List strategies = new ArrayList<>(); private final Set resolvers = new LinkedHashSet<>(); ......略。。。 ``` ```java @FunctionalInterface public interface ContentNegotiationStrategy {//支持所有类型,MediaType.ALL /** * A singleton list with {@link MediaType#ALL} that is returned from * {@link #resolveMediaTypes} when no specific media types are requested. * @since 5.0.5 */ List MEDIA_TYPE_ALL_LIST = Collections.singletonList(MediaType.ALL); ``` ```java @Override public List resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { for (ContentNegotiationStrategy strategy : this.strategies) { List mediaTypes = strategy.resolveMediaTypes(request); if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) { continue; } return mediaTypes; } return MEDIA_TYPE_ALL_LIST; } ``` ContentNegotiationManager.resolveMediaTypes 解析请求类型 问题:为什么第一次是json,增加了xml 依赖之后,就变成了xml了 答:spring boot默认没有增加xml处理实现,所以最后采用轮询的方式逐一尝试是否canWrite(Pojo), 如果返回ture,说明可以序列化该Pojo对象,那么Jackson2恰好能处理,那么Jackson输出了。 问题:当accept请求头未必指定时,为什么还是JSON来处理? 答:这依赖于messageConverters的插入顺序。 问题:messageConverters里面的顺序可以修改吗? 答:可以修改,怎么改: ```java @Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); @Autowired(required = false)//这个是自己配置的mediaType,不是必须的 。。。实现WebMvcConfigurer public void setConfigurers(List configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } } ......略。。。 ``` ```java protected final List> getMessageConverters() { if (this.messageConverters == null) { this.messageConverters = new ArrayList<>(); configureMessageConverters(this.messageConverters);//添加自己想要的mediaType,如果不为空,就不添加默认的了。。。。。。 if (this.messageConverters.isEmpty()) { addDefaultHttpMessageConverters(this.messageConverters); } extendMessageConverters(this.messageConverters); } return this.messageConverters; } ``` WebMvcConfigurationSupport.getMessageConverters ## 自己添加mediaType ```java import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class WebMvcConfig implements WebMvcConfigurer { /** * 添加自己的mediaType * @param converters */ // public void configureMessageConverters(List> converters) { // System.out.println("converters不为空,所有的默认的都已经加载进来了:"+converters); // Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json(); // converters.add(new MappingJackson2HttpMessageConverter(builder.build())); // System.out.println(""); // } /** * 添加自己的mediaType,把xml默认添加到第一个 * @param converters */ public void extendMessageConverters(List> converters) { System.out.println("converters不为空,所有的默认的都已经加载进来了:"+converters); Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json(); converters.add(0,new MappingJackson2XmlHttpMessageConverter()); converters.add(0,new MappingJackson2HttpMessageConverter(builder.build())); System.out.println(""); } } ``` 这样可以修改默认的顺序,自己把默认的重新清空,按照自己的顺序添加 ##自定义mediaType 自己添加扩展一个Properties格式,待扩展,之前的返回结果: XML格式:(Accept :application/xml ) ```xml 3 樊坤 ``` JSON格式:(Accept :application/json) ```json { "id": 3, "name": "樊坤" } ``` Properties格式:(Accept :application/properties+person)(需要扩展) ```properties person.id=3 person.name=樊坤 ``` 实现参考:MappingJackson2XmlHttpMessageConverter 继承:org.springframework.http.converter.AbstractHttpMessageConverter ```java import Person; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.AbstractGenericHttpMessageConverter; import org.springframework.http.converter.AbstractHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import java.io.*; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.Properties; /** * Person Properties自描述消息处理 */ public class PerpertiesPersonHttpMessageConverter extends AbstractHttpMessageConverter { /** * 添加所支持的类型的参数。。。 */ public PerpertiesPersonHttpMessageConverter() { super(MediaType.valueOf("application/properties+person")); setDefaultCharset(Charset.forName("UTF-8")); } @Override protected boolean supports(Class clazz) { //必须是Person的子类才支持 return clazz.isAssignableFrom(Person.class); } /** * 读,把properties类型的请求内容转化为Bean(Person)类型 * @param clazz * @param inputMessage * @return * @throws IOException * @throws HttpMessageNotReadableException */ @Override protected Person readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { InputStream inputStream = inputMessage.getBody(); /** * person.id=3 * person.name=樊坤 */ Properties properties = new Properties(); //将请求中的内容转换为Properties properties.load(new InputStreamReader(inputStream,getDefaultCharset())); //将Properties内容转为Person对象中 Person person = new Person(); person.setId(Long.valueOf(properties.getProperty("person.id"))); person.setName(properties.getProperty("person.name")); return person; } /** * ,把properties类型的请求内容转化为Bean(Person)类型 * @param clazz * @param inputMessage * @return * @throws IOException * @throws HttpMessageNotReadableException */ /** * 写,把Bean(Person)类型返回内容转化为properties类型的 * @param person * @param outputMessage * @throws IOException * @throws HttpMessageNotWritableException */ @Override protected void writeInternal(Person person, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { OutputStream outputStream = outputMessage.getBody(); Properties properties = new Properties(); properties.setProperty("person.id",String.valueOf(person.getId())); properties.setProperty("person.name",person.getName()); properties.store(new OutputStreamWriter(outputStream,getDefaultCharset()),"written by web server"); } } ``` supports方法,是否支持pojo类型 readInternal方法,读取http请求,转换为pojo对象 writeInternal方法,将pojo对象序列化为文本内容(properties内容) 添加到配置中: ```java import PerpertiesPersonHttpMessageConverter; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration public class WebMvcConfig implements WebMvcConfigurer { /** * 添加自己的mediaType * @param converters */ public void extendMessageConverters(List> converters) { System.out.println("converters不为空,所有的默认的都已经加载进来了:"+converters); converters.add(new PerpertiesPersonHttpMessageConverter()); System.out.println(""); } } ``` ```java ``` 使用postman测试 ###测试1 url:localhost:8080//person/json/to/properties headers[{"key":"Content-Type","value":"application/json;charset=UTF-8"}] headers[{"key":"Accept","value":"application/properties+person","description":"","enabled":true}] type:post params: ```json { "id": 3, "name": "樊坤" }result: ``` ```properties #written by web server #Thu Jan 17 15:53:17 CST 2019 person.name=樊坤 person.id=3 ``` ###测试2 url:localhost:8080/person/properties/to/json headers[{"key":"Content-Type","value":"application/properties+person "}] headers[{"key":"Accept","value":"application/json;charset=UTF-8","description":"","enabled":true}] type:post params: ```properties person.name=樊坤 person.id=3 ``` result: ```json { "id": 3, "name": "樊坤" } ``` RequestMapping中: consumers-->content-type producers-->accept HttpMessageConverter执行逻辑 *读操作,尝试是否能读取,canRead方法去尝试,如果返回true,下一步执行read *写操作,尝试是否能写取,canWrite方法去尝试,如果返回true,下一步执行write # 1.2 WEBMVC课程(含flux) j2ee 核心模式 mvc m : model v : view c : contoller-->dispatchServlet Font Controller --> DispatchServlet Application Controller --> @Controller ServletContextListener --> ContextLoaderListener--> Root WebApplicaitonContext(根) DispatcherServlet--> Servlet WebApplicationContext(上层) Services=>@Services Repositories=>@Repositories ##映射处理 ###Servlet请求映射 --Servlet URL Pattern --Filter URL Pattern ### Spring Web MVC --DispatcherServlet --HandlerMapping 用eclipse建立dynamic web project 新建一个servlet3.0的servlet,只是简单测试 ## 代码模块,在spring-cloud-rest-demo中 ###文件夹:com.fankun.webmvc DispatcherServlet <-- FrameworkServlet <-- HttpServletBean <-- HttpServlet 映射关系: org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration ServletContext Path = "" 或者 ”/" Request URI = ServletContext Path + RequestMapping("")/@getMapping("")/@PostMapping("") Spring Web MVC的配置Bean : WebMvcProperties Spring Boot 允许通过 application.properties去定义配置,配置外部化 ```java @ConfigurationProperties(prefix = "spring.mvc") //配置前缀 public class WebMvcProperties { ``` WebMvcProperties的前缀:spring.mvc RestDemoController#index() HandlerMapping,寻找Request URI匹配的Handler Handler是处理的方法,当然这是一种实例 Request-->Handler-->执行结果-->返回(REST)-->普通的文本 (和Spring MVC源码讲解的差不多) HandlerMapping-->RequestMappingHandlerMapping(@RequestMapping Handler Mapping) @RequestMapping默认处理@GetMapping请求 @PostMapping==@RequestMapping(method=RequestMethod.POST) Create(C) @GetMapping==@RequestMapping(method=RequestMethod.GET) Read(R) @PutMapping==@RequestMapping(method=RequestMethod.PUT) Update(U) @DeleteMapping==@RequestMapping(method=RequestMethod.DELETE) Delete(D) CRUD windows通过postman测试 linux: curl -X POST 拦截器:HandlerInterceptor ```java package com.fankun.webmvc.controller; import org.springframework.web.bind.annotation.*; @RestController//==@Controller+@ResponseBody public class RestDemoController { //测试URL:http://localhost:8080/hello @GetMapping("/hello") public String index(){ return "hello,world"; } } ``` ```java package com.fankun.webmvc.interceptor; import org.springframework.lang.Nullable; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class DefaultHandlerInterceptor implements HandlerInterceptor{ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println(handler.toString()); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } } ``` 上面为自定义handlerInterceptor ## 添加自定义Handler ```java @SpringBootApplication public class RestOnSpringWebmvc extends WebMvcConfigurerAdapter{ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new DefaultHandlerInterceptor()); } public static void main(String[] args) { SpringApplication.run(RestOnSpringWebmvc.class,args); } } ``` ### HandlerInteceptor处理逻辑 处理顺序:preHandle(true)-->Handler:[HandlerMethod执行(Method#invoke)]-->postHandle(true)-->afterCompletion Handler不一定是HandlerMethod,所以在拦截器中是Object类型 ## 异常处理 ### Servlet标准 ####返回参数 Request Attributes Type javax.servlet.error.status_code java.lang.Integer javax.servlet.error.exception_type java.lang.Class javax.servlet.error.message java.lang.String javax.servlet.error.exception java.lang.Throwable javax.servlet.error.request_uri java.lang.String javax.servlet.error.servlet_name java.lang.String ####理解web.xml错误页面 处理逻辑: 处理状态码: 处理异常类型: 处理服务: 优点:业界的标准,统一处理 缺点:灵活度不够,只能定义在web.xml文件里面 ```xml 404 /404.html PageNotFoundServlet PageNotFoundServlet com.fankun.PageNotFoundServlet PageNotFoundServlet /404.html ``` 找不到页面会跳转到url为:/404.html 然后找到PageNotFoundServlet进行处理 ```java import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class PageNotFoundServlet extends HttpServlet { private static final long serialVersionUID = 1L; public PageNotFoundServlet() { super(); // TODO Auto-generated constructor stub } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub response.setContentType("text/html;charset=UTF-8"); PrintWriter writer = response.getWriter(); writer.write("页面没有找到"); writer.close(); System.out.println("============================="); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } ``` ### Spring Web MVC 第一, 需要注意Spring MVC 和 Spring Rest两种情况下的区别。 Spring MVC是可以通过增加/error的handler来处理异常的, 而REST却不行,因为在spring Rest中, 当用户访问了一个不存在的链接时, Spring 默认会将页面重定向到 **/error** 上, 而不会抛出异常。 处理方法是,在application.properties文件中, 增加下面两项设置 ``` spring.mvc.throw-exception-if-no-handler-found=true spring.resources.add-mappings=false ``` 添加处理器: @RestControllerAdvice==@ControllerAdvice+@ResponseBody basePackages属性指定扫描的路径,否则是指当前路径下 ``` @ControllerAdvice(basePackages = "com.fankun.webmvc.controller") ``` ```java import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.NoHandlerFoundException; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; @ControllerAdvice public class RestControllerAdvicer { @ExceptionHandler(NullPointerException.class)//要处理的异常 @ResponseBody public Object npe(HttpServletRequest request, Throwable throwable){ return throwable.getMessage(); } @ExceptionHandler(value = {NoHandlerFoundException.class})//要处理的异常404 @ResponseBody public Object pageNotPage(HttpServletRequest request, Throwable throwable){ Map errors = new HashMap<>(); //没有获取到信息,好像request的信息都被删除了 errors.put("status_code",request.getAttribute("javax.servlet.error.status_code")); errors.put("request_uri",request.getAttribute("javax.servlet.error.request_uri")); // javax.servlet.error.status_code java.lang.Integer // javax.servlet.error.exception_type java.lang.Class // javax.servlet.error.message java.lang.String // javax.servlet.error.exception java.lang.Throwable // javax.servlet.error.request_uri java.lang.String // javax.servlet.error.servlet_name java.lang.String return errors; } } ``` @ExceptionHandler(NullPointerException.class)//要处理的异常 缺点:很难完全掌握掌握所有的异常类型 优点:易于理解,尤其是全局异常处理 ### Spring Boot 错误页面处理 实现:org.springframework.boot.web.server.ErrorPageRegistrar ​ 缺点:页面处理的路径必须固定 ​ 优点:比较通用,不需要理解Spring MVC的异常体系 注册:Error Page对象 实现:ErrorPage对象中Path路径WEB服务 ```java @SpringBootApplication public class RestOnSpringWebmvc extends WebMvcConfigurerAdapter implements ErrorPageRegistrar{ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new DefaultHandlerInterceptor()); } public static void main(String[] args) { SpringApplication.run(RestOnSpringWebmvc.class,args); } /** * spring boot 异常处理器 * @param registry */ @Override public void registerErrorPages(ErrorPageRegistry registry) { registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND,"/404.html")); } } ``` 处理服务: ```java import org.springframework.web.bind.annotation.*; @RestController//==@Controller+@ResponseBody public class RestDemoController { /** * 处理页面找不到的情况 * @return */ @GetMapping("/404.html") public Map handlerPageNotFond(HttpServletRequest request){ Map errors = new HashMap<>(); errors.put("status_code",request.getAttribute("javax.servlet.error.status_code")); errors.put("request_uri",request.getAttribute("javax.servlet.error.request_uri")); return errors; } } ``` ## 视图技术 ### View #### render方法 处理页面渲染的逻辑,Velocity,JSP,Thymeleaf ### ViewResover View Resolver = 页面 +解析器 -> resolveViewName 寻找合适/对应View对象 RequestURI -> RequestMappingHandlerMapping->HandleMethod -> return "viewName"-> 完整的页面名称=prefix +"viewName"+suffix -> ViewResolver-> View -> render ->HTML Spring Boot 解析完整的页面路径: spring.view.prefix + HandlerMethod +return spring.view+suffix org.springframework.web.servlet.view.ContentNegotiatingViewResolver 用于处理多个ViewResolver: JSP,Velocity, thymeleaf 最佳匹配原则 当所有的ViewResover配置完成是时,他们的order默认值是一样的,所有先来先服务(list) 当他们定义自己的order,通过order来倒序排列 ### Thymeleaf #### 自动装配类:ThymeleafAutoConfiguration(jar没有引入,打开里面飘红) maven导入 ```xml org.springframework.boot spring-boot-starter-thymeleaf ``` ```java @Configuration @EnableConfigurationProperties(ThymeleafProperties.class) @ConditionalOnClass(TemplateMode.class) @AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class }) public class ThymeleafAutoConfiguration { ......略。。。 ``` 打开ThymeleafProperties.class ```java @ConfigurationProperties(prefix = "spring.thymeleaf") public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; /** * Whether to check that the template exists before rendering it. */ private boolean checkTemplate = true; /** * Whether to check that the templates location exists. */ private boolean checkTemplateLocation = true; /** * Prefix that gets prepended to view names when building a URL. */ private String prefix = DEFAULT_PREFIX; /** * Suffix that gets appended to view names when building a URL. */ private String suffix = DEFAULT_SUFFIX; ......略。。。 ``` 配置前缀:spring.thymeleaf 模板寻找前缀:spring.thymeleaf.prefix [classpath:/templates/] 模板寻找后缀:spring.thymeleaf.suffix [.html] 添加配置: ```properties spring.thymeleaf.prefix=classpath:/thymeleaf/ spring.thymeleaf.suffix=.htm ``` Controller ```java import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class ThymeleafController { @RequestMapping("/thymeleaf/index.do") public String index(){ return "index"; } } ``` prefix:/thymeleaf/ suffix:.htm return value:index 完整页面: /thymeleaf/index.htm 测试url: http://localhost:8080/thymeleaf/index.do ViewResolver,查看它的实现类 org.thymeleaf.spring5.view.ThymeleafViewResolver 父类:org.springframework.web.servlet.view.AbstractCachingViewResolver 中的resolveViewName方法 ## 国际化(i18n) java.util.Locale MessageSourceAutoConfiguration ```java @Configuration @ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Conditional(ResourceBundleCondition.class) @EnableConfigurationProperties public class MessageSourceAutoConfiguration { private static final Resource[] NO_RESOURCES = {}; @Bean @ConfigurationProperties(prefix = "spring.messages") public MessageSourceProperties messageSourceProperties() { return new MessageSourceProperties(); } ......略。。。 ``` 国际化配置: ``` spring.messages.basename=META-INF/locale/messages ``` 创建文件夹:resources/META-INF/locale 在文件夹中新建: messages_zh_CN.properties messages.properties 两个都要建 在html中使用和替换文本: ```html

这是我的第一个thymeleaf测试

``` 在messages_zh_CN.properties中添加响应的配置: ```properties home.welcome=Hello,World, ABC ``` 好像默认使用messages_zh_CN.properties中的配置,不是messages.properties中的配置 %JAVA_HOME%/bin中 中文转Unicode:native2ascii -encoding UTF-8 D:/abc.txt D:/abd.txt //GB2312、 GBK 也可以 Unicode转中文:native2ascii -reverse -encoding UTF-8 D:/abd.txt D:/abc.txt LocaleContext LocaleContextHolder LocaleResolver/LocaleContextResolver ``` AcceptHeaderLocaleResolver 实现 LocaleResolver ``` 使用postman测试,headers:[{"key":"Accept-Language","value":"en","description":"","enabled":true}] 结果还是英文??? # 1.3 Spring Boot Jdbc(含webflux) ## 数据源(DataSource) maven 导入 ```xml org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-webflux mysql mysql-connector-java runtime ``` spring-boot-starter-jdbc 如果没有配置数据源,启动会报错,如果没有配置数据源,就只能把这个maven导入注释掉,再启动 ###类型 ​ 通用型数据源 ​ javax.sql.DataSource ​ 分布式数据源 ​ javax.sql.XADataSource ​ 嵌入式数据源 ​ org.springframework.datasource.embedded.EmbeddedDatabase ### Spring Boot JDBC 场景演示 ​ 单数据源场景 javax.sql.DataSource ### 数据库连接池技术 ####Apache Commons DBCP commons-dbcp 依赖 commons-pool(老版本) commons-dbcp2 依赖 commons-pool2 ​ 多数据源场景 ####[Tomcat DBCP](http://tomcat.apache.org/tomcat-8.5-doc/jndi-datasource-examples-howto.html) ```sql CREATE TABLE myuser ( id BIGINT NOT NULL PRIMARY KEY auto_increment, NAME VARCHAR (32) NOT NULL ); ``` ```properties spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=123456 ``` ## 事务(Transaction) ### 重要概念 #### 自动提交模式 #### 事务隔离级别(Transaction isolation levels) javax.sql.Connection ```java int TRANSACTION_READ_UNCOMMITTED = 1; int TRANSACTION_READ_COMMITTED = 2; int TRANSACTION_REPEATABLE_READ = 4; int TRANSACTION_SERIALIZABLE = 8; ``` 事务的隔离级别 从上往下,级别越高,性能越差 Spring Transaction实现重用了jdbc api org.springframework.transaction.annotation.Isolation-->TransactionDefinition ```java int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; ``` #### 代理执行 TransactionInterceptor @Transactional注解的方法,被代理了,要是想查看代理之后的样子,需要在方法调用的地方进行Dubugger跟踪! TransactionAspectSupport @Transactional *可以控制rollback的异常粒度:rollbackFor()以及noRollbackFor()* *可以执行事务管理器:transactionManager()* #### API实现方式 org.springframework.transaction.PlatformTransactionManager @Transactional在事务传播 @Transactional save(){ ​ //insert into DS1 ​ save2()//insert into DS2;save2()没有@Transactional } 单独调用save2()是没有事务的,但是save()是有事务的,一起的事务 #### 保护点(Savepoints) save(){ ​ //新建一个安全点SP1 ​ SP1 ​ SP2{ ​ //操作 ​ }catch(){ ​ rollback(SP2) ​ } ​ commit(); ​ release(SP1) } 小马哥的github: https://github.com/mercyblitz/jsr ##Spring Boot 实际使用场景 Spring Boot 2.0容器使用 spring webflux认启动容器是netty(嵌入式) Spring webmvc的默认启动容器是tomcat(嵌入式) Spring Boot 1.4 开始,错误分析接口 org.springframework.boot.diagnostics.FailureAnalysisReporter 数据源初始化 org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration WebFlux Mono:0-1 个 Publisher (类似于java8中的Optional) Flux:0-N 个 Publisher (类似于java中的List) 传统的Servlet采用的是HttpServletRequest,HttpServletResponse WebFlux采用ServerRequest,ServerResponse(不再限制于Servlet容器,可以选择自定义实现,比如Netty Web Server) ## 查找异常调用链 1、找到异常输出的字符串 2、把字符串在project and libraries中查找(ctrl+shift+f) 3、找到异常方法的方法,字段;然后查看在那里使用,右击,Find Usages,看看在哪个方法中调用 4、对找到的方法进行断点,执行到断点处,查看方法栈,看看是否在Configuration中就报错了或者其他情况 ##问题集合 问题:用reactive web,原来mvc的好多东西都不能用了? 答:不是,Reactive Web还是能兼容Spring mvc的。 问题:开个线程池事务控制用api方式? 答:TransactionSynchronizationManager,使用大量的ThreadLocal来实现的。 问题:spring boot中分布式事务有几种方式? https://docs.spring.io/spring-boot/docs/2.0.8.RELEASE/reference/htmlsingle/#boot-features-jta # 1.4 Spring Boot 初体验 ## 基本概念 应用分为两个方面:功能性、非功能性 功能性:系统所设计的业务范畴 非功能性:安全、性能、监控、数据指标(CPU使用率、网卡使用率) Spring Boot 规约大于配置,大多数组件,不需要自行配置,而是自动组装!简化开发,大多数情况,使用默认即可! production-ready就是非功能性范畴 opinionated 固化的 独立Spring应用,不需要依赖,依赖容器(tomcat) 嵌入式Tomcat Jetty 外部配置:启动参数、配置文件、环境变量 Profiles Logging 外部应用:Servlet应用、Spring Web MVC、Spring Web Flux、WebSockets、Web Service SQL: JDBC、JPA、ORM NoSql(Not Only SQL): Redis、ElasticSearch、Hive、Hbase ## Spring Boot 创建方式 图形化方式:http://start.spring.io 命令行方式:maven mvn archetype:generate -DgroupId=com.fankun -DartifactId=first-sping-boot-app -Dversion=1.0.0-SNAPSHOT -DinteractiveMode=false 创建之后,idea可以import project导入! interactiveMode 交互模式 #### spring-webflux 目前不能和 spring-webmvc同时使用