在我目前的公司中,我们正在开始一个新项目,该项目将是Java中的REST API,部署在像Tomcat这样的servlet容器中.在我之前使用REST框架(如JAX-RS和Jersey,JBOSS REST Easy,Spring MVC)的经验中,我知道使用类似于直接编写Servlet来处理请求的框架的一些优点.
(当然我们知道所提到的框架仍然使用Servlets)
我发现很难说服他们.因为他们建议编写servlet,认为它对性能更好(可能是这种情况,但我认为使用其中一个框架的开销对于REST API来说应该是微不足道的).
这是我的理由:
1)更少的样板和更简洁的代码(更易于维护和测试).使用JAX-RS框架或SpringMVC,您可以通过编写带有注释的方法来非常轻松地定义REST资源,注释指示资源的PATH,要使用的http方法,查询和url参数,接受的编码等标题等.
例:
@GET
@Path("/users")
@Produces({MediaType.APPLICATION_JSON})
public UserList getUsers(@QueryParam("group") String group) {
return userService.findUsers(group);
}
Run Code Online (Sandbox Code Playgroud)
使用servlet,您至少需要这样的东西:
在web.xml中映射每个servlet的url(在Servlet 3.0中不需要):
<servlet>
<servlet-name>UsersServlet</servlet-name>
<servlet-class>test.UsersServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UsersServlet</servlet-name>
<url-pattern>/users</url-pattern>
</servlet-mapping>
Run Code Online (Sandbox Code Playgroud)
然后在servlet类中:
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String group = request.getParameter("group");
response.setContentType("application/json");
PrintWriter out = response.getWriter();
JsonSerializer someJsonSerializer = new JsonSerializer();
String json = someJsonSerializer.serialize(userService.findUsers(group));
out.print(json);
}
Run Code Online (Sandbox Code Playgroud)
2)适应性.上述框架允许您轻松地向应用程序添加功能,否则您将需要手动执行,例如使用多个媒体类型输入和输出.例如,根据accept标头,使服务返回xml或json或任何其他服务.像SpringMVC和Jersey这样的框架可以很容易地为您的请求和响应配置序列化器/反序列化器.
3)REST最佳实践.通常,这些框架是基于对REST API遵循的最佳实践的充分理解而构建的,并且基于REST体系结构的标准进行定义,这使得构建可靠且标准的符合标准的应用程序变得更加容易.另一方面,Servlets为您提供了如何处理请求/响应的高度自由,以至于您很难意识到您根本不是RESTfull.
任何其他?
我在我的REST API中使用Jersey 2.10和Jackson序列化/反序列化功能.
我的想法是让我的REST API始终返回标准的JSON错误响应.为此,我有ExceptionMapper类,为Jersey应用程序中抛出的任何异常构建正确的json错误响应.我还有一个生成相同类型的JSON响应的jsp,我在web.xml中注册为error-page,它涵盖了在加载Jersey之前可能出现的所有错误.
但是有一种情况,我的Exception mappers和我的json生成jsp都没有工作,就是当一个坏的形成的json发送到POST REST端点时,它只返回以下消息:
HTTP/1.1 400 Bad Request
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, PUT
Content-Type: text/plain
Content-Length: 210
Date: Tue, 24 Jun 2014 22:14:11 GMT
Connection: close
Can not deserialize instance of com.example.rest.User[] out of START_OBJECT token
at [Source: org.glassfish.jersey.message.internal.EntityInputStream@1dcccac; line: 1, column: 1]
Run Code Online (Sandbox Code Playgroud)
如何让泽西岛返回我的自定义错误响应而不是这个?
更新:
基于@Lucasz的答案,我做了更多的研究,发现在com.fasterxml.jackson.jaxrs.base包中定义了两个异常映射器(https://github.com/FasterXML/jackson-jaxrs-providers/ tree/master/base/src/main/java/com/fasterxml/jackson/jaxrs/base)JsonMappingExceptionMapper和JsonParseExceptionMapper似乎正在影响我的自定义映射器.
如何取消注册那些映射器?
这就是我目前正在注册地图制作者的方式:
@ApplicationPath("/")
public class MyApp extends ResourceConfig{
public SyntheticAPIApp() {
packages("com.example.resource", "com.example.mapper");
register(org.glassfish.jersey.jackson.JacksonFeature.class);
}
}
Run Code Online (Sandbox Code Playgroud) 我是Vaadin框架的新手,我看起来非常有趣,使用eclipse和maven来开发和构建我的应用程序我觉得很烦人每次我做一个mvn干净安装它需要很长时间来构建应用程序,我发现这是因为它编译了整套小部件.
即使我只在我的布局中使用Button,也会在构建应用程序时花费太多.
我已经研究了一段时间在互联网和2本书,但找不到足够的信息,如何使它只编译我正在使用的组件,而不是整套.
我使用maven原型创建了项目:
mvn archetype:generate -DarchetypeGroupId=com.vaadin -DarchetypeArtifactId=vaadin-archetype-application -DarchetypeVersion=7.1.9
Run Code Online (Sandbox Code Playgroud)
我确信每次构建战争时都会编译widgetset,当我执行mvn clean时会删除目录:/ src/main/webapp/VAADIN/widgetsets和/ src/main/webapp/VAADIN/gwt-unitCache
当我运行mvn install时,构建将持续超过3分钟:
...
[INFO] Compiling 6 permutations
[INFO] Compiling permutation 0...
[INFO] Process output
[INFO] Compiling
[INFO] Compiling permutation 1...
[INFO] Process output
[INFO] Compiling
[INFO] Compiling permutation 3...
[INFO] Process output
[INFO] Compiling
[INFO] Compiling permutation 2...
[INFO] Compiling permutation 4...
[INFO] Compiling
[INFO] Compiling permutation 5...
[INFO] Compile of permutations succeeded
[INFO] Linking into /.../example/src/main/webapp/VAADIN/widgetsets/com.my.example.AppWidgetSet; Writing extras to /.../example/target/extra/com.my.example.AppWidgetSet
[INFO] Link succeeded
[INFO] Compilation succeeded …Run Code Online (Sandbox Code Playgroud) 尝试将作为参数传递的数组匹配到接收varargs数组的方法时,我遇到了问题.
其他问题/答案中提到的anyVararg()匹配器对我不起作用,因为我想确保提供的数组是我需要的数组.
我把问题简化为这个更易于理解和抽象问题的例子(我的真正问题是生产代码并具有业务逻辑,因此对于这个问题的目的而言会令人困惑):
@RunWith(MockitoJUnitRunner.class)
public class UnitTest {
private Object[] objectArray;
private List<Object> expected;
private TestTarget target;
@Before
public void setUp() {
objectArray = new Object[]{ new Object() };
expected = Arrays.asList(new Object(), new Object());
target = Mockito.spy(new TestTarget());
}
@Test
public void testMakeList() { // this pass as eq works well with normal array
doReturn(expected).when(target).toList(Mockito.eq(objectArray));
Assert.assertEquals(expected, target.makeList(objectArray));
}
@Test
public void testMakeList1() { // this one fails as eq is not working with varargs
doReturn(expected).when(target).toList1(Mockito.eq(objectArray));
Assert.assertEquals(expected, target.makeList1(objectArray));
}
@Test …Run Code Online (Sandbox Code Playgroud) 我发现在我开始为我的REST API使用swagger文档工具之后,我的war文件的大小增加了近4.5倍,从8.7 MB增加到39MB.我正在使用Maven来构建项目.
这是因为Swagger Scala依赖大尺寸,特别是scala编译器.所以我试图找出哪些依赖项并不是真正需要的.我在项目的Github页面中创建了一个新问题:https://github.com/wordnik/swagger-core/issues/624他们回答说删除任何scala依赖项不是一个好主意,因为框架是用该语言编写的这可能会破坏它.还建议将依赖项放在容器/服务器而不是战争中.
我有一个类,我使用Jackson对JSON,XML进行序列化/反序列化.
public class User {
Integer userId;
String name;
Integer groupId;
...
}
Run Code Online (Sandbox Code Playgroud)
我想在进行xml处理时忽略groupId,所以我的XML不会包含它:
<User>
<userId>...</userId>
<name>...</name>
</User>
Run Code Online (Sandbox Code Playgroud)
但是JSON会:
{
"userId":"...",
"name":"...",
"groupId":"..."
}
Run Code Online (Sandbox Code Playgroud)
我知道@JsonIgnore可以兼用,但我只想在xml中忽略它.
我知道可用于执行此操作的混合注释(/sf/answers/1603477641/),但我认为应该有一个简单的注释来执行此操作,但无法找到它.杰克逊的文档(至少对我而言)并不像我想要找到这些东西时那样好.
我们目前有 4 个使用 Spring Security Oauth2 项目进行身份验证的 Spring 应用程序。这些应用程序是 REST API,供我工作的公司中的其他内部应用程序使用。
在开发和 QA 环境中一切正常,因为我们没有进行负载平衡,现在我们处于预生产阶段,我们正面临负载平衡器 (LB) 的问题。
这是此问题的工作流程:
我们正在使用内存用户存储:
<bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" />
Run Code Online (Sandbox Code Playgroud)
有没有办法让不同的盒子共享同一个令牌存储?我知道有一个 JdbcTokenStore 可用于将令牌持久化到数据库,但我更愿意避免持久化令牌,因为这些应用程序指向仅存储业务信息的旧数据库。
首先,我想评论一下,我已经检查了Stack Overflow中的其他问题,并根据答案实现了我自己的方法:https://stackoverflow.com/a/14425801/2487263和/sf/answers/1127115461/
我试图在Spring 3.2,Spring MVC应用程序中使用Spring安全3.1来保护REST API,我使用简单配置的基本身份验证方法:
<http create-session="stateless" entry-point-ref="authenticationFailedEntryPoint">
<intercept-url pattern="/**" access="ROLE_USER"/>
<http-basic />
</http>
Run Code Online (Sandbox Code Playgroud)
如您所见,我使用自定义入口点,我有自己的ErrorResponse对象,我将以json格式添加到http响应,请参阅下面的代码:
@Component
public class AuthenticationFailedEntryPoint implements AuthenticationEntryPoint {
static Logger log = Logger.getLogger(AuthenticationFailedEntryPoint.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
log.error(ExceptionUtils.getStackTrace(authException));
ErrorResponse errorResponse = new ErrorResponse();
... here I fill my errorResponse object ...
ObjectMapper jsonMapper = new ObjectMapper();
response.setContentType("application/json;charset=UTF-8");
response.setStatus(status);
PrintWriter out = response.getWriter();
out.print(jsonMapper.writeValueAsString(errorResponse));
}
}
Run Code Online (Sandbox Code Playgroud)
我尝试了两种测试用例的方法:
这是请求/响应:
GET http://localhost:8081/accounts/accounts?accountNumber=1013
-- …Run Code Online (Sandbox Code Playgroud) 我使用带有Jersey 2.7的ResourceConfig并在Tomcat 7上进行部署,从web.xml迁移到完全Java配置.之后,我无法使用与web.xml方法相同的URL来访问服务.我不明白ResourceConfig如何影响路径.
我以前的web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>my.app</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.mypackage.resource,com.mypackage.providers</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.scanning.recursive</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>org.glassfish.jersey.filter.LoggingFilter</param-value>
</init-param>
<init-param>
<param-name>org.glassfish.jersey.server.ServerProperties.BV_SEND_ERROR_IN_RESPONSE</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>my.app</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Run Code Online (Sandbox Code Playgroud)
扩展ResourceConfig的配置类是:
MyRESTAPIApp.java
@ApplicationPath("")
public class MyRESTAPIApp extends ResourceConfig{
public MyRESTAPIApp () {
packages("com.mypackage.resource", "com.mypackage.providers");
register(org.glassfish.jersey.filter.LoggingFilter.class);
property("jersey.config.beanValidation.enableOutputValidationErrorEntity.server", "true");
}
}
Run Code Online (Sandbox Code Playgroud)
我的一个资源是:
FlagResource.java
@Path("my-resource")
public class FlagResource {
private MyService myService = new MyService();
@GET
@Produces(MediaType.APPLICATION_JSON)
public FlagResource getFlagResource(@NotNull @QueryParam("level") Long level) { …Run Code Online (Sandbox Code Playgroud) 在泽西岛2,可以这样做:
@GET
@PATH("user/{email}")
public IDto getUser(@NotNull @Email @PathParam("email") String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}
Run Code Online (Sandbox Code Playgroud)
但我无法在Spring MVC中创建类似的东西,似乎验证只在@RequestBody中提供对象或使用SpringMVC表单时完成,例如以下操作无效:
@RequestMapping(value="/user/{email}", method = RequestMethod.GET)
public @ResponseBody IDto getUser(@NotNull @Email @PathVariable String validEmail) {
return userManagementService.findUserByEmail(validEmail);
}
Run Code Online (Sandbox Code Playgroud)
还有其他类似的问题,但那些似乎是面向Spring MVC UI应用程序,在我的情况下,它只是一个返回JSON响应的REST API,因此我没有任何View映射/绑定到控制器.