当前位置: 首页 > 工具软件 > JAX-RS > 使用案例 >

springboot整合cxf-rt-transports-http-jetty发布jax-rs服务

柴瀚昂
2023-12-01

一、JAX-RS是什么?

JAX-RS,全称为Java API for RESTful Web Services,具体的指支持REST架构风格创建Web服务的规范,具体的实现有Apache CXF、Jersey、RESTEasy等,本文是由的是apache的实现cxf。

二、阅读本片博客你将掌握:

1、如何使用springboot发布jax-rs服务。
2、如何在jax-rs服务中配置拦截器并实现基于token的权限校验功能。

让我们开始把~
老规矩贴一下pom文件

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.syx</groupId>
    <artifactId>cxf-springboot</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <cxf.version>3.2.4</cxf.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxrs</artifactId>
            <version>3.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http-jetty</artifactId>
            <version>3.2.4</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.74</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

三、主要程序

1、定义一个用于资源访问的接口(直接使用类也行),同时使用jax-rs的注解进行标注,@PATH代表资源的请求路径,@Produces代表该接口响应的数据类型。在接口声明的方法上,@POST代表以POST类型的请求方式,@Consumes代表该方法所接受的参数类型。同时我们可以和客户端定义好接口方法中参数的json类型。

package com.syx.endpoint;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
@Path("/syxEndpoint")
@Produces({"application/json"})
public interface SYXEndPoint {
    /**
     *
     * @param args   {
     *   "RuleName":"",
     *   "Auth": "",
     *   "RequestParam": {
     *
     *   }
     * }
     * @return
     */
    @POST
    @Path("/service")
    @Consumes({"application/json"})
    String service(String args);

}

2、定义上面声明的资源接口的实现类,由于我们需要使用spring的ioc功能,所以标注上@Compoent注解,将其交给ioc容器管理。

package com.syx.endpoint.impl;
import com.alibaba.fastjson.JSON;
import com.syx.endpoint.SYXEndPoint;
import com.syx.endpoint.handler.Handler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Map;
@Component
public class SYXEndPointImpl implements SYXEndPoint {

    @Autowired
    private ApplicationContext ctx;

    @Override
    public String service(String args) {
        Map<String,Object> map = JSON.parseObject(args, Map.class);
        String ruleName = (String) map.get("RuleName");

        Handler handler = (Handler) ctx.getBean(ruleName);

        Map<String,Object> requestParam = (Map<String, Object>) map.get("RequestParam");

        return handler.service(requestParam).toJsonString();
    }

}

3、配置jax-rs服务并发布(由于我们使用的是cxf-rt-transports-http-jetty这个依赖,cxf将使用jetty服务器发布webService服务,当然还有其他的实现,比如netty、tomcat)

package com.syx.config;

import com.syx.endpoint.SYXEndPoint;
import com.syx.endpoint.interceptor.AuthInterceptor;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

import javax.annotation.PostConstruct;
import java.util.Collections;

@Configuration
@ImportResource(value = {"classpath:/META-INF/cxf/cxf.xml"})//加载类路径下的cxf配置文件,用于初始化SpringBus
public class EndpointConfig {


    @Autowired
    private SpringBus bus;

    @Autowired
    private SYXEndPoint syxEndPoint;

    @PostConstruct
    public void init(){
        JAXRSServerFactoryBean serverFactoryBean = new JAXRSServerFactoryBean();
        //配置服务实现类
        serverFactoryBean.setServiceBean(syxEndPoint);
        //配置服务的发布地址
        serverFactoryBean.setAddress("http://127.0.0.1:9999/cxf/");
        //添加请求响应对应的输入输出日志拦截器
        serverFactoryBean.setInInterceptors(Collections.singletonList(new LoggingInInterceptor()));
        serverFactoryBean.setOutInterceptors(Collections.singletonList(new LoggingOutInterceptor()));
        //添加自定义权限控制拦截器
        serverFactoryBean.getInInterceptors().add(new AuthInterceptor());
        serverFactoryBean.create();
    }

}

4、自定义权限拦截器并实现基于token的权限校验功能,这里我们自定一个权限校验类然后继承AbstractPhaseInterceptor同时重写handleMessage和handleFault两个方法,其中handleMessage主要是用于在请求我们的业务方法前进行一个拦截,这里我们配置了一个简单的拦截规则,如果用户请求的是获取token的处理器我们就直接放行,如果是其他的处理器我们的程序就进行token的校验,还有一部分权限资源的控制我没实现,大家可以自行实现(其实就是业务handler和角色的匹配)。handleFault方法主要是用于捕获我们在handleMessage处理中的异常,譬如token过期、token校验不通过等。目前这个自定义的权限拦截器比较粗糙(目前只处理Exception类型的错误,其实这个异常可以更准确 ),大家可以在handleFault中添加更多的异常处理类型。

