SpringMVC核心——视图渲染(包含视图解析)问题

一、本来想说的是返回值处理问题,但在 SpringMVC 中,返回值处理问题的核心就是视图渲染。所以这里标题叫视图渲染问题。

本来想在上一篇文章中对视图解析进行说明的,但是通过源码发现,它应该算到视图渲染中,所以在这篇文章中进行说明

org.springframework.web.servlet.DispatcherServlet#doDispatch方法中

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());//945行返回了 ModelAndView 对象
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);// 959行进行的就是返回值处理问题

org.springframework.web.servlet.DispatcherServlet#processDispatchResult方法中

render(mv, request, response); //1012进行视图的渲染(包含视图解析)

org.springframework.web.servlet.DispatcherServlet#render 方法

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {// Determine locale for request and apply it to the response.Locale locale = this.localeResolver.resolveLocale(request);response.setLocale(locale);View view;if (mv.isReference()) {// We need to resolve the view name.view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);if (view == null) {throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" +getServletName() + "'");}}else {// No need to lookup: the ModelAndView object contains the actual View object.view = mv.getView();if (view == null) {throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +"View object in servlet with name '" + getServletName() + "'");}}// Delegate to the View object for rendering.if (logger.isDebugEnabled()) {logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");}try {view.render(mv.getModelInternal(), request, response);}catch (Exception ex) {if (logger.isDebugEnabled()) {logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '"+ getServletName() + "'", ex);}throw ex;}}

可以看到有两个 if ,第一个 if 解决的是视图解析问题,第二个 if 解决的是视图渲染问题。还有官方是这样描述这个方法的:Render the given ModelAndView.

二、视图解析:通过视图解析器进行视图的解析

1.解析一个视图名到一个视图对象,具体解析的过程是:在容器中查找所有配置好的视图解析器(List类型),然后进行遍历,

只要有一个视图解析器能解析出视图就返回 View 对象,若遍历完成后都不能解析出视图,那么返回 null。

具体来看:

org.springframework.web.servlet.DispatcherServlet#resolveViewName

protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,HttpServletRequest request) throws Exception {  for (ViewResolver viewResolver : this.viewResolvers) {View view = viewResolver.resolveViewName(viewName, locale);
    if (view != null) {
      return view;}}
  return null;
}

2. ViewResolver

(1)官方描述:

* Interface to be implemented by objects that can resolve views by name.
*
* <p>View state doesn't change during the running of the application,
* so implementations are free to cache views.
*
* <p>Implementations are encouraged to support internationalization,
* i.e. localized view resolution.

 

 

 

 

 

 

说明:

ViewResolver 接口由能解析视图名称的实现类来实现。

在程序运行期间视图的状态不能更改,所以实现能被随意缓存。鼓励实现支持国际化。

(2)ViewResolver 的整个体系

可以看出 SpringMVC 提供了很多类型视图解析器。

(3)在 SpringMVC 的第一篇文章中,配置过一个视图解析器。

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/"/>
  <property name="suffix" value=".jsp"/>
</bean>

发送一个请求之后,发现要遍历的 ViewResolvers 只有一个,就是上面的这个 ViewResolver。没有其他默认的视图解析器。所以说在SpringMVC 配置文件中,必须配置至少一个 视图解析器。

那么这里会有一个问题?如果配置多个视图解析器,他们的遍历顺序是怎么样的呢?

ViewResolver 的所有实现类中都存在一个 order 属性。

看这个属性的 setOrder() 注释:Set the order in which this {@link org.springframework.web.servlet.ViewResolver} is evaluated。设置谁先被评估。

还有一点小不同:

除 ContentNegotiatingViewResolver 之外,其他所有的 ViewResolver 的默认值都是:Integer.MAX_VALUE(2^31 -1,即2147483647),

而 ContentNegotiatingViewResolver 的默认值为 Ordered.HIGHEST_PRECEDENCE(-2147483648)。

