每篇一句

人生很有意思:首先就得活得长。活得长才能够见自己,再长就可以见众生

前言

在经过 前两篇 文章了解了Spring MVC的内容协商机制之后,相信你已经能够熟练的运用Spring MVC提供的这项能力,配合RESTful发挥它的功效了。这其实也就达到了我们目的的80%,也达到了我书写这块知识点的目的。

为何说是80%呢?因为我认为在前后端完全分离的今天,绝大部分使用场景都是这种情况,完成了覆盖。

为何还有20%呢?因为内容协商不仅仅可以使用在HttpMessage上,还可以使用在View视图上,这也就是本文想重点补充的内容。

内容协商在HttpMessage上的应用

前两篇文章的示例都是基于此。在讲解原理的时候提到:处理的入口在AbstractMessageConverterMethodProcessor.writeWithMessageConverters()方法上,看此抽象类的子类也能看出端倪:



从子类实现中你也能够明白:它和HttpMessage是强相关的,都是经过了HttpMessageConverter处理的消息来做内容协商。

这两个实现类处理的也就是我们当下最为常用的注解:@ResponseBody。或者返回值直接是HttpEntity/ResponseEntity类型(也就是不能是RequestEntity就成)

毫无疑问,基于@ResponseBody的Rest接口方式在前后端完全分离的今天已然是主流方式,因此我说前两篇文章覆盖了80%的场景应该不为过吧~


我搜索到ContentNegotiationManager.resolveMediaTypes()方法在ContentNegotiatingViewResolver里也使用到了,因此我自然而然的联想到了内容协商也能结合视图解析器一起使用~

内容协商在视图View上的应用

由于前面我给的示例都是基于Http消息的,没有视图可言。本文此处需要讲解的是内容协商在视图解析方面的应用:同一个URL,以不同的视图作为展示方式

我们已经知道了:RequestMappingInfoHandlerMapping(@RequestMapping)它在对带有后缀的http请求进行匹配的时候,如果找不到精确的pattern, 那么就会pattern+.*后再匹配 url,它会处理多个不同形式是 url,但是返回的是同一个View。本文就教你用一个@RequestMapping也能返回多个View~

注意:我这里指的是返回的是View视图,对于消息体的这种返回方式,不是本处讨论的范畴,它属于case 1。

视图解析器ViewResolver

关于视图的内容,可参见这里:View

关于视图解析器的内容,可参见这里:ViewResolver

本文简单的再“复习”一下Spring MVC对视图解析器的使用流程:

使用处:DispatcherServlet.resolveViewName()

得到逻辑视图后,通过已经注册好的视图解析器ViewResolver把逻辑视图解析为真正的视图View

DispatcherServlet:
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
// 按照顺序:一个一个执行。第一个最先解析到不返回null的 就是最终返回的view视图
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}

加载处:DispatcherServlet.initViewResolvers()

这个在讲解Spring MVC九大组件加载时详细说过

DispatcherServlet:
private void initViewResolvers(ApplicationContext context) {
// 1、若detectAllViewResolvers=true,去容器中找到所有的ViewResolver Bean们。排序后返回
// 2、若不是探测全部。就只找BeanName=viewResolver它的这一个Bean
// 2、若一个都没有找到,就走默认策略:从DispatcherServlet.properties里配置的读取默认的配置
}

这个查找策略,对我们合理注册、管理视图解析器都是很有用的,可以稍加留意

声明处:WebMvcConfigurationSupport.mvcViewResolver()

WebMvcConfigurationSupport:

	// @since 4.1 向容器注册一个ViewResolver Bean
// 使用的是容器管理方式:ViewResolverComposite
@Bean
public ViewResolver mvcViewResolver() { // mvcContentNegotiationManager:内容协商管理器(本文重点之一)
ViewResolverRegistry registry = new ViewResolverRegistry(mvcContentNegotiationManager(), this.applicationContext);
// protected方法,回调给我们调用者,允许自定义ViewResolverRegistry
configureViewResolvers(registry); // 它的意思是:如果你没有自定义(或者自定义了但一个解析器都木有)
// 那就主动去容器里找。如果仅仅仅仅只知道一个:那它就是InternalResourceViewResolver(注意此处是new的)
// 注意此处的处理方式哦~~~~
if (registry.getViewResolvers().isEmpty() && this.applicationContext != null) {
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.applicationContext, ViewResolver.class, true, false);
if (names.length == 1) {
registry.getViewResolvers().add(new InternalResourceViewResolver());
}
} // 最终使用ViewResolverComposite把这些(多个)装起来,便于管理~
ViewResolverComposite composite = new ViewResolverComposite();
composite.setOrder(registry.getOrder());
composite.setViewResolvers(registry.getViewResolvers());
if (this.applicationContext != null) {
composite.setApplicationContext(this.applicationContext);
}
if (this.servletContext != null) {
composite.setServletContext(this.servletContext);
}
return composite;
}

这里我们能发现,它默认情况下使用的是我们上文说的默认的ContentNegotiationManager来处理内容协商的。因此下面重点要来到今天的主角ContentNegotiatingViewResolver身上

ContentNegotiatingViewResolver:内容协商视图解析器

ContentNagotiatingViewResolver自己并不解析视图,而是委派给其他的视图处理器。

为了使这个解析器正常工作,order序号需要设置成比其他的视图处理器高的优先级(默认就是最高的)

// @since 3.0
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean {
// 用于内容协商的管理器
@Nullable
private ContentNegotiationManager contentNegotiationManager;
private final ContentNegotiationManagerFactoryBean cnmFactoryBean = new ContentNegotiationManagerFactoryBean(); // 如果没有合适的view的时候,是否使用406这个状态码(HttpServletResponse#SC_NOT_ACCEPTABLE)
// 默认值是false:表示没有找到就返回null,而不是406
private boolean useNotAcceptableStatusCode = false;
// 当无法获取到具体的视图时,会走defaultViews
@Nullable
private List<View> defaultViews; @Nullable
private List<ViewResolver> viewResolvers;
private int order = Ordered.HIGHEST_PRECEDENCE; // 默认,优先级就是最高的 // 复写:WebApplicationObjectSupport的方法
// 它在setServletContext和initApplicationContext会调用(也就是容器启动时候会调用)
@Override
protected void initServletContext(ServletContext servletContext) {
Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
//容器内找到了 就以容器内所有已经配置好的视图解析器都拿出来(包含父容器)
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) { // 排除自己
this.viewResolvers.add(viewResolver);
}
}
} else { // 进入这里证明是调用者自己set进来的
for (int i = 0; i < this.viewResolvers.size(); i++) {
ViewResolver vr = this.viewResolvers.get(i);
if (matchingBeans.contains(vr)) {
continue;
}
String name = vr.getClass().getName() + i;
// 对视图解析器完成初始化工作~~~~~
// 关于AutowireCapableBeanFactory的使用,参见:https://blog.csdn.net/f641385712/article/details/88651128
obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
} } // 找到所有的ViewResolvers排序后,放进ContentNegotiationManagerFactoryBean里
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
} // 从这一步骤可以知道:contentNegotiationManager 可以自己set
// 也可以通过工厂来生成 两种方式均可
@Override
public void afterPropertiesSet() {
if (this.contentNegotiationManager == null) {
this.contentNegotiationManager = this.cnmFactoryBean.build();
}
if (this.viewResolvers == null || this.viewResolvers.isEmpty()) {
logger.warn("No ViewResolvers configured");
}
} // 处理逻辑视图到View 在此处会进行内容协商
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes"); // getMediaTypes()这个方法完成了
// 1、通过contentNegotiationManager.resolveMediaTypes(webRequest)得到请求的MediaTypes
// 2、拿到服务端能够提供的MediaTypes producibleMediaTypes
// (请注意因为没有消息转换器,所以它的值的唯一来源是:request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE))
// (若没有指定producers的值,那就是ALL)
// 3、按照优先级,协商出`selectedMediaTypes`(是个List)
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest()); // 进入此处:说明协商出了有可用的MediaTypes(至少有一个嘛)
if (requestedMediaTypes != null) { // getCandidateViews()这个很重要的方法,见下文
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes); // 上面一步骤解析出了多个符合条件的views,这里就是通过MediaType、attrs等等一起决定出一个,一个,一个最佳的
// getBestView()方法描述如下:
// 第一大步:遍历所有的candidateViews,只要是smartView.isRedirectView(),就直接return
// 第二大步:遍历所有的requestedMediaTypes,针对每一种MediaType下再遍历所有的candidateViews
// 1、针对每一种MediaType,拿出View.getContentType(),只会看这个值不为null的
// 2、view的contentType!=null,继续看看mediaType.isCompatibleWith(candidateContentType) 若不匹配这个视图就略过
// 3、若匹配:attrs.setAttribute(View.SELECTED_CONTENT_TYPE, mediaType, RequestAttributes.SCOPE_REQUEST) 然后return掉此视图作为best最佳的
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) { // 很显然,找到了最佳的就返回渲染吧
return bestView;
}
} ...
// useNotAcceptableStatusCode=true没找到视图就返回406
// NOT_ACCEPTABLE_VIEW是个private内部静态类View,它的render方法只有一句话:
// response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
if (this.useNotAcceptableStatusCode) {
return NOT_ACCEPTABLE_VIEW;
} else {
return null;
}
} // 根据viewName、requestedMediaTypes等等去得到所有的备选的Views~~
// 这这里会调用所有的viewResolvers.resolveViewName()来分别处理~~~所以可能生成多多个viewo ~
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes) throws Exception {
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set"); // 遍历所有的viewResolvers,多逻辑视图一个一个的处理
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view); // 处理好的就装进来
} // 另外还没有完:遍历所有支持的MediaType,拿到它对应的扩展名们(一个MediaType可以对应多个扩展名)
// 如果viewName + '.' + extension能被处理成一个视图,也是ok的
// 也就是说index和index.jsp都能被解析成view视图~~~
for (MediaType requestedMediaType : requestedMediaTypes) {
// resolveFileExtensions()方法可以说这里是唯一调用的地方
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
for (String extension : extensions) {
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view); // 带上后缀名也能够处理的 这种视图也ok
}
}
}
}
}
// 若指定了默认视图,把视图也得加上(在最后面哦~)
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
}