package com.syx.endpoint.interceptor;
import com.alibaba.fastjson.JSON;
import com.syx.auth.jwt.JwtUtil;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.transport.http.AbstractHTTPDestination;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;


/**
 * 用于token身份令牌校验
 */
public class AuthInterceptor extends AbstractPhaseInterceptor<Message> {

    public AuthInterceptor() {
        super(Phase.PRE_INVOKE);
    }

    @Override
    public void handleMessage(Message message) throws Fault {
        List content = message.getContent(List.class);
        String o = (String) content.get(0);
        Map map = JSON.parseObject(o, Map.class);
        String ruleName = (String) map.get("RuleName");
        if("TokenHandler".equals(ruleName)){
            return;
        }
        String token = (String) map.get("Auth");
        //token合法性校验
        JwtUtil.isExpiration(token);

		//解析token中的角色,进行资源使用的权限校验(自行实现。。。)
    }
    @Override
    public void handleFault(Message message) {
        Exception ex = message.getContent(Exception.class);
        HttpServletResponse resp = (HttpServletResponse)message.getExchange()
                .getInMessage().get(AbstractHTTPDestination.HTTP_RESPONSE);
        resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        resp.setContentType("text/plain");
        try {
            resp.getOutputStream().write(ex.getMessage().getBytes());
            resp.getOutputStream().flush();
            message.getInterceptorChain().setFaultObserver(null); //avoid return soap fault
            message.getInterceptorChain().abort();
        } catch (IOException e) {
            // TODO
        }
    }
}

好啦~,springboot整合cxf-rt-transports-http-jetty发布jax-rs服务重点内容就讲完了(其实我感觉这个jax-rs和springmvc很像),其他的代码,我将贴在下面。

四、其他程序

1、JwtToken的工具类

package com.syx.auth.jwt;
import com.syx.entity.UserVo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.List;

public class JwtUtil {

    // 主题
    private static final String SUBJECT = "zx";

    // jwt的token有效期,
    private static final long EXPIRITION = 1000L * 60 * 60 * 24 * 7;//7天
//    private static final long EXPIRITION = 1000L * 60 * 30;   // 半小时

    // 加密key(黑客没有该值无法篡改token内容)
    private static final String APPSECRET_KEY = "zxdc";

    // 用户url权限列表key
    private static final String AUTH_CLAIMS = "auth";


    /**
     * TODO  生成token
     *
     * @param user
     * @return java.lang.String
     * @date 2020/7/6 0006 9:26
     */
    public static String generateToken(UserVo user) {
        String token = Jwts
                .builder()
                // 主题
                .setSubject(SUBJECT)
                // 添加jwt自定义值
                .claim(AUTH_CLAIMS, user.getRole())
                .claim("username", user.getUserName())
                .claim("userId", user.getUid())
                .setIssuedAt(new Date())
                // 过期时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
                // 加密方式,加密key
                .signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();
        return token;
    }

    /**
     * 获取用户Id
     *
     * @param token
     * @return
     */
    public static String getUserId(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("userId").toString();
    }

    /**
     * 获取用户名
     *
     * @param token
     * @return
     */
    public static String getUsername(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("username").toString();
    }

    /**
     * 获取用户角色列表, 没有返回空
     *
     * @param token
     * @return
     */
    public static List<String> getUserAuth(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return  (List<String>) claims.get(AUTH_CLAIMS);
    }

    /**
     * 验证是否过期
     *
     * @param token
     * @return
     */
    public static boolean isExpiration(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        System.out.println("过期时间: " + claims.getExpiration());
        return claims.getExpiration().before(new Date());
    }
}

2、DataSourceConfig数据源配置类

package com.syx.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.sql.SQLException;

@Configuration
@ConditionalOnClass(DataSource.class)
@EnableTransactionManagement
public class DataSourceConfig {




    @Bean
    @Primary
    public DataSource ds() throws SQLException {
        DruidDataSource ds = new DruidDataSource();
        ds.setUrl("jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true");
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUsername("root");
        ds.setPassword("whh123");
        ds.setInitialSize(3);
        ds.setMinIdle(3);
        ds.setMaxActive(5);
        ds.setTestWhileIdle(true);
        ds.setValidationQuery("select 1");
        ds.init();
        return ds;
    }

    @Bean
    @Primary
    public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(ds);
        //配置mybatis的mapper.xml的路径,由于我们使用的是mybatis注解,所以注掉了
//        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResource("classpath:database/**/*.xml"));
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setUseGeneratedKeys(true);
        configuration.setCacheEnabled(true);
        configuration.setLazyLoadingEnabled(false);
        configuration.setLogPrefix("dao.");
        //添加分页插件
        configuration.addInterceptor(new PageInterceptor());
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        bean.setConfiguration(configuration);
        return bean.getObject();
    }
}