那他们的遍历的顺序与 order 是什么关系呢?如果不设置 order 的话,遍历顺序又是怎么样的?

<1>设置 order 属性后,遍历顺序是怎么样的

<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
  <property name="order" value="99"/>
</bean><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/"/>
  <property name="suffix" value=".jsp"/>
  <property name="order" value="-99"/>
</bean>

对 BeanNameViewResolver 的 order 属性指定为 99,对 InternalResourceViewResolver 指定为-99。这里故意将 BeanNameViewResolver 放到了 InternalResourceViewResolver 前面。

遍历顺序:

发现 InternalResourceViewResolver 会先被遍历。

结论:

在指定 order 属性的情况下,order 值越小的,越先会遍历。

<2>不设置 order 属性,遍历顺序是怎样的

在测试这个的时候,发现一个这样的现象:我将 BeanNameViewResolver 和 InternalResourceViewResolver 的 order 属性都去掉,我这里用的是 Jrebel 的热部署。发现再次请求的时候,

这两个视图的 order 属性值还和之前的一样:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/"/>
  <property name="suffix" value=".jsp"/>
</bean><bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
</bean>

为什么呢?想起了官方的描述:"在程序运行期间视图的状态不能更改,所以实现能被随意缓存",这里被缓存了。重启后来看真正的测试。

第一种情况:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/"/>
  <property name="suffix" value=".jsp"/>
</bean><bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
</bean>

第二种情况:将 bean 在 SpringMVC  Config 文件中的顺序进行替换,需要注意的是,重启服务器,否则它们的顺序还是会被缓存下来。重启后来看:

<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
</bean><bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/"/>
  <property name="suffix" value=".jsp"/>
</bean>

结论已经很明显了:

在同等优先级的情况下,遍历的顺序是由 ViewResolver 在 SpringMVC Config 文件中配置的顺序决定的,谁在前谁先遍历。

这里不对具体的每个视图解析器进行说明,路已经指明了。

3.ViewResolver 具体是怎么将 view name 解析为一个视图的?

先看 ViewResolver 中的 View resolveViewName(String viewName, Locale locale)

说明:

解析视图通过其名称。注意:允许 ViewResolver 链。

如果一个给定名称的view 没有在一个 ViewResolver 中定义,那么它应该返回 null。

然而它也不是必须的:有一些 ViewResolver 当尝试通过视图名称构建 View 对象失败后,不返回 null。而替代它的是,抛出一个异常。

 

以 org.springframework.web.servlet.view.AbstractCachingViewResolver 进行分析。

@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {if (!isCache()) {
    return createView(viewName, locale);}
  else {Object cacheKey = getCacheKey(viewName, locale);View view = this.viewAccessCache.get(cacheKey);
    if (view == null) {
      synchronized (this.viewCreationCache) {view = this.viewCreationCache.get(cacheKey);
      if (view == null) {
        // Ask the subclass to create the View object.view = createView(viewName, locale);
        if (view == null && this.cacheUnresolved) {view = UNRESOLVED_VIEW;}
        if (view != null) {
          this.viewAccessCache.put(cacheKey, view);
          this.viewCreationCache.put(cacheKey, view);
          if (logger.isTraceEnabled()) {logger.trace("Cached view [" + cacheKey + "]");}}}}}
  return (view != UNRESOLVED_VIEW ? view : null);}
}

判断该视图是否被缓存,如果没有被缓存,则创建视图,如果被缓存,则从缓存中获取。

创建视图,以 InternalResourceViewResolver 和 BeanNameViewResolver 为例:

(1)InternalResourceViewResolver 

org.springframework.web.servlet.view.UrlBasedViewResolver#createView

protected View createView(String viewName, Locale locale) throws Exception {// If this resolver is not supposed to handle the given view,// return null to pass on to the next resolver in the chain.if (!canHandle(viewName, locale)) {return null;}// Check for special "redirect:" prefix.if (viewName.startsWith(REDIRECT_URL_PREFIX)) {String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());return applyLifecycleMethods(viewName, view);}// Check for special "forward:" prefix.if (viewName.startsWith(FORWARD_URL_PREFIX)) {String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());return new InternalResourceView(forwardUrl);}// Else fall back to superclass implementation: calling loadView.
  return super.createView(viewName, locale);
}    