关于ContentNegotiatingViewResolver我总结出如下细节要点:

  1. ContentNegotiationManager用于内容协商的策略可以手动set指定,也可以通过FactoryBean自动生成
  2. viewResolvers默认是去容器内找到所有的,当然你也可以手动set进来的~
  3. 使用request的媒体类型,根据扩展名选择不同的view输出不同的格式
  4. 不是自己处理view,而是代理给不同的ViewResolver来处理不同的view;
  5. 默认是支持Accept和后缀的协商方式的。并且还支持 逻辑视图名.后缀的视图解析方式~
  6. 依据View.getContentType匹配MediaType来完成的最佳匹配

如何使用?

我们已经知道,默认情况下Spring MVC可没有使用此内容协商视图解析器,因此若有同一资源,多视图展示的需求,我们是需要手动配置(开启)支持的。

通过检索可以看到ViewResolverRegistry它为我们提供了便捷使用的方式:

当然你也可以通过单独配置一个ContentNegotiatingViewResolver @Bean的方式来做,原理也很简单很好解释。本文我就给个最佳实践作为参考示例

public class ViewResolverRegistry {
...
public void enableContentNegotiation(View... defaultViews) {
initContentNegotiatingViewResolver(defaultViews);
}
public void enableContentNegotiation(boolean useNotAcceptableStatus, View... defaultViews) {
ContentNegotiatingViewResolver vr = initContentNegotiatingViewResolver(defaultViews);
vr.setUseNotAcceptableStatusCode(useNotAcceptableStatus);
}
// 初始化一个内容协商视图解析器
private ContentNegotiatingViewResolver initContentNegotiatingViewResolver(View[] defaultViews) {
// ContentNegotiatingResolver in the registry: elevate its precedence!
// 请保证它是最高优先级的:在所有视图解析器之前执行
// 这样即使你配置了其它的视图解析器 也会先执行这个(后面的被短路掉)
this.order = (this.order != null ? this.order : Ordered.HIGHEST_PRECEDENCE); // 调用者自己已经配置好了一个contentNegotiatingResolver,那就用他的
if (this.contentNegotiatingResolver != null) {
// 若存在defaultViews,那就处理一下把它放进contentNegotiatingResolver里面
if (!ObjectUtils.isEmpty(defaultViews) && !CollectionUtils.isEmpty(this.contentNegotiatingResolver.getDefaultViews())) {
List<View> views = new ArrayList<>(this.contentNegotiatingResolver.getDefaultViews());
views.addAll(Arrays.asList(defaultViews));
this.contentNegotiatingResolver.setDefaultViews(views);
}
} else { // 若没配置就自己new一个 并且设置好viewResolvers
this.contentNegotiatingResolver = new ContentNegotiatingViewResolver();
this.contentNegotiatingResolver.setDefaultViews(Arrays.asList(defaultViews));
// 注意:这个viewResolvers是通过此ViewResolverRegistry配置进来的
// 若仅仅是容器内的Bean,这里可捕获不到。所以如果你有特殊需求建议你自己set
// 若仅仅是jsp()/tiles()/freeMarker()/groovy()/beanName()这些,内置的支持即可满足要求儿聊
// ViewResolverRegistry.viewResolver()可调用多次,因此可以多次指定 若有需要个性化,可以调用此方法
this.contentNegotiatingResolver.setViewResolvers(this.viewResolvers);
if (this.contentNegotiationManager != null) {
this.contentNegotiatingResolver.setContentNegotiationManager(this.contentNegotiationManager);
}
}
return this.contentNegotiatingResolver;
}
}

说明一点:虽然这里有些视图解析器是new出来的,但不用担心最后都会执行InitializingBeanApplicationContextAware...等等的一些接口方法的。因为这些都是交给ViewResolverComposite统一代劳的~(因此并不需要放进Spring容器里亦可,减少容器的负担也是一种优化)

上面"复习"的时候提到了,Spring MVC准备好ViewResolverRegistry后会回调我们,因此实际使用中可以通过此入口进行配置(最佳实践):

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(); // 开启内容协商视图解析器
}
}

在我准备介绍案例时,为了便于对小伙伴对整个内容协商流程的把控和理解,我提供如下这张执行原理流程图作为辅助理解(若图有错误可留言指出,多谢):



使用示例

是骡子是马,总归还是要拉出来溜溜。下面我用一个工作中非常具象的案例,来演示一下它的用法。

需求:同一个RESTful的URL,我希望得到一个PDF视图、JSON视图、Html视图???

实现代码

因为是同一个URL,并且还要求是有不同视图的,因此这里用ContentNegotiatingViewResolver来做内容协商就非常得心应手了。

1、准备针对于处理这三种视图的ViewResolver实现类:

    // 自定义三个视图分别用于处理对应的视图需求
private final ViewResolver pdf_viewresolver= (viewName, locale) -> new View() {
@Override
public String getContentType() {
return MediaType.APPLICATION_PDF_VALUE;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("<html><body style='color:red'>this is pdf view</body></html>");
}
};
private final ViewResolver excel_viewresolver= (viewName, locale) -> new View() {
@Override
public String getContentType() {
return MediaType.APPLICATION_JSON_UTF8_VALUE;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("<html><body style='color:yellow'>this is json view</body></html>");
}
};
private final ViewResolver html_viewresolver= (viewName, locale) -> new View() {
@Override
public String getContentType() {
return MediaType.TEXT_HTML_VALUE;
}
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("<html><body style='color:green'>this is html view</body></html>");
}
};

