各1
人生は非常に興味深いです:最初にすべての長生きしなければなりません。長く生きる自分を見ることができ、その後、あなたは長い生き物を見ることができます
序文
後最初の二つは、記事を理解しSpring MVC
、コンテンツの交渉の後、私はあなたが巧みに使用することができたと考えているSpring MVC
と、提供するために、この能力はRESTful
その有効性の遊びを。実際には、これは私たちの目的を達成する80%
、また知識ポイントのこの作品は、私の執筆の目的を達しました。
なぜそれがあり80%
、その後?私は今日の前面と背面を完全に分離すると思うので、シーンのほとんどは、カバレッジを完了するには、このような状況を使用しています。
なぜあり20%
すぎ?コンテンツネゴシエーションにのみ使用することができていないためHttpMessage
に、またして使用することができView
、この記事の焦点は、コンテンツを追加したいですビュー、。
コンテンツネゴシエーションでHttpMessage
上のアプリケーション
最初の二つの記事の例は、これに基づいています。私はの原則を説明するために言及したときは:で入場処理AbstractMessageConverterMethodProcessor.writeWithMessageConverters()
:メソッドを、この抽象クラスのサブクラスを識別することができます参照して
それは:サブクラスの実装から、あなたは理解することができHttpMessage
、両方を通じて、強く関連しているHttpMessageConverter
プロセスメッセージは、コンテンツネゴシエーションを行います。
それは我々が2つの実装クラスの治療を提示し、最も一般的なコメントです:
@ResponseBody
。戻り値は、直接またはHttpEntity/ResponseEntity类型(也就是不能是RequestEntity就成)
そこは間違いないに基づいて、ある@ResponseBody
フロントのインターフェイスモードの完全な分離の残りとバックすでに今日の主流の方法ですので、私は覆う第1の2件の記事と述べ80%
〜シーンは過言それすべきではありません
私は、検索ContentNegotiationManager.resolveMediaTypes()
のメソッドContentNegotiatingViewResolver
〜もViewRendererで使用を組み合わせることができます内側に使用するので、私は自然に相談の内容を考えます
観点から、コンテントネゴシエーションView
アプリケーション
私が与えた前の例をベースとしているとしてHttp消息
、まったくビューがありません。:この記事では、ビューの観点から解決するために、コンテンツネゴシエーションを使用する必要性を説明するためにここにある道を示すこととして、異なるビューで、同じURLを。
我々はすでに知っている:RequestMappingInfoHandlerMapping(@RequestMapping)
それは接尾辞を持つ上にあるhttp
要求が一致したときに、正確なパターンを見つけていない場合、それはなりpattern+.*
、その後のURLと一致し、それはURLの異なる形の数を扱います、しかし同じに戻りましたビュー。この記事では、あなたが使用することをお教えします@RequestMapping
以上を返すことができますView
〜
注意:私は、この実施形態は、メッセージ本文を返すために、この議論は、それはケース1に属するカテゴリではないが、返すように表示するには、こちらを参照してください。
ビューリゾルバViewResolver
ビューの内容については、ここで見つけることができます:ビュー
ビューリゾルバの内容は、ここで見つけることができます:ViewResolver
このホワイトペーパーで簡単に再びについて「レビュー」Spring MVC
プロセスViewResolverの使用:
:で使用して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
の詳細9つのアセンブリを言っ負荷
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
これは、ビューが、他のプロセッサに委任ビューをレンダリングしません。
優先度の高いプロセッサビューを(デフォルトは最高である)を設定する必要性以外に、この正常に動作パーサ、オーダー番号を作成するには
// @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
私の次の要素の詳細を要約したものです。
ContentNegotiationManager
コンテンツネゴシエーションのための戦略を指定手動で設定することができ、また、することができますFactoryBean
自動的に生成しますviewResolvers
デフォルトでは〜あなたも手動でコンテナ内に入るように設定することができ、もちろんすべてを見つけることです- 異なる出力フォーマットビューの異なる拡張機能の選択に応じて、メディアタイプを使用して要求
- 自分のビューが、別のビューを処理するためのさまざまなViewResolverエージェントへのハンドルではありません。
- デフォルトでは、サポートすることです
Accept
し、交渉の延長。そしてまた、サポート逻辑视图名.后缀
〜ビューの解像度モードを - よると、
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;
}
}
ポイント:そこにいくつかの新しいビューリゾルバが出ているが、最終的な心配はありませんが、実行されInitializingBean
、ApplicationContextAware
...およびインタフェース・メソッドのようにいくつか。これらはに引き渡されているため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、のためにこれらの3つのビューに対処する準備ができて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 3()、色の内容をレンダリングすることは同じではありません
説明:シミュレーションは、そのすべてを実装するために私の匿名クラス、女性や小さなパートナーは支障がなければならない理論的に何を理解しているので、私は(この問題は私にメッセージを与えることはできません〜)ここだけです
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()
それが与えられているが、任意の拡張子を解決することができ、同等のビューを返します。あなたの拡張機能が存在しない場合でも、それは最終的に解決され、HTML形式で表示されます。実際の場面ではそれほど魅力的ではありません
また、この場合でも、簡単にテストプログラムがある - >ノープランビューリゾルバ、単にデフォルトのビューを提供することができ、あなたが興味を持っているが、理解を深め、自分の小さなパートナーを試すことができます。
概要
例の助けを借りて、説明ContentNegotiatingViewResolver
の残りを埋めるために呼ばれるビューの観点から分析コンテンツネゴシエーションのアプリケーション、20%
コンテンツを。
今比較的小さいために使用するバックエンド技術を見るが、すべての後に、けれどもthymeleaf
まだ非常に良いです(熟練した、フルスタックエンジニアとして、あなたはまた、言語テンプレートエンジンをマスターする理由がVue、React
、私は言わなかった場合には)
関連読書
ContentNegotiationコンテンツネゴシエーションメカニズムは、(a)は---春のMVC 4忠は、ビルトインサポートコンテンツネゴシエーション[Spring MVCの学習を楽しむ]
ContentNegotiationコンテンツネゴシエーション(2)--- Spring MVCのコンテンツネゴシエーションの原理を、カスタム設定を楽しむことを学ぶ[春MVC]
のビュービューでContentNegotiationコンテンツネゴシエーションメカニズム(C)アプリケーション---:ContentNegotiatingViewResolver深さ分析[春MVC学習楽しみます]
知識交換
==最後に:あなたはあなたにこの記事が参考に思われる場合は、賞賛の聖歌を指すように望むことができます。もちろん、友人のサークルは、より小さなパートナーも見ているので、共有する作者本人许可的~
==を
技術内容に興味を持っている場合、グループWX交換に参加することができますJava高工、架构师3群
。
グループは、2次元コードを失敗した場合、WX番号を追加してください:fsx641385712
(または2次元コードがWXの下でスキャンされます)。そして注意:"java入群"
単語が、グループに招待され、手動
春、SpringBoot、MyBatisのソースコード解析やその他の興味は私にWXを追加することができますため場合==:fsx641385712、手動でグループにあなたを招待することは脱い==