在创建视图前会检查返回值是否是以:"redirect:" 或 "forward:" 开头的。

如果是重定向:则创建一个重定向视图,返回创建的视图。如果是转发:则返回通过 转发 url 创建的 InternalResourceView 视图。

org.springframework.web.servlet.view.UrlBasedViewResolver#loadView

AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);

调用具体的 InternalResourceViewResolver ,然后又调用 父类的 buildView() 方法

org.springframework.web.servlet.view.UrlBasedViewResolver#buildView

protected AbstractUrlBasedView buildView(String viewName) throws Exception {AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());view.setUrl(getPrefix() + viewName + getSuffix());String contentType = getContentType();
  if (contentType != null) {view.setContentType(contentType);}view.setRequestContextAttribute(getRequestContextAttribute());view.setAttributesMap(getAttributesMap());
  if (this.exposePathVariables != null) {view.setExposePathVariables(exposePathVariables);}
  return view;
}

可以看出:是通过 BeanUtils.instantiateClass(getViewClass()) 来创建 View 对象的。这个例子与其说是 InternalResourceViewResolver ,倒不如说是 UrlBasedViewResolver 类型的例子。

从这里也可以看出:该类型最终要到的目标URL为:getPrefix() + viewName + getSuffix()

(2)BeanNameViewResolver 

public View resolveViewName(String viewName, Locale locale) throws BeansException {ApplicationContext context = getApplicationContext();if (!context.containsBean(viewName)) {// Allow for ViewResolver chaining.return null;}return context.getBean(viewName, View.class);
}    

org.springframework.context.support.AbstractApplicationContext#getBean(java.lang.String, java.lang.Class<T>)

public <T> T getBean(String name, Class<T> requiredType) throws BeansException {this.assertBeanFactoryActive();return this.getBeanFactory().getBean(name, requiredType);
}

可以看出:是通过 BeanFactory.getBean(String name, Class<T> requiredType) 来获取的。

三、视图渲染

1.View

官方文档:

* MVC View for a web interaction. Implementations are responsible for rendering
* content, and exposing the model. A single view exposes multiple model attributes.
*
* <p>This class and the MVC approach associated with it is discussed in Chapter 12 of
* <a href="http://www.amazon.com/exec/obidos/tg/detail/-/0764543857/">Expert One-On-One J2EE Design and Development</a>
* by Rod Johnson (Wrox, 2002).
*
* <p>View implementations may differ widely. An obvious implementation would be
* JSP-based. Other implementations might be XSLT-based, or use an HTML generation library.
* This interface is designed to avoid restricting the range of possible implementations.
*
* <p>Views should be beans. They are likely to be instantiated as beans by a ViewResolver.
* As this interface is stateless, view implementations should be thread-safe.

 

 

 

 

 

 

 

 

 

 

说明:

SpringMVC 对一个 web 来说是相互作用的(不太明白)。View 的实现类是负责呈现内容的,并且 exposes(暴露、揭露、揭发的意思,这里就按暴露解释吧,想不出合适的词语) 模型的。

一个单一的视图可以包含多个模型。

View 的实现可能有很大的不同。一个明显的实现是基于 JSP 的。其他的实现可能是基于 XSLT 的,或者是一个 HTML 生成库。

设计这个接口是为了避免约束可能实现的范围(这里是不是说,我们可以通过实现该接口来自定义扩展自定义视图?)。

所有的视图都应该是一个 Bean 类。他们可能被 ViewResolver 当做一个 bean 进行实例化。

由于这个接口是无状态的,View 的所有实现类应该是线程安全的。

2.View 的整个体系

3.具体渲染的一个过程

org.springframework.web.servlet.view.AbstractView#render

说明一下这个方法:

为指定的模型指定视图,如果有必要的话,合并它静态的属性和RequestContext中的属性,renderMergedOutputModel() 执行实际的渲染。

public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  if (logger.isTraceEnabled()) {logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +" and static attributes " + this.staticAttributes);}Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);prepareResponse(request, response);renderMergedOutputModel(mergedModel, request, response);
}