请注意:三者的getContentType()、渲染内容、颜色都是不一样的

说明:因为此处我只是模拟,所以我全部以匿名类来实现,各位小伙伴理解起来理论上应该都没有啥障碍吧(有问题可给我留言~)

2、开启Spring MVC在视图上对ContentNegotiation内容协商的支持:

@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(pdf_viewresolver);
registry.viewResolver(excel_viewresolver);
registry.viewResolver(html_viewresolver); // 上面三个注册方法必须在此方法之上执行
registry.enableContentNegotiation(false);
}
}

3、测试代码

@Controller
@RequestMapping
public class HelloController { @GetMapping("/test/{type}")
public String testContentNegotiation() {
return "test ContentNegotiation";
}
}

顺序请求:/test/a.pdf/test/a.json/test/a.html/test/a(无后缀)分别得到的页面截图如下(一一对应)









使用Accept方式演示如下:







后缀的优先级高于Accept,符合我们前面的理论知识。若没有指定后缀,Accept就会生效。

说明:因为我这里resolveViewName()是很定的返回了一个view,相当于可以解析任何扩展名。so即使你的扩展名不存在也会被解析,最终以html形式展示出来。在真实场景中是不会这么搞的

另外:这种case还有一种更为简便的测试方案-->无需提供视图解析器,只需提供默认视图即可,有兴趣的小伙伴可以自行尝试,加深理解。

总结

本文借助实例,讲解了ContentNegotiatingViewResolver内容协商在视图解析方面的应用,填补所谓的剩余的20%的内容。

虽然说现在视图技术对于后端来说使用相对较少了,但毕竟thymeleaf还是很优秀的,作为全栈工程师,你也有理由掌握一门模版引擎语言(熟练Vue、React的当我没说)

相关阅读

ContentNegotiation内容协商机制(一)---Spring MVC内置支持的4种内容协商方式【享学Spring MVC】

ContentNegotiation内容协商机制(二)---Spring MVC内容协商实现原理及自定义配置【享学Spring MVC】

ContentNegotiation内容协商机制(三)---在视图View上的应用:ContentNegotiatingViewResolver深度解析【享学Spring MVC】

知识交流

The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的~

若对技术内容感兴趣可以加入wx群交流:Java高工、架构师3群

若群二维码失效,请加wx号:fsx641385712(或者扫描下方wx二维码)。并且备注:"java入群" 字样,会手动邀请入群


若对Spring、SpringBoot、MyBatis等源码分析感兴趣,可加我wx:fsx641385712,手动邀请你入群一起飞

