Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
爱吃血肠
spring-framework
提交
29db80c3
S
spring-framework
项目概览
爱吃血肠
/
spring-framework
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
spring-framework
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
29db80c3
编写于
2月 07, 2016
作者:
R
Rossen Stoyanchev
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add FreeMarker View, ViewResolver and basic hierarchy
上级
55d37c05
变更
13
隐藏空白更改
内联
并排
Showing
13 changed file
with
1134 addition
and
2 deletion
+1134
-2
spring-web-reactive/build.gradle
spring-web-reactive/build.gradle
+2
-0
spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/ResponseBodyResultHandler.java
...reactive/method/annotation/ResponseBodyResultHandler.java
+1
-1
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/AbstractUrlBasedView.java
...ringframework/web/reactive/view/AbstractUrlBasedView.java
+85
-0
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/AbstractView.java
...a/org/springframework/web/reactive/view/AbstractView.java
+166
-0
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/UrlBasedViewResolver.java
...ringframework/web/reactive/view/UrlBasedViewResolver.java
+168
-0
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/ViewResolverResultHandler.java
...ramework/web/reactive/view/ViewResolverResultHandler.java
+13
-1
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/ViewResolverSupport.java
...pringframework/web/reactive/view/ViewResolverSupport.java
+116
-0
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/freemarker/FreeMarkerConfig.java
...mework/web/reactive/view/freemarker/FreeMarkerConfig.java
+39
-0
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/freemarker/FreeMarkerConfigurer.java
...rk/web/reactive/view/freemarker/FreeMarkerConfigurer.java
+116
-0
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/freemarker/FreeMarkerView.java
...ramework/web/reactive/view/freemarker/FreeMarkerView.java
+219
-0
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/freemarker/FreeMarkerViewResolver.java
.../web/reactive/view/freemarker/FreeMarkerViewResolver.java
+58
-0
spring-web-reactive/src/test/java/org/springframework/web/reactive/view/freemarker/FreeMarkerViewTests.java
...ork/web/reactive/view/freemarker/FreeMarkerViewTests.java
+150
-0
spring-web-reactive/src/test/resources/org/springframework/web/reactive/view/freemarker/test.ftl
...org/springframework/web/reactive/view/freemarker/test.ftl
+1
-0
未找到文件。
spring-web-reactive/build.gradle
浏览文件 @
29db80c3
...
...
@@ -84,6 +84,7 @@ dependencies {
compile
"io.projectreactor:reactor-core:${reactorVersion}"
compile
"commons-logging:commons-logging:1.2"
optional
"org.springframework:spring-context-support:${springVersion}"
// for FreeMarker
optional
'io.reactivex:rxjava:1.1.0'
optional
"io.reactivex:rxnetty-http:0.5.0-SNAPSHOT"
optional
"com.fasterxml.jackson.core:jackson-databind:2.6.2"
...
...
@@ -95,6 +96,7 @@ dependencies {
optional
'io.undertow:undertow-core:1.3.5.Final'
optional
"org.eclipse.jetty:jetty-server:${jettyVersion}"
optional
"org.eclipse.jetty:jetty-servlet:${jettyVersion}"
optional
(
"org.freemarker:freemarker:2.3.23"
)
provided
"javax.servlet:javax.servlet-api:3.1.0"
...
...
spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/ResponseBodyResultHandler.java
浏览文件 @
29db80c3
...
...
@@ -66,7 +66,7 @@ public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered
private
final
Map
<
Encoder
<?>,
List
<
MediaType
>>
mediaTypesByEncoder
;
private
int
order
=
0
;
private
int
order
=
0
;
// TODO: should be MAX_VALUE
public
ResponseBodyResultHandler
(
List
<
Encoder
<?>>
encoders
,
ConversionService
service
)
{
...
...
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/AbstractUrlBasedView.java
0 → 100644
浏览文件 @
29db80c3
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.springframework.web.reactive.view
;
import
java.util.Locale
;
import
org.springframework.beans.factory.InitializingBean
;
/**
* Abstract base class for URL-based views. Provides a consistent way of
* holding the URL that a View wraps, in the form of a "url" bean property.
*
* @author Rossen Stoyanchev
*/
public
abstract
class
AbstractUrlBasedView
extends
AbstractView
implements
InitializingBean
{
private
String
url
;
/**
* Constructor for use as a bean.
*/
protected
AbstractUrlBasedView
()
{
}
/**
* Create a new AbstractUrlBasedView with the given URL.
*/
protected
AbstractUrlBasedView
(
String
url
)
{
this
.
url
=
url
;
}
/**
* Set the URL of the resource that this view wraps.
* The URL must be appropriate for the concrete View implementation.
*/
public
void
setUrl
(
String
url
)
{
this
.
url
=
url
;
}
/**
* Return the URL of the resource that this view wraps.
*/
public
String
getUrl
()
{
return
this
.
url
;
}
@Override
public
void
afterPropertiesSet
()
throws
Exception
{
if
(
getUrl
()
==
null
)
{
throw
new
IllegalArgumentException
(
"Property 'url' is required"
);
}
}
/**
* Check whether the resource for the configured URL actually exists.
* @param locale the desired Locale that we're looking for
* @return {@code false} if the resource exists
* {@code false} if we know that it does not exist
* @throws Exception if the resource exists but is invalid (e.g. could not be parsed)
*/
public
abstract
boolean
checkResourceExists
(
Locale
locale
)
throws
Exception
;
@Override
public
String
toString
()
{
return
super
.
toString
()
+
"; URL ["
+
getUrl
()
+
"]"
;
}
}
\ No newline at end of file
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/AbstractView.java
0 → 100644
浏览文件 @
29db80c3
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.springframework.web.reactive.view
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.LinkedHashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Optional
;
import
org.apache.commons.logging.Log
;
import
org.apache.commons.logging.LogFactory
;
import
reactor.core.publisher.Flux
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.context.ApplicationContextAware
;
import
org.springframework.core.io.buffer.DataBuffer
;
import
org.springframework.core.io.buffer.DataBufferAllocator
;
import
org.springframework.core.io.buffer.DefaultDataBufferAllocator
;
import
org.springframework.http.MediaType
;
import
org.springframework.ui.ModelMap
;
import
org.springframework.util.Assert
;
import
org.springframework.web.reactive.HandlerResult
;
import
org.springframework.web.reactive.View
;
import
org.springframework.web.server.ServerWebExchange
;
/**
*
* @author Rossen Stoyanchev
*/
public
abstract
class
AbstractView
implements
View
,
ApplicationContextAware
{
/** Logger that is available to subclasses */
protected
final
Log
logger
=
LogFactory
.
getLog
(
getClass
());
private
final
List
<
MediaType
>
mediaTypes
=
new
ArrayList
<>(
4
);
private
DataBufferAllocator
bufferAllocator
=
new
DefaultDataBufferAllocator
();
private
ApplicationContext
applicationContext
;
public
AbstractView
()
{
this
.
mediaTypes
.
add
(
ViewResolverSupport
.
DEFAULT_CONTENT_TYPE
);
}
/**
* Set the supported media types for this view.
* Default is "text/html;charset=UTF-8".
*/
public
void
setSupportedMediaTypes
(
List
<
MediaType
>
supportedMediaTypes
)
{
Assert
.
notEmpty
(
supportedMediaTypes
,
"'supportedMediaTypes' is required."
);
this
.
mediaTypes
.
clear
();
if
(
supportedMediaTypes
!=
null
)
{
this
.
mediaTypes
.
addAll
(
supportedMediaTypes
);
}
}
/**
* Return the configured media types supported by this view.
*/
@Override
public
List
<
MediaType
>
getSupportedMediaTypes
()
{
return
this
.
mediaTypes
;
}
/**
* Configure the {@link DataBufferAllocator} to use for write I/O.
* <p>By default this is set to {@link DefaultDataBufferAllocator}.
* @param bufferAllocator the allocator to use
*/
public
void
setBufferAllocator
(
DataBufferAllocator
bufferAllocator
)
{
Assert
.
notNull
(
bufferAllocator
,
"'bufferAllocator' is required."
);
this
.
bufferAllocator
=
bufferAllocator
;
}
/**
* Return the configured buffer allocator, never {@code null}.
*/
public
DataBufferAllocator
getBufferAllocator
()
{
return
this
.
bufferAllocator
;
}
@Override
public
void
setApplicationContext
(
ApplicationContext
applicationContext
)
{
this
.
applicationContext
=
applicationContext
;
}
public
ApplicationContext
getApplicationContext
()
{
return
applicationContext
;
}
/**
* Prepare the model to render.
* @param result the result from handler execution
* @param contentType the content type selected to render with which should
* match one of the {@link #getSupportedMediaTypes() supported media types}.
* @param exchange the current exchange
* @return
*/
@Override
public
Flux
<
DataBuffer
>
render
(
HandlerResult
result
,
Optional
<
MediaType
>
contentType
,
ServerWebExchange
exchange
)
{
if
(
logger
.
isTraceEnabled
())
{
logger
.
trace
(
"Rendering view with model "
+
result
.
getModel
());
}
if
(
contentType
.
isPresent
())
{
exchange
.
getResponse
().
getHeaders
().
setContentType
(
contentType
.
get
());
}
Map
<
String
,
Object
>
mergedModel
=
getModelAttributes
(
result
,
exchange
);
return
renderInternal
(
mergedModel
,
exchange
);
}
/**
* Prepare the model to use for rendering.
* <p>The default implementation creates a combined output Map that includes
* model as well as static attributes with the former taking precedence.
*/
protected
Map
<
String
,
Object
>
getModelAttributes
(
HandlerResult
result
,
ServerWebExchange
exchange
)
{
ModelMap
model
=
result
.
getModel
();
int
size
=
(
model
!=
null
?
model
.
size
()
:
0
);
Map
<
String
,
Object
>
attributes
=
new
LinkedHashMap
<>(
size
);
if
(
model
!=
null
)
{
attributes
.
putAll
(
model
);
}
return
attributes
;
}
/**
* Subclasses must implement this method to actually render the view.
* @param renderAttributes combined output Map (never {@code null}),
* with dynamic values taking precedence over static attributes
* @param exchange current exchange
*/
protected
abstract
Flux
<
DataBuffer
>
renderInternal
(
Map
<
String
,
Object
>
renderAttributes
,
ServerWebExchange
exchange
);
@Override
public
String
toString
()
{
return
getClass
().
getName
();
}
}
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/UrlBasedViewResolver.java
0 → 100644
浏览文件 @
29db80c3
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.springframework.web.reactive.view
;
import
java.util.Locale
;
import
reactor.core.publisher.Mono
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.beans.factory.InitializingBean
;
import
org.springframework.web.reactive.View
;
/**
* A {@link org.springframework.web.reactive.ViewResolver ViewResolver} that
* allow direct resolution of symbolic view names to URLs without explicit
* mapping definition. This is useful if symbolic names match the names of view
* resources in a straightforward manner (i.e. the symbolic name is the unique
* part of the resource's filename), without the need for a dedicated mapping
* to be defined for each view.
*
* <p>Supports {@link AbstractUrlBasedView} subclasses like
* {@link org.springframework.web.reactive.view.freemarker.FreeMarkerView}.
* The view class for all views generated by this resolver can be specified
* via the "viewClass" property.
*
* <p>View names can either be resource URLs themselves, or get augmented by a
* specified prefix and/or suffix. Exporting an attribute that holds the
* RequestContext to all views is explicitly supported.
*
* <p>Example: prefix="templates/", suffix=".ftl", viewname="test" ->
* "templates/test.ftl"
*
* <p>As a special feature, redirect URLs can be specified via the "redirect:"
* prefix. E.g.: "redirect:myAction" will trigger a redirect to the given
* URL, rather than resolution as standard view name. This is typically used
* for redirecting to a controller URL after finishing a form workflow.
*
* <p>Note: This class does not support localized resolution, i.e. resolving
* a symbolic view name to different resources depending on the current locale.
* * @author Rossen Stoyanchev
*/
public
class
UrlBasedViewResolver
extends
ViewResolverSupport
implements
InitializingBean
{
private
Class
<?>
viewClass
;
private
String
prefix
=
""
;
private
String
suffix
=
""
;
/**
* Set the view class to instantiate through {@link #createUrlBasedView(String)}.
* @param viewClass a class that is assignable to the required view class
* which by default is AbstractUrlBasedView.
*/
public
void
setViewClass
(
Class
<?>
viewClass
)
{
if
(
viewClass
==
null
||
!
requiredViewClass
().
isAssignableFrom
(
viewClass
))
{
String
name
=
(
viewClass
!=
null
?
viewClass
.
getName
()
:
null
);
throw
new
IllegalArgumentException
(
"Given view class ["
+
name
+
"] "
+
"is not of type ["
+
requiredViewClass
().
getName
()
+
"]"
);
}
this
.
viewClass
=
viewClass
;
}
/**
* Return the view class to be used to create views.
*/
protected
Class
<?>
getViewClass
()
{
return
this
.
viewClass
;
}
/**
* Return the required type of view for this resolver.
* This implementation returns {@link AbstractUrlBasedView}.
* @see AbstractUrlBasedView
*/
protected
Class
<?>
requiredViewClass
()
{
return
AbstractUrlBasedView
.
class
;
}
/**
* Set the prefix that gets prepended to view names when building a URL.
*/
public
void
setPrefix
(
String
prefix
)
{
this
.
prefix
=
(
prefix
!=
null
?
prefix
:
""
);
}
/**
* Return the prefix that gets prepended to view names when building a URL.
*/
protected
String
getPrefix
()
{
return
this
.
prefix
;
}
/**
* Set the suffix that gets appended to view names when building a URL.
*/
public
void
setSuffix
(
String
suffix
)
{
this
.
suffix
=
(
suffix
!=
null
?
suffix
:
""
);
}
/**
* Return the suffix that gets appended to view names when building a URL.
*/
protected
String
getSuffix
()
{
return
this
.
suffix
;
}
@Override
public
void
afterPropertiesSet
()
throws
Exception
{
if
(
getViewClass
()
==
null
)
{
throw
new
IllegalArgumentException
(
"Property 'viewClass' is required"
);
}
}
@Override
public
Mono
<
View
>
resolveViewName
(
String
viewName
,
Locale
locale
)
{
AbstractUrlBasedView
urlBasedView
=
createUrlBasedView
(
viewName
);
View
view
=
applyLifecycleMethods
(
viewName
,
urlBasedView
);
try
{
return
(
urlBasedView
.
checkResourceExists
(
locale
)
?
Mono
.
just
(
view
)
:
Mono
.
empty
());
}
catch
(
Exception
ex
)
{
return
Mono
.
error
(
ex
);
}
}
/**
* Creates a new View instance of the specified view class and configures it.
* Does <i>not</i> perform any lookup for pre-defined View instances.
* <p>Spring lifecycle methods as defined by the bean container do not have to
* be called here; those will be applied by the {@code loadView} method
* after this method returns.
* <p>Subclasses will typically call {@code super.buildView(viewName)}
* first, before setting further properties themselves. {@code loadView}
* will then apply Spring lifecycle methods at the end of this process.
* @param viewName the name of the view to build
* @return the View instance
*/
protected
AbstractUrlBasedView
createUrlBasedView
(
String
viewName
)
{
AbstractUrlBasedView
view
=
(
AbstractUrlBasedView
)
BeanUtils
.
instantiateClass
(
getViewClass
());
view
.
setSupportedMediaTypes
(
getSupportedMediaTypes
());
view
.
setBufferAllocator
(
getBufferAllocator
());
view
.
setUrl
(
getPrefix
()
+
viewName
+
getSuffix
());
return
view
;
}
private
View
applyLifecycleMethods
(
String
viewName
,
AbstractView
view
)
{
return
(
View
)
getApplicationContext
().
getAutowireCapableBeanFactory
().
initializeBean
(
view
,
viewName
);
}
}
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/ViewResolverResultHandler.java
浏览文件 @
29db80c3
...
...
@@ -24,6 +24,7 @@ import java.util.Optional;
import
reactor.core.publisher.Flux
;
import
reactor.core.publisher.Mono
;
import
org.springframework.core.Ordered
;
import
org.springframework.core.convert.ConversionService
;
import
org.springframework.core.io.buffer.DataBuffer
;
import
org.springframework.util.Assert
;
...
...
@@ -45,12 +46,14 @@ import org.springframework.web.server.ServerWebExchange;
*
* @author Rossen Stoyanchev
*/
public
class
ViewResolverResultHandler
implements
HandlerResultHandler
{
public
class
ViewResolverResultHandler
implements
HandlerResultHandler
,
Ordered
{
private
final
List
<
ViewResolver
>
viewResolvers
=
new
ArrayList
<>(
4
);
private
final
ConversionService
conversionService
;
private
int
order
=
Integer
.
MAX_VALUE
;
public
ViewResolverResultHandler
(
List
<
ViewResolver
>
resolvers
,
ConversionService
service
)
{
Assert
.
notEmpty
(
resolvers
,
"At least one ViewResolver is required."
);
...
...
@@ -67,6 +70,15 @@ public class ViewResolverResultHandler implements HandlerResultHandler {
return
Collections
.
unmodifiableList
(
this
.
viewResolvers
);
}
public
void
setOrder
(
int
order
)
{
this
.
order
=
order
;
}
@Override
public
int
getOrder
()
{
return
this
.
order
;
}
// TODO: @ModelAttribute return value, declared Object return value (either String or View)
...
...
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/ViewResolverSupport.java
0 → 100644
浏览文件 @
29db80c3
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.springframework.web.reactive.view
;
import
java.util.ArrayList
;
import
java.util.List
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.context.ApplicationContextAware
;
import
org.springframework.core.Ordered
;
import
org.springframework.core.io.buffer.DataBufferAllocator
;
import
org.springframework.core.io.buffer.DefaultDataBufferAllocator
;
import
org.springframework.http.MediaType
;
import
org.springframework.util.Assert
;
import
org.springframework.web.reactive.ViewResolver
;
/**
* Base class for {@code ViewResolver} implementations with shared properties.
*
* @author Rossen Stoyanchev
* @since 4.3
*/
public
abstract
class
ViewResolverSupport
implements
ViewResolver
,
ApplicationContextAware
,
Ordered
{
public
static
final
MediaType
DEFAULT_CONTENT_TYPE
=
MediaType
.
parseMediaType
(
"text/html;charset=UTF-8"
);
private
List
<
MediaType
>
mediaTypes
=
new
ArrayList
<>(
4
);
private
DataBufferAllocator
bufferAllocator
=
new
DefaultDataBufferAllocator
();
private
ApplicationContext
applicationContext
;
private
int
order
=
Integer
.
MAX_VALUE
;
public
ViewResolverSupport
()
{
this
.
mediaTypes
.
add
(
DEFAULT_CONTENT_TYPE
);
}
/**
* Set the supported media types for this view.
* Default is "text/html;charset=UTF-8".
*/
public
void
setSupportedMediaTypes
(
List
<
MediaType
>
supportedMediaTypes
)
{
Assert
.
notEmpty
(
supportedMediaTypes
,
"'supportedMediaTypes' is required."
);
this
.
mediaTypes
.
clear
();
if
(
supportedMediaTypes
!=
null
)
{
this
.
mediaTypes
.
addAll
(
supportedMediaTypes
);
}
}
/**
* Return the configured media types supported by this view.
*/
public
List
<
MediaType
>
getSupportedMediaTypes
()
{
return
this
.
mediaTypes
;
}
/**
* Configure the {@link DataBufferAllocator} to use for write I/O.
* <p>By default this is set to {@link DefaultDataBufferAllocator}.
* @param bufferAllocator the allocator to use
*/
public
void
setBufferAllocator
(
DataBufferAllocator
bufferAllocator
)
{
Assert
.
notNull
(
bufferAllocator
,
"'bufferAllocator' is required."
);
this
.
bufferAllocator
=
bufferAllocator
;
}
/**
* Return the configured buffer allocator, never {@code null}.
*/
public
DataBufferAllocator
getBufferAllocator
()
{
return
this
.
bufferAllocator
;
}
@Override
public
void
setApplicationContext
(
ApplicationContext
applicationContext
)
{
this
.
applicationContext
=
applicationContext
;
}
public
ApplicationContext
getApplicationContext
()
{
return
this
.
applicationContext
;
}
/**
* Set the order in which this {@link ViewResolver}
* is evaluated.
*/
public
void
setOrder
(
int
order
)
{
this
.
order
=
order
;
}
/**
* Return the order in which this {@link ViewResolver} is evaluated.
*/
@Override
public
int
getOrder
()
{
return
this
.
order
;
}
}
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/freemarker/FreeMarkerConfig.java
0 → 100644
浏览文件 @
29db80c3
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.springframework.web.reactive.view.freemarker
;
import
freemarker.template.Configuration
;
/**
* Interface to be implemented by objects that configure and manage a
* FreeMarker Configuration object in a web environment. Detected and
* used by {@link FreeMarkerView}.
*
* @author Rossen Stoyanchev
*/
public
interface
FreeMarkerConfig
{
/**
* Return the FreeMarker Configuration object for the current
* web application context.
* <p>A FreeMarker Configuration object may be used to set FreeMarker
* properties and shared objects, and allows to retrieve templates.
* @return the FreeMarker Configuration
*/
Configuration
getConfiguration
();
}
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/freemarker/FreeMarkerConfigurer.java
0 → 100644
浏览文件 @
29db80c3
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.springframework.web.reactive.view.freemarker
;
import
java.io.IOException
;
import
java.util.List
;
import
freemarker.cache.ClassTemplateLoader
;
import
freemarker.cache.TemplateLoader
;
import
freemarker.template.Configuration
;
import
freemarker.template.TemplateException
;
import
org.springframework.beans.factory.InitializingBean
;
import
org.springframework.context.ResourceLoaderAware
;
import
org.springframework.ui.freemarker.FreeMarkerConfigurationFactory
;
/**
* Configures FreeMarker for web usage via the "configLocation" and/or
* "freemarkerSettings" and/or "templateLoaderPath" properties.
* The simplest way to use this class is to specify just a "templateLoaderPath"
* (e.g. "classpath:templates"); you do not need any further configuration then.
*
* <p>This bean must be included in the application context of any application
* using {@link FreeMarkerView}. It exists purely to configure FreeMarker.
* It is not meant to be referenced by application components but just internally
* by {@code FreeMarkerView}. Implements {@link FreeMarkerConfig} to be found by
* {@code FreeMarkerView} without depending on the bean name the configurer.
*
* <p>Note that you can also refer to a pre-configured FreeMarker Configuration
* instance via the "configuration" property. This allows to share a FreeMarker
* Configuration for web and email usage for example.
*
* <p>TODO: macros
*
* <p>This configurer registers a template loader for this package, allowing to
* reference the "spring.ftl" macro library contained in this package:
*
* <pre class="code">
* <#import "/spring.ftl" as spring/>
* <@spring.bind "person.age"/>
* age is ${spring.status.value}</pre>
*
* Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher.
*
* @author Rossen Stoyanchev
*/
public
class
FreeMarkerConfigurer
extends
FreeMarkerConfigurationFactory
implements
FreeMarkerConfig
,
InitializingBean
,
ResourceLoaderAware
{
private
Configuration
configuration
;
public
FreeMarkerConfigurer
()
{
setDefaultEncoding
(
"UTF-8"
);
}
/**
* Set a pre-configured Configuration to use for the FreeMarker web config,
* e.g. a shared one for web and email usage. If this is not set,
* FreeMarkerConfigurationFactory's properties (inherited by this class)
* have to be specified.
*/
public
void
setConfiguration
(
Configuration
configuration
)
{
this
.
configuration
=
configuration
;
}
/**
* Initialize FreeMarkerConfigurationFactory's Configuration
* if not overridden by a pre-configured FreeMarker Configuation.
* <p>Sets up a ClassTemplateLoader to use for loading Spring macros.
* @see #createConfiguration
* @see #setConfiguration
*/
@Override
public
void
afterPropertiesSet
()
throws
IOException
,
TemplateException
{
if
(
this
.
configuration
==
null
)
{
this
.
configuration
=
createConfiguration
();
}
}
/**
* This implementation registers an additional ClassTemplateLoader
* for the Spring-provided macros, added to the end of the list.
*/
@Override
protected
void
postProcessTemplateLoaders
(
List
<
TemplateLoader
>
templateLoaders
)
{
templateLoaders
.
add
(
new
ClassTemplateLoader
(
FreeMarkerConfigurer
.
class
,
""
));
logger
.
info
(
"ClassTemplateLoader for Spring macros added to FreeMarker configuration"
);
}
/**
* Return the Configuration object wrapped by this bean.
*/
@Override
public
Configuration
getConfiguration
()
{
return
this
.
configuration
;
}
}
\ No newline at end of file
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/freemarker/FreeMarkerView.java
0 → 100644
浏览文件 @
29db80c3
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.springframework.web.reactive.view.freemarker
;
import
java.io.FileNotFoundException
;
import
java.io.IOException
;
import
java.io.OutputStreamWriter
;
import
java.io.Writer
;
import
java.util.Locale
;
import
java.util.Map
;
import
freemarker.core.ParseException
;
import
freemarker.template.Configuration
;
import
freemarker.template.DefaultObjectWrapperBuilder
;
import
freemarker.template.ObjectWrapper
;
import
freemarker.template.SimpleHash
;
import
freemarker.template.Template
;
import
freemarker.template.Version
;
import
reactor.core.publisher.Flux
;
import
org.springframework.beans.BeansException
;
import
org.springframework.beans.factory.BeanFactoryUtils
;
import
org.springframework.beans.factory.NoSuchBeanDefinitionException
;
import
org.springframework.context.ApplicationContextException
;
import
org.springframework.core.io.buffer.DataBuffer
;
import
org.springframework.web.reactive.view.AbstractUrlBasedView
;
import
org.springframework.web.server.ServerWebExchange
;
/**
* A {@code View} implementation that uses the FreeMarker template engine.
*
* <p>Depends on a single {@link FreeMarkerConfig} object such as
* {@link FreeMarkerConfigurer} being accessible in the application context.
* Alternatively set the FreeMarker configuration can be set directly on this
* class via {@link #setConfiguration}.
*
* <p>The {@link #setUrl(String) url} property is the location of the FreeMarker
* template relative to the FreeMarkerConfigurer's
* {@link FreeMarkerConfigurer#setTemplateLoaderPath templateLoaderPath}.
*
* <p>Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher.
*
* @author Rossen Stoyanchev
*/
public
class
FreeMarkerView
extends
AbstractUrlBasedView
{
private
Configuration
configuration
;
private
String
encoding
;
/**
* Set the FreeMarker Configuration to be used by this view.
* <p>Typically this property is not set directly. Instead a single
* {@link FreeMarkerConfig} is expected in the Spring application context
* which is used to obtain the FreeMarker configuration.
*/
public
void
setConfiguration
(
Configuration
configuration
)
{
this
.
configuration
=
configuration
;
}
/**
* Return the FreeMarker configuration used by this view.
*/
protected
Configuration
getConfiguration
()
{
return
this
.
configuration
;
}
/**
* Set the encoding of the FreeMarker template file.
* <p>By default {@link FreeMarkerConfigurer} sets the default encoding in
* the FreeMarker configuration to "UTF-8". It's recommended to specify the
* encoding in the FreeMarker Configuration rather than per template if all
* your templates share a common encoding.
*/
public
void
setEncoding
(
String
encoding
)
{
this
.
encoding
=
encoding
;
}
/**
* Return the encoding for the FreeMarker template.
*/
protected
String
getEncoding
()
{
return
this
.
encoding
;
}
@Override
public
void
afterPropertiesSet
()
throws
Exception
{
super
.
afterPropertiesSet
();
if
(
getConfiguration
()
==
null
)
{
FreeMarkerConfig
config
=
autodetectConfiguration
();
setConfiguration
(
config
.
getConfiguration
());
}
}
/**
* Autodetect a {@link FreeMarkerConfig} object via the ApplicationContext.
* @return the Configuration instance to use for FreeMarkerViews
* @throws BeansException if no Configuration instance could be found
* @see #setConfiguration
*/
protected
FreeMarkerConfig
autodetectConfiguration
()
throws
BeansException
{
try
{
return
BeanFactoryUtils
.
beanOfTypeIncludingAncestors
(
getApplicationContext
(),
FreeMarkerConfig
.
class
,
true
,
false
);
}
catch
(
NoSuchBeanDefinitionException
ex
)
{
throw
new
ApplicationContextException
(
"Must define a single FreeMarkerConfig bean in this web application context "
+
"(may be inherited): FreeMarkerConfigurer is the usual implementation. "
+
"This bean may be given any name."
,
ex
);
}
}
/**
* Check that the FreeMarker template used for this view exists and is valid.
* <p>Can be overridden to customize the behavior, for example in case of
* multiple templates to be rendered into a single view.
*/
@Override
public
boolean
checkResourceExists
(
Locale
locale
)
throws
Exception
{
try
{
// Check that we can get the template, even if we might subsequently get it again.
getTemplate
(
locale
);
return
true
;
}
catch
(
FileNotFoundException
ex
)
{
if
(
logger
.
isDebugEnabled
())
{
logger
.
debug
(
"No FreeMarker view found for URL: "
+
getUrl
());
}
return
false
;
}
catch
(
ParseException
ex
)
{
throw
new
ApplicationContextException
(
"Failed to parse FreeMarker template for URL ["
+
getUrl
()
+
"]"
,
ex
);
}
catch
(
IOException
ex
)
{
throw
new
ApplicationContextException
(
"Could not load FreeMarker template for URL ["
+
getUrl
()
+
"]"
,
ex
);
}
}
@Override
protected
Flux
<
DataBuffer
>
renderInternal
(
Map
<
String
,
Object
>
renderAttributes
,
ServerWebExchange
exchange
)
{
// Expose all standard FreeMarker hash models.
SimpleHash
freeMarkerModel
=
getTemplateModel
(
renderAttributes
,
exchange
);
if
(
logger
.
isDebugEnabled
())
{
logger
.
debug
(
"Rendering FreeMarker template ["
+
getUrl
()
+
"]."
);
}
Locale
locale
=
Locale
.
getDefault
();
// TODO
DataBuffer
dataBuffer
=
getBufferAllocator
().
allocateBuffer
();
try
{
Writer
writer
=
new
OutputStreamWriter
(
dataBuffer
.
asOutputStream
());
getTemplate
(
locale
).
process
(
freeMarkerModel
,
writer
);
}
catch
(
IOException
ex
)
{
String
message
=
"Could not load FreeMarker template for URL ["
+
getUrl
()
+
"]"
;
return
Flux
.
error
(
new
IllegalStateException
(
message
,
ex
));
}
catch
(
Throwable
ex
)
{
return
Flux
.
error
(
ex
);
}
return
Flux
.
just
(
dataBuffer
);
}
/**
* Build a FreeMarker template model for the given model Map.
* <p>The default implementation builds a {@link SimpleHash}.
* @param model the model to use for rendering
* @param exchange current exchange
* @return the FreeMarker template model, as a {@link SimpleHash} or subclass thereof
*/
protected
SimpleHash
getTemplateModel
(
Map
<
String
,
Object
>
model
,
ServerWebExchange
exchange
)
{
SimpleHash
fmModel
=
new
SimpleHash
(
getObjectWrapper
());
fmModel
.
putAll
(
model
);
return
fmModel
;
}
/**
* Return the configured FreeMarker {@link ObjectWrapper}, or the
* {@link ObjectWrapper#DEFAULT_WRAPPER default wrapper} if none specified.
* @see freemarker.template.Configuration#getObjectWrapper()
*/
protected
ObjectWrapper
getObjectWrapper
()
{
ObjectWrapper
ow
=
getConfiguration
().
getObjectWrapper
();
Version
version
=
Configuration
.
DEFAULT_INCOMPATIBLE_IMPROVEMENTS
;
return
(
ow
!=
null
?
ow
:
new
DefaultObjectWrapperBuilder
(
version
).
build
());
}
/**
* Retrieve the FreeMarker template for the given locale,
* to be rendering by this view.
* <p>By default, the template specified by the "url" bean property
* will be retrieved.
* @param locale the current locale
* @return the FreeMarker template to render
*/
protected
Template
getTemplate
(
Locale
locale
)
throws
IOException
{
return
(
getEncoding
()
!=
null
?
getConfiguration
().
getTemplate
(
getUrl
(),
locale
,
getEncoding
())
:
getConfiguration
().
getTemplate
(
getUrl
(),
locale
));
}
}
spring-web-reactive/src/main/java/org/springframework/web/reactive/view/freemarker/FreeMarkerViewResolver.java
0 → 100644
浏览文件 @
29db80c3
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.springframework.web.reactive.view.freemarker
;
import
org.springframework.web.reactive.view.UrlBasedViewResolver
;
/**
* A {@code ViewResolver} for resolving {@link FreeMarkerView} instances, i.e.
* FreeMarker templates and custom subclasses of it.
*
* <p>The view class for all views generated by this resolver can be specified
* via the "viewClass" property. See {@link UrlBasedViewResolver} for details.
*
* @author Rossen Stoyanchev
*/
public
class
FreeMarkerViewResolver
extends
UrlBasedViewResolver
{
/**
* Simple constructor.
*/
public
FreeMarkerViewResolver
()
{
setViewClass
(
requiredViewClass
());
}
/**
* Convenience constructor with a prefix and suffix.
* @param suffix the suffix to prepend view names with
* @param prefix the prefix to prepend view names with
*/
public
FreeMarkerViewResolver
(
String
prefix
,
String
suffix
)
{
setViewClass
(
requiredViewClass
());
setPrefix
(
prefix
);
setSuffix
(
suffix
);
}
/**
* Requires {@link FreeMarkerView}.
*/
@Override
protected
Class
<?>
requiredViewClass
()
{
return
FreeMarkerView
.
class
;
}
}
spring-web-reactive/src/test/java/org/springframework/web/reactive/view/freemarker/FreeMarkerViewTests.java
0 → 100644
浏览文件 @
29db80c3
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
org.springframework.web.reactive.view.freemarker
;
import
java.net.URI
;
import
java.nio.ByteBuffer
;
import
java.nio.charset.Charset
;
import
java.util.HashMap
;
import
java.util.Locale
;
import
java.util.Optional
;
import
freemarker.template.Configuration
;
import
org.junit.Before
;
import
org.junit.Rule
;
import
org.junit.Test
;
import
org.junit.rules.ExpectedException
;
import
reactor.core.publisher.Flux
;
import
reactor.core.test.TestSubscriber
;
import
org.springframework.context.ApplicationContextException
;
import
org.springframework.context.support.GenericApplicationContext
;
import
org.springframework.core.ResolvableType
;
import
org.springframework.core.io.buffer.DataBuffer
;
import
org.springframework.http.HttpMethod
;
import
org.springframework.http.server.reactive.MockServerHttpRequest
;
import
org.springframework.http.server.reactive.MockServerHttpResponse
;
import
org.springframework.ui.ExtendedModelMap
;
import
org.springframework.ui.ModelMap
;
import
org.springframework.web.context.WebApplicationContext
;
import
org.springframework.web.reactive.HandlerResult
;
import
org.springframework.web.server.ServerWebExchange
;
import
org.springframework.web.server.adapter.DefaultServerWebExchange
;
import
org.springframework.web.server.session.DefaultWebSessionManager
;
import
org.springframework.web.server.session.WebSessionManager
;
import
static
org
.
hamcrest
.
CoreMatchers
.
containsString
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
static
org
.
mockito
.
BDDMockito
.
given
;
import
static
org
.
mockito
.
Mockito
.
mock
;
/**
* @author Rossen Stoyanchev
*/
public
class
FreeMarkerViewTests
{
public
static
final
String
TEMPLATE_PATH
=
"classpath*:org/springframework/web/reactive/view/freemarker/"
;
private
static
final
Charset
UTF_8
=
Charset
.
forName
(
"UTF-8"
);
private
ServerWebExchange
exchange
;
private
GenericApplicationContext
context
;
private
Configuration
freeMarkerConfig
;
@Rule
public
final
ExpectedException
exception
=
ExpectedException
.
none
();
@Before
public
void
setUp
()
throws
Exception
{
this
.
context
=
new
GenericApplicationContext
();
this
.
context
.
refresh
();
FreeMarkerConfigurer
configurer
=
new
FreeMarkerConfigurer
();
configurer
.
setPreferFileSystemAccess
(
false
);
configurer
.
setTemplateLoaderPath
(
TEMPLATE_PATH
);
configurer
.
setResourceLoader
(
this
.
context
);
this
.
freeMarkerConfig
=
configurer
.
createConfiguration
();
FreeMarkerView
fv
=
new
FreeMarkerView
();
fv
.
setApplicationContext
(
this
.
context
);
MockServerHttpRequest
request
=
new
MockServerHttpRequest
(
HttpMethod
.
GET
,
new
URI
(
"/path"
));
MockServerHttpResponse
response
=
new
MockServerHttpResponse
();
WebSessionManager
manager
=
new
DefaultWebSessionManager
();
this
.
exchange
=
new
DefaultServerWebExchange
(
request
,
response
,
manager
);
}
@Test
public
void
noFreeMarkerConfig
()
throws
Exception
{
this
.
exception
.
expect
(
ApplicationContextException
.
class
);
this
.
exception
.
expectMessage
(
"Must define a single FreeMarkerConfig bean"
);
FreeMarkerView
view
=
new
FreeMarkerView
();
view
.
setApplicationContext
(
this
.
context
);
view
.
setUrl
(
"anythingButNull"
);
view
.
afterPropertiesSet
();
}
@Test
public
void
noTemplateName
()
throws
Exception
{
this
.
exception
.
expect
(
IllegalArgumentException
.
class
);
this
.
exception
.
expectMessage
(
"Property 'url' is required"
);
FreeMarkerView
freeMarkerView
=
new
FreeMarkerView
();
freeMarkerView
.
afterPropertiesSet
();
}
@Test
public
void
checkResourceExists
()
throws
Exception
{
FreeMarkerView
view
=
new
FreeMarkerView
();
view
.
setConfiguration
(
this
.
freeMarkerConfig
);
view
.
setUrl
(
"test.ftl"
);
assertTrue
(
view
.
checkResourceExists
(
Locale
.
US
));
}
@Test
public
void
render
()
throws
Exception
{
FreeMarkerView
view
=
new
FreeMarkerView
();
view
.
setConfiguration
(
this
.
freeMarkerConfig
);
view
.
setUrl
(
"test.ftl"
);
ModelMap
model
=
new
ExtendedModelMap
();
model
.
addAttribute
(
"hello"
,
"hi FreeMarker"
);
HandlerResult
result
=
new
HandlerResult
(
new
Object
(),
""
,
ResolvableType
.
NONE
,
model
);
Flux
<
DataBuffer
>
flux
=
view
.
render
(
result
,
Optional
.
empty
(),
this
.
exchange
);
TestSubscriber
<
DataBuffer
>
subscriber
=
new
TestSubscriber
<>();
subscriber
.
bindTo
(
flux
).
assertValuesWith
(
dataBuffer
->
assertEquals
(
"<html><body>hi FreeMarker</body></html>"
,
asString
(
dataBuffer
)));
}
private
static
String
asString
(
DataBuffer
dataBuffer
)
{
ByteBuffer
byteBuffer
=
dataBuffer
.
asByteBuffer
();
final
byte
[]
bytes
=
new
byte
[
byteBuffer
.
remaining
()];
byteBuffer
.
get
(
bytes
);
return
new
String
(
bytes
,
UTF_8
);
}
}
spring-web-reactive/src/test/resources/org/springframework/web/reactive/view/freemarker/test.ftl
0 → 100644
浏览文件 @
29db80c3
<html><body>
${hello}
</body></html>
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录