这里只看 org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel 这个方法

@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {  // Determine which request handle to expose to the RequestDispatcher.HttpServletRequest requestToExpose = getRequestToExpose(request);  // Expose the model object as request attributes.
  exposeModelAsRequestAttributes(model, requestToExpose);// Expose helpers as request attributes, if any.
  exposeHelpers(requestToExpose);  // Determine the path for the request dispatcher.String dispatcherPath = prepareForRendering(requestToExpose, response);  // Obtain a RequestDispatcher for the target resource (typically a JSP).RequestDispatcher rd = getRequestDispatcher(requestToExpose, 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(requestToExpose, response)) {response.setContentType(getContentType());if (logger.isDebugEnabled()) {logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");}rd.include(requestToExpose, response);}else {// Note: The forwarded resource is supposed to determine the content type itself.if (logger.isDebugEnabled()) {logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");}rd.forward(requestToExpose, response);}
}

可以看到前面的几个步骤都是为 RequestDispatch 做准备,装填数据。最后,到目标页面是通过转发。

四、总结

介绍了 SpringMVC 视图解析和视图渲染问题是如何解决的。SpringMVC 为逻辑视图提供了多种视图解析策略,可以在配置文件中配置多个视图的解析策略。并制定其先后顺序。

这里所说的视图解析策略,就是指视图解析器。视图解析器会将逻辑视图名解析为一个具体的视图对象。再说视图渲染的过程,视图对模型进行了渲染,最终将模型的数据以某种形式呈现给用户。

 

到此为止,在我看来,SpringMVC 整个流程已经跑完,前面的几篇文章,从一个小栗子开始,然后分别介绍了请求映射问题,参数问题,返回值问题,到这篇文章,返回值处理问题

我认为这几个问题是整个 SpringMVC 的核心内容。其他的问题,都是建立在该问题的基础上,或者是对这几个问题的一种延伸。

 

还有想说的是,在边测试边看源码边写文章的时候,给我这么一个感觉,不论是何种开源框架,它所有的应用都是从源码中来的,不论哪个人或者那本书把这个知识讲的多好,

但是在我看来,想要学好某个框架,都要到它源码中去,找到它的根,这样才会有理有据,不会忘的那么快,纵然忘了,下次还是可以找出来为什么。

纵然在看源码的过程中,可能会遇到很多困难,比如英文单词不认识,整个句子读不通,但是如果你能结合自己的理解,

然后理解文档中的话,我相信对你帮助是很大的,其实这就是一种自学能力,摸的着,看得见。

 

算是给各位看客老爷的一种建议吧,愿各位在学习的道路上不要停滞不前。

 

转载于:https://www.cnblogs.com/solverpeng/p/5743609.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/490202.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国际机器人联合会:全球工业机器人2019报告

来源&#xff1a;资本实验室据国际机器人联合会&#xff08;IFR&#xff09;最新发布的《全球机器人2019——工业机器人》报告数据&#xff0c;2018年全球工业机器人出货量42.2万台&#xff0c;比上年增长6%&#xff1b;年销售额达到165亿美元&#xff0c;创下新纪录。 IFR预测…

mediarecorder添加时间戳_Python脚本实现数据处理(官方实例)和Hive自带时间函数...

官网示例官网地址&#xff1a;https://cwiki.apache.org/confluence/display/Hive/GettingStarted#GettingStarted-ApacheWeblogData下载数据wget http://files.grouplens.org/datasets/movielens/ml-100k.zip我们下载好数据安装unzip yum install -y unzip解压数据创建库创建表…

从空城计到阿尔法狗,博弈论如何渗透我们的生活?