内容协商在视图View上的应用【享学Spring MVC】的更多相关文章

  1. Spring MVC内容协商实现原理及自定义配置【享学Spring MVC】

    每篇一句 在绝对力量面前,一切技巧都是浮云 前言 上文 介绍了Http内容协商的一些概念,以及Spring MVC内置的4种协商方式使用介绍.本文主要针对Spring MVC内容协商方式:从步骤.原理 ...

  2. Spring MVC内置支持的4种内容协商方式【享学Spring MVC】

    每篇一句 十个光头九个富,最后一个会砍树 前言 不知你在使用Spring Boot时是否对这样一个现象"诧异"过:同一个接口(同一个URL)在接口报错情况下,若你用rest访问,它 ...

  3. HandlerMethodArgumentResolver(一):Controller方法入参自动封装器【享学Spring MVC】

    每篇一句 你的工作效率高,老板会认为你强度不够.你代码bug多,各种生产环境救火,老板会觉得你是团队的核心成员. 前言 在享受Spring MVC带给你便捷的时候,你是否曾经这样疑问过:Control ...

  4. HandlerMethodArgumentResolver(二):Map参数类型和固定参数类型【享学Spring MVC】

    每篇一句 黄金的导电性最好,为什么电脑主板还是要用铜? 飞机最快,为什么还有人做火车? 清华大学最好,为什么还有人去普通学校? 因为资源都是有限的,我们现实生活中必须兼顾成本与产出的平衡 前言 上文 ...

  5. HandlerMethodArgumentResolver(三):基于消息转换器的参数处理器【享学Spring MVC】

    每篇一句 一个事实是:对于大多数技术,了解只需要一天,简单搞起来只需要一周.入门可能只需要一个月 前言 通过 前面两篇文章 的介绍,相信你对HandlerMethodArgumentResolver了 ...

  6. RestTemplate相关组件:ClientHttpRequestInterceptor【享学Spring MVC】

    每篇一句 做事的人和做梦的人最大的区别就是行动力 前言 本文为深入了解Spring提供的Rest调用客户端RestTemplate开山,对它相关的一些组件做讲解. Tips:请注意区分RestTemp ...

  7. RestTemplate的使用和原理你都烂熟于胸了吗?【享学Spring MVC】

    每篇一句 人圆月圆心圆,人和家和国和---中秋节快乐 前言 在阅读本篇之前,建议先阅读开山篇效果更佳.RestTemplate是Spring提供的用于访问Rest服务的客户端工具,它提供了多种便捷访问 ...

  8. 从原理层面掌握@InitBinder的使用【享学Spring MVC】

    每篇一句 大魔王张怡宁:女儿,这堆金牌你拿去玩吧,但我的银牌不能给你玩.你要想玩银牌就去找你王浩叔叔吧,他那银牌多 前言 为了讲述好Spring MVC最为复杂的数据绑定这块,我前面可谓是做足了功课, ...

  9. ModelAndViewContainer、ModelMap、Model详细介绍【享学Spring MVC】

    每篇一句 一个开源的技术产品做得好不好,主要是看你能解决多少非功能性问题(因为功能性问题是所有产品都能够想到的) 前言 写这篇文章非我本意,因为我觉得对如题的这个几个类的了解还是比较基础且简单的一块内 ...

随机推荐

  1. Excel催化剂开源第23波-VSTO开发辅助录入功能关键技术

    Excel催化剂开源第23波-VSTO开发辅助录入功能关键技术 Excel催化剂   2019.01.12 14:10* 字数 2948 阅读 41评论 0喜欢 0 编辑文章 在Excel催化剂的几大 ...

  2. java练习---14

    abstract class A{ private String name; public A(String name) { this.name = name; } public String get ...

  3. get解决乱码的方式

    //自定义的解决乱码方式

  4. hdoj 4762 Cut the Cake

    题意很简单就不说了. 解题的关键就是这个公式 answer=n/(m^(n-1)); 要用到大数的乘法.然后java水过. import java.util.*; import java.math.* ...

  5. golang-http 请求---设置header与直接发

    背景 现在各种软件到处都是,写代码难免有时候需要http 调用其他的接口. 其实这个东西还挺常用,虽然很简单,但是写的时候 又忘,就像是提笔忘字,索性总结一下吧. 不需要设置header属性的http ...

  6. TextView 使用详解

    极力推荐文章:欢迎收藏 Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以 ...

  7. Drawable与 Bitmap 转换总结

    极力推荐文章:欢迎收藏 Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android Drawable 使用方法详解请看上篇文章. Drawable 使用方法详解 本篇 ...

  8. 记录eclipse中文出现空格宽度不一致的bug

    起因 不久前更新了 eclipse(2019-03) 版本:突然发现出现了,使用注释使用中出现的空格的间隔大小不一致的问题,具体可以看下图: 遇到这种问题简直逼不能忍,在网上搜一下解决方式: 谷歌 搜 ...

  9. Spring Cloud 相关资料链接

    Spring Cloud中文网:https://springcloud.cc/ Spring Cloud API:https://springcloud.cc/spring-cloud-dalston ...

  10. mysql docker 主从配置

    主从复制相关 前置条件: docker安装的mysql是5.7.26版本 1. 编排docker-compose文件如下: version: '3' services: mysql-master: v ...