移动互联网产品server端的技术栈

在新版产品Server端技术选型上,我选了 RESTEasy+Netty+Spring IOC+Mybatis 技术栈。目前没有看到中文blog里有比较详细的实践文章。记录下我使用时遇到的问题。

API设计上采用了目前比较流行的RESTful架构,URL上表达出接口版本号信息.

/v2.0/user/login

关于http方法映射业务形式,这里就不再赘述,感兴趣的可以看下RESTful的相关文章。
相关maven依赖:

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
    <artifactId>antzb-resteasy</artifactId>
    <groupId>com.antzb</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>antzb-app</artifactId>

<properties>
    <resteasy.version>3.0.13.Final</resteasy.version>
    <netty.version>4.0.33.Final</netty.version>
    <org.springframework-version>3.2.2.RELEASE</org.springframework-version>
    <org.aspectj-version>1.6.10</org.aspectj-version>
    <org.slf4j-version>1.6.6</org.slf4j-version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.antzb</groupId>
        <artifactId>antzb-base</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
    </dependency>
    <dependency>
        <groupId>org.postgis</groupId>
        <artifactId>postgis-jdbc</artifactId>
    </dependency>

    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
    </dependency>
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jackson2-provider</artifactId>
        <version>${resteasy.version}</version>
    </dependency>

    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jaxrs</artifactId>
        <version>${resteasy.version}</version>
    </dependency>
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>async-http-servlet-3.0</artifactId>
        <version>${resteasy.version}</version>
    </dependency>
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-netty4</artifactId>
        <version>${resteasy.version}</version>
    </dependency>
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>${netty.version}</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.9</version>
        <scope>test</scope>
    </dependency>
    <!-- JAXB support -->
    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-jaxb-provider</artifactId>
        <version>${resteasy.version}</version>
    </dependency>
    <!-- Servlet -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>servlet-api</artifactId>
        <version>2.5</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
    </dependency><dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>4.2.4.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
    </dependency>

    <dependency>
        <groupId>org.jboss.resteasy</groupId>
        <artifactId>resteasy-validator-provider-11</artifactId>
        <version>3.0.15.Final</version>
    </dependency>
    <dependency>
        <groupId>javax.el</groupId>
        <artifactId>el-api</artifactId>
        <version>2.2.1-b04</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.6.2</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>1.5.0.RELEASE</version>
    </dependency>
    <!--云通信短信验证码-->
    <dependency>
        <groupId>com.cloopen</groupId>
        <artifactId>CCPRestSmsSDK</artifactId>
        <version>v2.6.3r</version>
    </dependency>
    <!-- Logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>${org.slf4j-version}</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>${org.slf4j-version}</version>
        <scope>runtime</scope>
        <exclusions>
            <exclusion>
                <artifactId>slf4j-api</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>${org.slf4j-version}</version>
        <scope>runtime</scope>
        <exclusions>
            <exclusion>
                <artifactId>slf4j-api</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
        <exclusions>
            <exclusion>
                <groupId>javax.mail</groupId>
                <artifactId>mail</artifactId>
            </exclusion>
            <exclusion>
                <groupId>javax.jms</groupId>
                <artifactId>jms</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.sun.jdmk</groupId>
                <artifactId>jmxtools</artifactId>
            </exclusion>
            <exclusion>
                <groupId>com.sun.jmx</groupId>
                <artifactId>jmxri</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>2.4.3</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.4.3</version>
    </dependency>
</dependencies>
<build>
    <plugins>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.6</version>
            <configuration>
                <descriptors>
                    <descriptor>src/main/assembly/assembly.xml</descriptor>
                </descriptors>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

    </plugins>
</build></project> 

服务运行不依赖jettytomcat容器。易于部署,压力上来了可以很方便的水平扩展。

项目层级如下:

1
2
3
4
5
6
src/main/assembly
src/main/java
src/main/resources
src/test/java/
src/test/resources
src/scripts/

使用assembly这个maven插件自定义打包。
关于RESTEasy Startup程序我并没有把它交给Spring容器管理。其中的service,dao层交给了Spring管理,还有事务相关,不得不说SpringAOP确实很强大。

入口类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

/**
* Created by flea on 16/2/18.
*/
public class App {
private static org.apache.log4j.Logger logger= org.apache.log4j.Logger.getLogger(App.class);
public static void main(String[] args) throws Exception {
String host = "0.0.0.0";
int port = 9000;
if (args.length > 0) {
host = args[0];
}
if (args.length > 1) {
port = Integer.parseInt(args[1]);
}
logger.info("app server init 中......");
NettyJaxrsServer netty = new NettyJaxrsServer();
ResteasyDeployment deployment = new ResteasyDeployment();
new BaseController();
List<String> arr =new ArrayList<>();
arr.add(AuthTokenFilter.class.getName());
arr.add(SqlSessionCloseable.class.getName());
arr.add(ResourceNotFoundExceptionHandler.class.getName());
arr.add(ValidationExceptionMapper.class.getName());
deployment.setProviderClasses(arr);
deployment.setApplication(new MyApplication());
netty.setDeployment(deployment);
netty.setHostname(host);
netty.setPort(port);
netty.setRootResourcePath("/v2.0/");
netty.setSecurityDomain(null);
ConstProperties.init();
logger.info("app server 启动成功");
netty.start();}}

对外交互数据格式使用json。自定义了404,403,状态时response数据。增加鉴权过滤器,验证是否登录。关于自定义相关mapper只需要实现javax.ws.rs.ext.ExceptionMapper并根据不同的Exception返回不同数据即可,并在程序入口出注入到providerClass里。参数valiad 在3.x的RESTEasy里有所不同,目前网上的配置大都是2.x版配置。现在只需要引入hibernate-validator,RESTEasy-validator providerjar包即可。只需在要使用的接口方法上加上@Valid@NotNull注解即可。

关于filter的设置和以上有所不同。RESTEasy把它理解为一个是client端的filter,一个是server端的filter,对应的接口类为ContainerRequestFilter,ContainerResponseFilter

     @Provider
     public class AuthTokenFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
    logger.info("User-Agent " + containerRequestContext.getHeaderString("User-Agent"));
    String path = containerRequestContext.getUriInfo().getRequestUri().getPath();
    ResultMessage message = new ResultMessage();
    logger.info("path ===" + path);
    if (!path.startsWith(ConstProperties.getVersion())) {
        message.setCode(Response.Status.NOT_FOUND.getStatusCode());
        message.setMessage("api版本号不对!");  containerRequestContext.abortWith(Response.status(Response.Status.NOT_FOUND).entity(message.toString()).header("Content-Type", "application/json;charset=UTF-8").build());
    }
    if (!notCheckPath.contains(path)) {
        String token = containerRequestContext.getHeaderString("Authorization");
        if (StringUtil.isNullOrEmpty(token)) {
            message.setCode(Response.Status.UNAUTHORIZED.getStatusCode());
            message.setMessage("请登录!!");
            containerRequestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity(message.toString()).header("Content-Type", "application/json;charset=UTF-8").build());
        }
    }
    return;}}

然后只需在需要过滤的地方加上@PreMath注解即可。server端的过滤器不用再另加主键。以上所的所有自定义功能都要记得把它注入到RESTEasy providerClass里。