来源&#xff1a;返朴2016年&#xff0c;有那么一只“狗”大闹天宫&#xff0c;这在整个人类社会引起了轩然大波。它就是阿尔法狗&#xff08;AlphaGo&#xff09;&#xff0c;是谷歌&#xff08;Google&#xff09;旗下公司DeepMind 在人工智能与博弈论交叉研究上的一个杰作。…

反弹式木马原理_汽车避震器的原理与改装问题

一、避震器or避振器“震”字是指车轮在路面的滚动过程中&#xff0c;由于路面的不平&#xff0c;从路面传递上来的震动&#xff0c;而不是人为制造“振”动&#xff0c;所以使用“震”字更科学。二、避震器的工作原理避震器的工作原理是通过活塞运动产生阻尼力&#xff0c;将动…

科学探索奖首批50名获奖者都有谁?

来源&#xff1a;知识分子9月20日上午&#xff0c;经过四个多月的评审&#xff0c;2019年 获奖名单正式公布。来自全国26个科研单位、高校和企业的50位科学家成为首届“科学探索奖”获奖者&#xff0c;每人将在未来5年获得由腾讯基金会资助的300万元人民币。据悉&#xff0c;20…

untitled软件怎么用_苹果手机怎么用4G网络于App Store下载超过200MB以上的软件

近两年来&#xff0c;随着苹果中国市场份额被国产品牌挤占了不少&#xff0c;他们越来越懂得“聆听”中国用家的需要&#xff0c;其中一项便是用手机网络(4G)下载iOS应用。在这一点上我们中国人也不用再妄自菲薄了&#xff0c;在疯狂的4G建网以后&#xff0c;我们国家已经拥有全…

关于人脸识别的最全研究!

来源&#xff1a;北京物联网智能技术应用协会本文内容涵盖人脸识别发展历程、市场研究、核心技术、商业应用以及产业落地、个人看法等干货研究。注意&#xff0c;本文干货满满&#xff0c;约有2万7千字&#xff0c;强烈建议大家先收藏后学习&#xff01;01 发展史1. 人脸识别的…

关于字节对齐以及内存占用

参考博文&#xff1a; http://www.javamex.com/tutorials/memory/object_memory_usage.shtml 本文主要考虑正常情况下一个对象在堆上的内存占用情况&#xff1a;对于下面的特殊情况不作讨论 1、某些情况下&#xff0c;JVM可能不会把对象存储在堆上&#xff1a;比如小的线程私有…

人工智能应用实践与趋势

来源&#xff1a;阿里云研究中心《崛起的超级智能》一书主要阐述当今天人类为人工智能的春天到来而兴奋&#xff0c;为人工智能是否超越人类而恐慌的时候&#xff0c;一个更为庞大、远超人类预期的智能形态正在崛起&#xff0c;种种迹象表明50年来&#xff0c;互联网正在从网状…

excel表格打印每页都有表头_这么漂亮的Excel表格,用黑白打印机打印真是可惜了...

我们在日常工作中&#xff0c;利用Excel制作的表格通过会填充颜色&#xff0c;设置字体和边框格式、颜色&#xff0c;把表格弄的漂漂亮亮的&#xff0c;一个是为了缓解视觉疲劳&#xff0c;另一个是为了老板看到漂亮的表格一不小心加工资呢&#xff0c;但是一个问题出来了&…

常用的分隔符有哪三种_加固博士:常用防水材料大比拼,究竟花落谁家?

建筑物漏水问题&#xff0c;是日常生活中非常常见的问题&#xff0c;房屋漏水怎么办才好呢&#xff1f;这个问题困扰了很多人&#xff0c;有些户主会图个省事&#xff0c;将漏水问题直接交给装修公司&#xff0c;只要愿意支付防水施工费用&#xff0c;装修公司就会安排资深的师…

互联网大脑,城市大脑的“大脑”究竟什么含义?

来源&#xff1a;刘锋的未来课堂21世纪以来&#xff0c;科技领域与一个神经学领域的概念 “大脑”出现了意外的结合&#xff0c;涌现出诸多前沿科技的大脑概念。从互联网大脑&#xff0c;城市大脑&#xff0c;到谷歌大脑&#xff0c;百度大脑&#xff0c;讯飞超脑、360安全大脑…

电脑fps低怎么办_电脑想要英雄联盟玩的流畅,主要看哪几个方面?

刚刚在找素材的时候&#xff0c;看到有个兄弟在某乎上说打英雄联盟有点卡&#xff0c;换个固态硬盘能不能提升游戏体验。在看到这个问题之后&#xff0c;小编发现原来还有很多电脑小白不知道什么东西会影响游戏体验。所以今天我就大概说一下玩英雄联盟这款游戏看重的地方吧。小…

使用代理下载Unity AssetStore上资源的方法

Unity的AssetStore下载package的时候经常抽风&#xff0c;而且开了代理工具的全局代理依然无效。 检索网络后得知&#xff0c;这是因为它下载的时候不检测IE代理设置&#xff0c;而是取环境变量中HTTPS_proxy和HTTP_proxy的值&#xff0c;所以添加这两个变量并指定其为你的代理…

2019华为全联接大会(汇总)

来源&#xff1a;龙为科技2019年9月18-20日&#xff0c;华为公司在上海举办第四届华为全联接大会&#xff08;HUAWEI CONNECT&#xff09;&#xff0c;发布云和AI的最新产品与解决方案&#xff0c;分享如何应用云和AI的技术,推进数字化转型的最新实践。华为首次发布整体计算战略…

xlwings 合并单元格 读取_xlwings,让excel飞起来

excel已经成为必不可少的数据处理软件&#xff0c;几乎天天在用。python有很多支持操作excel的第三方库&#xff0c;xlwings是其中一个。关于xlwingsxlwings开源免费&#xff0c;能够非常方便的读写Excel文件中的数据&#xff0c;并且能够进行单元格格式的修改。xlwings还可以和…

一棵树的生物量怎么算_宾利的眼镜盒价值2万,算坑人吗?网友:良心产品,不坑穷人...

阅读本文前&#xff0c;请您先点击上面的蓝色字体“总李谈车”&#xff0c;再点击“关注”&#xff0c;这样您就可以继续免费收到文章了。每天都有分享&#xff0c;完全是免费订阅&#xff0c;请放心关注。原创持续更新“第1991天”宾利&#xff0c;全球顶级豪车之一&#xff0…

Gartner 2019年人工智能成熟度曲线的超前趋势

来源&#xff1a;FreeBuf.COMGartner的这种成熟度周期凸显出人工智能正以多种不同的方式影响企业根据Gartner对2019年的CIO议程调查&#xff0c;2018年至2019年间&#xff0c;部署人工智能(AI)的组织从4%增长到了14%。与几年前相比&#xff0c;人工智能正在以多种不同的方式影响…

生产环境可以用吗_柑橘可以生产果醋吗?柑橘果醋的生产工艺是什么?

柑橘可以生产果醋吗&#xff1f;柑橘果醋的生产工艺是什么&#xff1f;柑橘果醋的生产工艺&#xff0c;其特征在于它是以新鲜、成熟的柑橘类水果&#xff0c;特别是宽皮桔类、柑类、橙类等柑橘为原料&#xff0c;通过去皮、榨汁、离心分离、脱苦、低温酒精发酵、醋酸发酵、陈酿…

a4b5笔记本大小对比_天津订做笔记本*价格多少【广通印务】

天津订做笔记本*价格多少【广通印务】很多礼品盒包装设计师选择将绿色和环保理念融入到礼品盒包装设计中&#xff0c;让整个礼品盒包装充满自然之气&#xff0c;不但愉悦了客户的心情。 廊坊市广通印务有限公司是廊坊印刷行业的专家&#xff0c;——执着追求精彩印刷品&#xf…