(三)传统Java Web项目(非Spring Boot项目、老版本项目)接入Spring Cloud环境方案
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了(三)传统Java Web项目(非Spring Boot项目、老版本项目)接入Spring Cloud环境方案,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含29636字,纯文字阅读大概需要43分钟。
内容图文
![(三)传统Java Web项目(非Spring Boot项目、老版本项目)接入Spring Cloud环境方案](/upload/InfoBanner/zyjiaocheng/772/398e6a2b82f0444ca8c01ee5bac4b554.jpg)
版权声明:本文为博主原创文章,版权归烟台华东数据科技有限公司与博主本人共同所有。烟台华东数据科技有限公司及其下属机构毋须博主授权即可在任意用途下转载、使用!
目录
前言
也许因为很多原因,也许是公司框架升级,或者业务升级,开始了Spring Cloud架构的设计和搭建,但是会出现一些问题,一些传统的Java Web项目也需要同时接入Spring Cloud环境中使用微服务。最简单粗暴的方案有以下两种:
- 把这个Java Web项目直接迁移到Spring Cloud项目下,使用Mavan管理jar包,添加Spring Cloud相关引用和配置
- 非Spring Cloud工程直接引入eureka、ribbon、feign依赖
- 使用网关访问微服务,仅限有配置网关的服务,而且针对网关还得自行写各个微服务的HTTP访问代码
如果以上两种方案可以解决问题,那么本篇博文后面的内容就可以不用看了。
我根据实际开发中的情况,我初步总结会遇到的问题如下:
- Spring不兼容,很多老项目的Spring版本甚至还是2.0版
- jersey不兼容,如有一些模块使用的还是jersey.1.10,这是2011年的版本了
JDK版本问题,JDK版本的问题反而不大,现在Spring Cloud环境最低版本要求使用JDK8,而很多老项目之前是没有怎么使用到老版本JDK特性的,可能会用到的是sun包里面的一些内容,但是新版的JDK都有替代,因此JDK升级到8版本的风险还是比较小的。
因此第一步,请先升级传统项目至JDK8!
另:如果是想把传统Java Web项目整入Spring Cloud提供微服务,那表示传统老项目是具有微服务潜力的,那就不如直接把代码新迁移到Spring Cloud的微服务模块中,因为核心代码还是通用的,只是之前可能是通过原始的Servlet或者jersey提供服务,可以直接修改为SpringMVC提供服务,风险也可控。就不要另外折腾把老代码改造成微服务模块了,有这时间微服务都开发出来了。
解决方案思路
初期在考虑解决方案时,参考了以下博文:
传统Java Web(非Spring Boot)、非Java语言项目接入Spring Cloud方案
刚开始我主要是参照第二篇博文的方案进行了相应的开发和试用,基本是可以满足要求的,但是还是存在一定的问题,首先代码里面方法过时的就不提了,一个最大的缺陷就是,要求传统的Java Web项目一定需要引用如下两个jar包:
<dependency> <groupId>com.netflix.eureka</groupId> <artifactId>eureka-client</artifactId> <version>1.4.12</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-core</artifactId> <version>1.1.7.RELEASE</version> </dependency>
因为那篇博主还是使用的eureka提供的方法来进行获注册和获取微服务地址。但是就会出现前面提到的,这两个引用里面可能会有一些jar包会与老项目冲突,造成老项目的功能不能正常使用。因为我这边进行了相应的修改和优化,具体解决思路如下:
- eureka提供此端点eureka/apps可以获取在eureka中注册的所有微服务和相关信息,因此我们可以自行实现微服务的获取功能,也不需要额外注册到eureka上面。
- 然后使用工厂模式和代理模式,使老项目可以直接调用微服务对外暴露的接口,就像在项目内部调用一样,减少后续的开发和维护工作量,Spring Cloud中的模式也不需要任何改变。
其实就是我们需要实现一个桥梁,用这个桥梁可以让传统的Java Web项目正常调用微服务的接口,从而得到服务。
具体实现
Spring Cloud基于之前两篇博文的设计框架和内容,我们使用Redis模块进行演示。
需要实现的功能模块就这样:
其中被我废弃的HddEurekaClient就是老版的使用eureka自己的方法获取注册的微服务信息。此文不展示。
整个模块使用Spring Boot搭建,从而可以打成jar包,然后给传统Java Web项目使用。
pom文件
<artifactId>HDD_SHJJHY_CLOUD</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-openfeign-core</artifactId> </dependency> <dependency> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty</artifactId> <version>6.1.26</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.51</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Finchley.SR4</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Eureka提供的apps信息
首先我们可以访问本地的eureka提供的端点信息,使用浏览器默认返回xml格式如下:
不过在请求访问的时候,把请求的结果设置为JSON格式,则会返回JSON格式,为了简便解析,我这里使用的是JSON格式。
实体类
首先根据eureka 返回的信息,设计实体类如下:
EurekaApplication(主要记录的是在eureka中注册的每一个微服务信息)
package net.huadong.cloud.entity;
import java.util.List;
/**
* @author yangzj
* @desc Eureka中的微服务实体信息
* @create 2019-06-17 11:34
**/
public class EurekaApplication {
private String name;
private List<EurekaApplicationInstance> instances;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<EurekaApplicationInstance> getInstances() {
return instances;
}
public void setInstances(List<EurekaApplicationInstance> instances) {
this.instances = instances;
}
}
每一个微服务可能都会对应多个不同端口的实例,而且关键的访问信息都是在每一个实例上面,因此再设计微服务的实例类:
EurekaApplicationInstance
package net.huadong.cloud.entity;
/**
* @author yangzj
* @desc Eureka中每个实例的不同多个服务的实体类
* @create 2019-06-17 11:36
**/
public class EurekaApplicationInstance {
private volatile String instanceId; // 实例ID
private volatile String hostName; // 实例主机名
private volatile String app; // 实例名定义
private volatile String ipAddr; // 实例所在IP
private volatile String status; // 实例状态,UP或DOWN
private volatile String overriddenstatus; // 未知
private volatile String port; // 实例端口
private volatile String securePort; // 实例加密端口
private volatile boolean securePortEnabled = false; // 实例是否开启加密
private volatile String countryId;
private volatile String appGroupName; // 集群名称,一般没用
private volatile String homePageUrl; // 实例访问url
private volatile String statusPageUrl; // 实例状态访问url
private volatile String healthCheckUrl; // 实例健康检查url
private volatile String vipAddress; // 实例的实际名(未大小写转换,基本没用)
public String getInstanceId() {
return instanceId;
}
public void setInstanceId(String instanceId) {
this.instanceId = instanceId;
}
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public String getApp() {
return app;
}
public void setApp(String app) {
this.app = app;
}
public String getIpAddr() {
return ipAddr;
}
public void setIpAddr(String ipAddr) {
this.ipAddr = ipAddr;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getOverriddenstatus() {
return overriddenstatus;
}
public void setOverriddenstatus(String overriddenstatus) {
this.overriddenstatus = overriddenstatus;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
public String getSecurePort() {
return securePort;
}
public void setSecurePort(String securePort) {
this.securePort = securePort;
}
public boolean isSecurePortEnabled() {
return securePortEnabled;
}
public void setSecurePortEnabled(boolean securePortEnabled) {
this.securePortEnabled = securePortEnabled;
}
public String getCountryId() {
return countryId;
}
public void setCountryId(String countryId) {
this.countryId = countryId;
}
public String getHomePageUrl() {
return homePageUrl;
}
public void setHomePageUrl(String homePageUrl) {
this.homePageUrl = homePageUrl;
}
public String getStatusPageUrl() {
return statusPageUrl;
}
public void setStatusPageUrl(String statusPageUrl) {
this.statusPageUrl = statusPageUrl;
}
public String getHealthCheckUrl() {
return healthCheckUrl;
}
public void setHealthCheckUrl(String healthCheckUrl) {
this.healthCheckUrl = healthCheckUrl;
}
public String getVipAddress() {
return vipAddress;
}
public void setVipAddress(String vipAddress) {
this.vipAddress = vipAddress;
}
public String getAppGroupName() {
return appGroupName;
}
public void setAppGroupName(String appGroupName) {
this.appGroupName = appGroupName;
}
}
这样就有了自定义的微服务实体类,方便后续的操作。
自定义异常类
EurekaClientNotExistsException
package net.huadong.exception;
/**
* Thrown when an application attempts to use {@code null} in a
* case where an Eureka Client is required. These include:
* <ul>
* <li>Calling the instance method of a {@code null} Eureka Client Service.
* <li>The eureka Client Service is not started.
* <li>The eureka Client Service is offline or stopped.
* </ul>
* <p>
* Applications should throw instances of this class to indicate
* other illegal uses of the {@code null} Eureka Client.
*
* {@code EurekaClientNotExistsException} objects may be constructed by the
* virtual machine as if {@linkplain Throwable#Throwable(String,
* Throwable, boolean, boolean) suppression were disabled and/or the
* stack trace was not writable}.
*
* 微服务未启动或不存在的异常
*
* @author yangzj
* @since JDK1.0
* @create 2019-01-18 13:57
*/
public class EurekaClientNotExistsException extends RuntimeException {
/**
* Constructs a {@code EurekaClientNotExistsException} with no detail message.
*/
public EurekaClientNotExistsException() {
super();
}
/**
* Constructs a {@code EurekaClientNotExistsException} with the specified
* detail message.
*
* @param s the detail message.
*/
public EurekaClientNotExistsException(String s) {
super(s);
}
}
关键的处理类
核心业务处理类
HddHttpEurekaClient
这个类主要就是从eureka中获取所有实例信息,或者刷新指定微服务的实例,然后发起REST的调用并根据微服务暴露的接口定义的返回值返回对应的类型的结果。
package net.huadong.cloud.eureka;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import net.huadong.cloud.entity.EurekaApplication;
import net.huadong.cloud.entity.EurekaApplicationInstance;
import net.huadong.exception.EurekaClientNotExistsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;
import utils.HttpUtil;
import utils.StringUtil;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.ReentrantLock;
/**
* 同EurekaServer建立连接
* 负责定时更新
* 负责获取指定的Service
* 外部不需要调用这个类
* 这个类是个单例
*
* @author yangzj
*/
public class HddHttpEurekaClient {
private static final Logger logger = LoggerFactory.getLogger(HddHttpEurekaClient.class);
private volatile static String EUREKA_SERVER_URL;
private volatile static Hashtable<String, EurekaApplication> applications = new Hashtable<>();
// 网络访问错误的service地址
private volatile static Hashtable<String, Long> eurekaError = new Hashtable<>();
// 保存service的上次刷新时间
private volatile static Hashtable<String, Long> eurekaRefresh = new Hashtable<>();
// 网络访问错误重试次数
private static final int NETWORK_RETRY_TIME = 2;
private static final int SERVICE_RETRY_TIME = 3;
// 网络访问错误重试间隔时间
private static final long RETRY_WAIT_MILLISECONDS = 1000;
// 刷新远程链接间隔时间
private static final long REFRESH_INSTANCE_SECONDS = 20;
private static final long REFRESH_ERROR_INSTANCE_SECONDS = 30;
private volatile static int FLAG = 0;
private static ReentrantLock lock = new ReentrantLock();
@Autowired
private RestTemplate restTemplate;
protected HddHttpEurekaClient() {
if (restTemplate == null) {
restTemplate = new RestTemplate();
}
init();
}
public Hashtable<String, EurekaApplication> getApplications() {
return this.applications;
}
public EurekaApplication getApplicatioin(String serviceName) {
return this.applications.get(serviceName);
}
protected void init() {
ResourceBundle resource = ResourceBundle.getBundle("eureka-client");
EUREKA_SERVER_URL = resource.getString("eureka.serviceUrl");
refreshApplication("");
}
/**
* 刷新eureka的实例
* @param serviceName
*/
private void refreshApplication(String serviceName) {
String url = EUREKA_SERVER_URL + "/apps";
if (!StringUtil.isEmpty(serviceName)) {
url += "/" + serviceName.toUpperCase();
String appsJson = HttpUtil.sendHttp(url,
"GET", null, "",
"UTF-8", "json");
analyseApplication(appsJson, serviceName);
} else {
String appsJson = HttpUtil.sendHttp(url,
"GET", null, "",
"UTF-8", "json");
analyseApplication(appsJson);
}
}
/**
* 解析Eureka服务传过来的json串进行解析
* @param appsJson
*/
private void analyseApplication(String appsJson) {
synchronized (HddHttpEurekaClient.class) {
if (FLAG == 1) {
return;
}
FLAG = 1;
lock.lock();
}
try {
JSONObject jsonObject = JSONObject.parseObject(appsJson);
JSONArray jsonArray = jsonObject.getJSONObject("applications").getJSONArray("application");
if (jsonArray != null && jsonArray.size() > 0) {
applications.clear();
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject application = jsonArray.getJSONObject(i);
String name = application.getString("name");
EurekaApplication eurekaApplication = analyseInstance(application);
applications.put(name, eurekaApplication);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e.getCause());
} finally {
FLAG = 0;
lock.unlock();
}
}
/**
* 解析Eureka服务传过来的json串进行解析
* @param appsJson
* @param serviceName
*/
private void analyseApplication(String appsJson, String serviceName) {
if (StringUtil.isEmpty(serviceName)) {
return;
}
lock.lock();
try {
JSONObject application = JSONObject.parseObject(appsJson).getJSONObject("application");
String name = application.getString("name");
EurekaApplication eurekaApplication = analyseInstance(application);
if (eurekaApplication.getInstances().size() == 0) {
logger.error("Application instance not exist。[" + name + "]");
} else {
refreshErrorEureka();
}
applications.put(name, eurekaApplication);
} catch (Exception e) {
logger.error(e.getMessage(), e.getCause());
logger.error("Target Application not exist。[" + serviceName + "]");
} finally {
eurekaRefresh.put(serviceName, System.currentTimeMillis());
lock.unlock();
}
}
/**
* 解析单个Service的相关属性
* @param application
* @return
*/
private EurekaApplication analyseInstance(JSONObject application) {
String name = application.getString("name");
EurekaApplication eurekaApplication = new EurekaApplication();
eurekaApplication.setName(name);
List<EurekaApplicationInstance> instances = new ArrayList<>();
JSONArray instanceArray = application.getJSONArray("instance");
for (int j = 0; j < instanceArray.size(); j++) {
JSONObject instance = instanceArray.getJSONObject(j);
if (!"UP".equals(instance.getString("status"))) {
continue;
}
EurekaApplicationInstance eurekaApplicationInstance =
new EurekaApplicationInstance();
eurekaApplicationInstance.setInstanceId(instance.getString("instanceId"));
eurekaApplicationInstance.setHostName(instance.getString("hostName"));
eurekaApplicationInstance.setApp(instance.getString("app"));
eurekaApplicationInstance.setIpAddr(instance.getString("ipAddr"));
eurekaApplicationInstance.setStatus(instance.getString("status"));
eurekaApplicationInstance.setOverriddenstatus(instance.getString("overriddenstatus"));
eurekaApplicationInstance.setPort(instance.getJSONObject("port").getString("$"));
eurekaApplicationInstance.setSecurePort(instance.getJSONObject("securePort").getString("$"));
eurekaApplicationInstance.setSecurePortEnabled("true".equals(instance.getJSONObject("securePort").getString("@enabled")));
eurekaApplicationInstance.setCountryId(instance.getString("countryId"));
eurekaApplicationInstance.setAppGroupName(instance.getString("appGroupName"));
eurekaApplicationInstance.setHomePageUrl(instance.getString("homePageUrl"));
eurekaApplicationInstance.setStatusPageUrl(instance.getString("statusPageUrl"));
eurekaApplicationInstance.setHealthCheckUrl(instance.getString("healthCheckUrl"));
eurekaApplicationInstance.setVipAddress(instance.getString("vipAddress"));
instances.add(eurekaApplicationInstance);
}
eurekaApplication.setInstances(instances);
return eurekaApplication;
}
/**
* 进行RestTemplate请求响应
* @param serviceName service实例名称
* @param url 需要访问的方法url
* @param method http请求模式
* @param entity rest请求的实体
* @param returnClaz 请求需要返回的对象类
* @param <T> 泛型
* @return
*/
public <T> T request(String serviceName, String url,
HttpMethod method, HttpEntity<?> entity, Class<T> returnClaz) {
int serviceCount = 0;
// 根据指定次数,如果获取的连接地址不可用,可以再次尝试获取新的地址,否则就返回null
while (serviceCount < SERVICE_RETRY_TIME) {
String reqUrl = chooseService(serviceName, url);
int count = 0;
// 允许多次循环尝试访问
while (count < NETWORK_RETRY_TIME) {
try {
ResponseEntity<T> returnBody = restTemplate.exchange(reqUrl + url, method, entity, returnClaz);
return returnBody.getBody();
} catch (ResourceAccessException rae) {
System.out.println(rae.getMessage());
if (rae.getMessage().toLowerCase().contains("connection refused") ||
rae.getMessage().toLowerCase().contains("connection timed out")) {
count++;
if (RETRY_WAIT_MILLISECONDS > 0) {
try {
Thread.sleep(RETRY_WAIT_MILLISECONDS);
} catch (Exception e) {
logger.error("request retry wait error。[" + e.getMessage() + "]", e);
}
}
}
} catch (Exception e) {
logger.error("request is error。[" + serviceName + "]", e);
return null;
}
}
// 如果多次访问失败,则添加到失败列表中
if (count >= NETWORK_RETRY_TIME) {
serviceCount++;
this.eurekaError.put(reqUrl, System.currentTimeMillis());
}
}
return null;
}
/**
* 获取详细的Service地址
* @param serviceName
* @return
*/
public String chooseService(String serviceName, String url) {
if (!applications.containsKey(serviceName) ||
!eurekaRefresh.containsKey(serviceName) ||
(System.currentTimeMillis() - eurekaRefresh.get(serviceName)
> REFRESH_INSTANCE_SECONDS * 1000)) {
refreshApplication(serviceName);
}
EurekaApplicationInstance instance = getFreeInstance(serviceName, url);
if (instance == null) {
eurekaError.clear();
refreshApplication(serviceName);
instance = getFreeInstance(serviceName, url);
}
// TODO 如果还是未空, 则可能未启动相应的微服务,给出特殊提醒
if (instance == null) {
throw new EurekaClientNotExistsException("The Eureka Client Serivce[" +
serviceName + "] is not exists.");
}
return instance.getHomePageUrl();
}
/**
* 随机获取可以使用的Service的连接对象
* @param serviceName
* @return
*/
private EurekaApplicationInstance randomGetInstanceInfo(String serviceName) {
EurekaApplication eurekaApplication = this.getApplicatioin(serviceName);
List<EurekaApplicationInstance> instanceInfos = eurekaApplication.getInstances();
// 如果只有一个Serivce端口,那么直接返回
if (instanceInfos.size() == 1) {
return instanceInfos.get(0);
}
EurekaApplicationInstance instance;
int count = 0;
// 可以循环100次
while (count < 100) {
if (instanceInfos == null || instanceInfos.size() == 0) {
break;
}
// 获取随机数,范围为0到连接句柄对象列表的大小
int randomNum = ThreadLocalRandom.current().nextInt(0, instanceInfos.size());
instance = instanceInfos.get(randomNum);
// 如果随机出来的连接句柄已经是错误对象,或者在isAll为false时,与DDL的连接句柄是同一个时,则从列表中移除
// 然后再次循环随机
if (isErrorInstanceInfo(instance)) {
instanceInfos.remove(randomNum);
} else {
return instance;
}
}
return null;
}
/**
* 判断是否已经是失效的连接句柄
* @param instance
* @return
*/
private boolean isErrorInstanceInfo(EurekaApplicationInstance instance) {
if (instance == null) {
return false;
}
return eurekaError.containsKey(instance.getHomePageUrl());
}
/**
* 根据设置来刷新错误的连接信息数据,以免部分数据恢复
*/
private void refreshErrorEureka() {
Iterator<String> keys = eurekaError.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
if (System.currentTimeMillis() - eurekaError.get(key)
> REFRESH_ERROR_INSTANCE_SECONDS * 1000) {
eurekaError.remove(key);
}
}
}
}
其中有两个工具类,HttpUtil和StringUtil;一个是自己写的实现普通的HTTP访问的方法,一个是一些字符串的操作类,比如判断是否非空或者字符串强转等。这两个工具类就交给各位自行实现了。
同时这里面使用的负载均衡策略仅仅是使用了随机模式,如果需要保证根据一个session使用同一个微服务的话,可以自行实现相应的算法。
工厂类,这个工厂类管理了当前应用需要调用或者已经调用的接口实例。
HddEurekaFactory
package net.huadong.cloud.eureka;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* 这个工厂类管理了当前应用需要调用或者已经调用的接口实例。
* 因为通过动态代理对服务进行了封装。
* 因此对于一个应用来讲,是需要对服务实例进行管理的,否则每次都会进行创建。
*
* @author yangzj
*/
public class HddEurekaFactory {
private static final Logger logger = LoggerFactory.getLogger(HddEurekaFactory.class);
/**
* 当前应用对应的服务Map
*/
public volatile Map<Class<?>, Object> serviceMap = new HashMap<>();
private static HddEurekaFactory factory;
private static ReentrantLock lock = new ReentrantLock();
private HddHttpEurekaClient client;
/**
* 这个是当前的启动入口
*/
private HddEurekaFactory() {
client = new HddHttpEurekaClient();
}
/**
* 单例模式创建对象,当然可以通过注解的形式进行创建
*
* @return
*/
public static HddEurekaFactory gi() {
if (factory == null) {
lock.lock();
try {
if (factory == null) {
factory = new HddEurekaFactory();
}
} catch (Exception e) {
logger.error(e.getLocalizedMessage(), e.getCause());
} finally {
lock.unlock();
}
}
return factory;
}
@SuppressWarnings("unchecked")
public <T> T createService(Class<T> serviceInterface) {
if (serviceMap.containsKey(serviceInterface)) {
logger.debug("Service存在" + serviceInterface);
return (T) serviceMap.get(serviceInterface);
} else {
logger.debug("Service不存在" + serviceInterface);
return add(serviceInterface);
}
}
/**
* 此处未做同步,因为创建多个实例会被覆盖,不会出现问题!
* 只会影响首次的创建效率
*
* @param serviceInterface
* @return
*/
private <T> T add(Class<T> serviceInterface) {
HddEurekaHandler handler = new HddEurekaHandler(client);
T t = handler.create(serviceInterface);
serviceMap.put(serviceInterface, t);
return t;
}
}
代理模式处理类,此类主要是解析微服务暴露的接口类,通过里面的Feign配置和参数配置,再配合上面的eureka的微服务获取类,最后生成需要访问的URL地址。
HddEurekaHandler
package net.huadong.cloud.eureka;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.net.URLEncoder;
public class HddEurekaHandler implements InvocationHandler {
private static final Logger logger = LoggerFactory.getLogger(HddEurekaHandler.class);
/**
* 代理的目标接口类
*/
private Class<?> target;
/**
* Eureka中定义的Service的名称
*/
private String serviceName;
private HddHttpEurekaClient client;
HddEurekaHandler(HddHttpEurekaClient client) {
this.client = client;
}
public <T> T create(Class<T> target) {
this.target = target;
FeignClient s = target.getAnnotation(FeignClient.class);
if (s != null) {
serviceName = s.value().toUpperCase();
if (serviceName.length() == 0) {
serviceName = s.name().toUpperCase();
}
if (serviceName.length() == 0) {
logger.error(target.getName() + ", 定义的 @FeignClient 中没有指定Service Name!");
return null;
}
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[]{target}, this);
} else {
logger.error(target.getName() + ",没有定义 @FeignClient!");
return null;
}
}
/**
* 函数的实现内容,可以使用用其他的方式实现,例如通信等。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
String[] values = requestMapping.value();
String value;
if (values.length > 0) {
value = values[0];
} else {
logger.error(target.getName() + "." + method.getName() +
",没有定义 @RequestMapping!或者@RequestMapping的值为空");
return null;
}
RequestMethod[] requestMethods = requestMapping.method();
HttpMethod httpMethod = confirmHttpMethod(requestMethods);
if (httpMethod == null) {
logger.error(target.getName() + "." + method.getName() +
",没有定义 @RequestMapping!或者@RequestMapping的Http请求类型为空");
return null;
}
Class<?> returnClaz = method.getReturnType();
if (args.length == 1 && args[0] instanceof MultiValueMap) {
HttpEntity<?> entity = new HttpEntity<MultiValueMap<String, Object>>(
(MultiValueMap<String, Object>) args[0]);
return client.request(serviceName, value, httpMethod, entity, returnClaz);
}
// 判断是否把参数作为RequestBody传输
Annotation[][] annotations = method.getParameterAnnotations();
if (annotations[0][0] instanceof RequestBody) {
if (args.length == 1) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<?> entity = new HttpEntity<>(args[0], headers);
return client.request(serviceName, value, httpMethod, entity, returnClaz);
} else {
logger.error(target.getName() + "." + method.getName() +
",定义了错误的参数注解!");
return null;
}
}
// 判断参数为
String body = parseRequestParam(method, args);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity entity = new HttpEntity(body, headers);
return client.request(serviceName, value, httpMethod, entity, returnClaz);
}
/**
*
* @param requestMethods
* @return
*/
protected HttpMethod confirmHttpMethod(RequestMethod[] requestMethods) {
for (int i = 0; i < requestMethods.length; i++) {
if (RequestMethod.POST == requestMethods[i]) {
return HttpMethod.POST;
} else if (RequestMethod.GET == requestMethods[i]) {
return HttpMethod.GET;
}
}
return null;
}
/**
* 根据方法传入的参数,通过获取注解的方式,组装参数列表数据
* @param method
* @param args
* @return
*/
protected String parseRequestParam(Method method, Object[] args) {
StringBuilder builder = new StringBuilder();
Annotation[][] annotations = method.getParameterAnnotations();
int j = 0;
for (int i = 0; i < annotations.length; i++) {
Annotation annotation = annotations[i][0];
if (annotation instanceof RequestParam) {
RequestParam param = (RequestParam) annotation;
if (j > 0) {
builder.append("&");
}
builder.append(param.value());
builder.append("=");
if (args[i] instanceof String) {
try {
builder.append(URLEncoder.encode((String) args[i], "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
builder.append(args[i]);
}
j++;
}
}
return builder.toString();
}
}
具体的业务逻辑就不介绍了,感兴趣的可以自行查看。
配置文件
eureka-client.properties
eureka.serviceUrl=http://localhost:8101/eureka
上面此配置文件为默认的eureka地址,如果使用的eureka地址不同,那么在导入传统的Java Web项目时,在resource中配置此文件,修改为正确的eureka地址即可。
导入传统Java Web程序
把上面开发的Spring Boot小项目,打成jar包,然后导入传统的Java Web项目。如:
引入传统Java Web项目(目前我的做法是把这个jar包上传到公司内部的maven环境中,然后使用maven引用)
<!-- Spring Cloud 微服务接口引用 --> <dependency> <groupId>net.huadong.hdd.shjjhy</groupId> <artifactId>shjjhy.cloud</artifactId> <version>2.1</version> </dependency>
同时由于在这个里面使用到了Feign注解的解析,因此也需要引入Feign的jar包如下:
<!-- Spring Cloud Feign 引用 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-openfeign-core</artifactId> <version>2.0.4.RELEASE</version> </dependency>
注意:如果部分老项目中有jar包与这个Feign包冲突,那么请直接在这个引用 中过滤掉,因为我们需要的仅仅是Feign的注解类。
然后再引入需要调用的微服务对外暴露的接口jar包:
<dependency> <groupId>net.huadong.hdd.redis</groupId> <artifactId>redis.service.interface</artifactId> <version>2.2</version> </dependency>
此处不明白的可以参考前面两篇博文。
RedisService示例如下:
package net.huadong.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@FeignClient(name = "redisService", fallbackFactory = RedisServiceFallback.class)
@Component
public interface RedisService {
/**
* 根据传入的map(key,value)值设置redis
*
* @param map
* @return
*/
@RequestMapping(value = "/Redis/setRedis", method = RequestMethod.POST)
void set(@RequestBody Map<String, Object> map);
/**
* 传入key获取对应的redis 值
*
* @return obj
*/
@RequestMapping(value = "/Redis/getRedis", method = RequestMethod.POST)
@ResponseBody
Object get(@RequestParam("key") String key);
/**
* 传入key获取对应的redis 值
*
* @return obj
*/
@RequestMapping(value = "/Redis/getRedisString", method = RequestMethod.POST)
@ResponseBody
String getString(@RequestParam("key") String key);
}
演示示例
在传统的Java Web项目里面,使用main方法演示,演示类如下:
RedisService相关接口请参考前面两篇博文
public static void main(String[] args) throws Exception {
RedisService redisService = HddEurekaFactory.gi().createService(RedisService.class);
String result = redisService.getString("SH_EIR_REFRESH_TOKEN");
System.out.println(result);
}
使用了工厂类生成了代理类,然后就可以像在本地使用接口一样直接调用对应的方法,而不需要开发人员去另外关心HTTP访问等相关细节了。
如果对此原理还有什么不能理解,可以自行debug理解。
总结
最小代价实现了传统Java Web项目调用Spring Cloud环境中的微服务
让传统的Java Web项目可以像使用本地的接口服务一样,而不需要开发人员额外考虑底层的HTTP访问等细节问题了。
缺点:
没有另外实现其他负载均衡的算法,其实要实现也很简单,可以在接口的Feign信息或者Request信息中设定相应的算法模式;
每次30秒会去刷新服务器的实例,此处可能会造成传统的Java Web项目大量访问eureka中心,可以考虑修改为消息触发的方式去刷新对应的实例
下一章,在Spring Cloud中集成Spring Cloud Gateway的网关模块
内容总结
以上是互联网集市为您收集整理的(三)传统Java Web项目(非Spring Boot项目、老版本项目)接入Spring Cloud环境方案全部内容,希望文章能够帮你解决(三)传统Java Web项目(非Spring Boot项目、老版本项目)接入Spring Cloud环境方案所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。