3、CompanyDao

package com.syx.dao;

import com.syx.entity.Company;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface CompanyDao {
    @Select("select * from company")
    List<Company> findAll();
}

4、业务处理的handler,我都粘贴到一起了,大家自行拆开

package com.syx.endpoint.handler;

import com.syx.response.CommonResponse;

import java.util.Map;

public interface Handler {
     CommonResponse service(Map<String,Object> args);
}





//-----------------------------------------------------------

package com.syx.endpoint.handler;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.syx.dao.CompanyDao;
import com.syx.entity.Company;
import com.syx.response.CommonResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

@Component("CompanyHandler")
public class CompanyHandler implements Handler {
    @Autowired
    private CompanyDao companyDao;
    @Override
    public CommonResponse service(Map<String,Object> args) {
        PageHelper.startPage(1,2);
        List<Company> all = companyDao.findAll();
        PageInfo<Company> pageInfo = new PageInfo<>(all);
        return CommonResponse.success(pageInfo,null);
    }
}

//-----------------------------------------------------------
package com.syx.endpoint.handler;
import com.syx.auth.jwt.JwtUtil;
import com.syx.entity.UserVo;
import com.syx.response.CommonResponse;
import com.syx.response.CommonResponseCode;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@Component("TokenHandler")
public class TokenHandler implements Handler {
    Map<Integer, UserVo> userMap  = new HashMap<Integer, UserVo>(){
        {
            UserVo u1 = new UserVo();
            u1.setUid(1);
            u1.setRole(Arrays.asList("1","2"));
            u1.setUserName("张三");
            put(u1.getUid(),u1);
        }
    };

    /**
     * {
     *   "RuleName":"TokenHandler",
     *   "Auth": "",
     *   "RequestParam": {
     *        "UserId":"123",
     *        "Username":"张三"
     *   }
     * }
     * @param args
     * @return
     */
    @Override
    public CommonResponse service(Map<String, Object> args) {

        int userId = Integer.parseInt((String) args.get("UserId"));

        String userName = (String) args.get("Username");

        //数据库查询是否有该用户,如果有,生成token,如果没有返回无权访问
        UserVo userVo = userMap.get(userId);
        if(userVo == null){
            return CommonResponse.fail(CommonResponseCode.RC400,"用户"+userId+"不存在");
        }
        return CommonResponse.success( JwtUtil.generateToken(userVo),null);
    }
}

5、实体类

package com.syx.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class Company {
    private int id;
    private String name;
    private int age;
    private String address;
    private double salary;
}


//--------------------------------------------

package com.syx.entity;

import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Setter
@Getter
public class UserVo {

    /**
     * 账户id
     */
    private int uid;

    /**
     * 姓名
     */
    private String userName;

    /**
     * 密码
     */
    private String password;

    /**
     * 是否可用 1可用 0不可用
     */
    private Integer lock;


    /**
     * 角色列表
     */
    private List<String> role;


}

6、统一响应的包装类

package com.syx.response;


import com.alibaba.fastjson.JSON;
import org.springframework.util.StringUtils;

public class CommonResponse<T> {

    private int status;
    private String msg;
    private T data;
    private long timeStamp;

    private CommonResponse() {
        this.timeStamp = System.currentTimeMillis();
    }


    /**
     * 成功
     * @param data
     * @param msg
     * @param <T>
     * @return
     */
    public static <T> CommonResponse success(T data,String msg){
        CommonResponse response = new CommonResponse<>();

        response.status = CommonResponseCode.RC200.getCode();
        if(StringUtils.isEmpty(msg)){
            response.msg = CommonResponseCode.RC200.getMsg();
        }else{
            response.msg = msg;
        }
        response.data = data;
        return response;
    }




    public static <T> CommonResponse<T> fail(CommonResponseCode code,String msg){
        CommonResponse response = new CommonResponse();
        response.status = code.getCode();
        if(StringUtils.isEmpty(msg)){
            response.msg = code.getMsg();
        }else{
            response.msg = msg;
        }
        return response;
    }


    /**
     * 转换成json
     * @return
     */
    public String toJsonString(){
        return JSON.toJSONString(this);
    }
    public int getStatus() {
        return status;
    }

    public String getMsg() {
        return msg;
    }

    public T getData() {
        return data;
    }

    public long getTimeStamp() {
        return timeStamp;
    }
}

//----------------------------------------

package com.syx.response;

public enum CommonResponseCode {

    RC200(200,"SUCCESS"),
    RC400(400,"FAIL"),
    RC500(500,"Internal ERROR")
    ;

    private final int code;
    private final String msg;
    CommonResponseCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

7、springboot的启动类

package com.syx;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.syx.**.dao")
public class MainApp {
    public static void main(String[] args) {
        SpringApplication.run(MainApp.class);
    }
}
 类似资料: