Http服務(wù)與Dubbo服務(wù)相互轉(zhuǎn)換的SpringBoot代理節(jié)點實現(xiàn)方式
1.需求
目前有些項目已經(jīng)接入了Spring cloud管理,節(jié)點間通信(包括老項目)通過eureka(非boot web項目的注eureka注冊與發(fā)現(xiàn)參照前文)提供http通信,由于我們公司內(nèi)部項目間交流要求通過dubbo做服務(wù)的暴露與消費,考慮新加一個boot節(jié)點用于http與dubbo之間的相互轉(zhuǎn)換
2.主要思想,方案與問題
主要思想
<1>做一個Spring Boot節(jié)點用于http與dubbo服務(wù)的代理
<2>Http調(diào)用Dubbo:
將節(jié)點接入Spring Cloud管理,注冊到eureka上提供SC方面的服務(wù),節(jié)點依賴需要接入的項目jar包,并配置掃描等將Dubbo代理Bean交由Spring Bean管理,通過通用的Controller提供http服務(wù)(通用controller后面會說)
<3>Dubbo調(diào)用Http:
這個相對簡單,只需要對dubbo暴露一個通用接口,調(diào)用方在調(diào)用的時候指定要調(diào)用的鏈接,入?yún)⒌葏?shù),做一層轉(zhuǎn)發(fā)就可以
方案與問題
<1> 依賴io.dubbo.springboot:spring-boot-starter-dubbo:1.0.0這個jar中提供了很多接入SC的配置,但在開發(fā)完成后發(fā)現(xiàn)一個致命問題,就是好像不支持一個消費者配置多個生產(chǎn)者,查看源碼也沒有找到很好的解決方案(個人水平有限)…此方案相對簡單,如果只針對一個生產(chǎn)者,可以考慮此方案
<2> 消費者配置生產(chǎn)者仍然采用原先的xml配置,項目依賴也只是原始的dubbo依賴,其余手動配置(通過@Value從配置中心拿)
3.核心代碼
扯了那么多沒用的,終于輪到代碼了(這里只公開了核心代碼)…
啟動類:HttpDubboProxyApplication:
/**
* Created by Kowalski on 2017/7/11
* Updated by Kowalski on 2017/7/11
*/
@SpringBootApplication
@EnableEurekaClient
@ImportResource("classpath:dubbo-consumer.xml")
public class HttpDubboProxyApplication {
public static void main(String... args) {
// 程序啟動入口
SpringApplication.run(HttpDubboProxyApplication.class,args);
}
}項目啟動時一些基本配置Bean(類xml)ConfigurationBean:
/**
* Created by Kowalski on 2017/7/17
* Updated by Kowalski on 2017/7/17
* 配置bean
*/
@Configuration
public class ConfigurationBean {
@Bean
ProxySpringContextsUtil proxySpringContextsUtil(){
return new ProxySpringContextsUtil();
}
@Bean
AnnotationBean annotationBean(){
AnnotationBean annotationBean = new AnnotationBean();
/**啟動掃描包(與正常dubbo掃描類似)*/
annotationBean.setPackage("com.kowalski.proxy.service");
return annotationBean;
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
/**設(shè)置傳輸格式 避免中文亂碼*/
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
}我們這里http請求通過restTemplate,也可以使用feign
上段代碼的proxySpringContextsUtil:
package com.kowalski.proxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* Created by Kowalski on 2017/5/18
* Updated by Kowalski on 2017/5/18
*/
public class ProxySpringContextsUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext; //Spring應(yīng)用上下文環(huán)境
/**
* 實現(xiàn)ApplicationContextAware接口的回調(diào)方法,設(shè)置上下文環(huán)境
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
ProxySpringContextsUtil.applicationContext = applicationContext;
}
/**
* @return ApplicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 獲取對象
* @param name
* @return Object 一個以所給名字注冊的bean的實例
* @throws BeansException
*/
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
/**
* 獲取類型為requiredType的對象
* 如果bean不能被類型轉(zhuǎn)換,相應(yīng)的異常將會被拋出(BeanNotOfRequiredTypeException)
* @param name bean注冊名
* @param requiredType 返回對象類型
* @return Object 返回requiredType類型對象
* @throws BeansException
*/
public static Object getBean(String name, Class<?> requiredType) {
return applicationContext.getBean(name, requiredType);
}
/**
* 如果BeanFactory包含一個與所給名稱匹配的bean定義,則返回true
* @param name
* @return boolean
*/
public static boolean containsBean(String name) {
return applicationContext.containsBean(name);
}
/**
* 判斷以給定名字注冊的bean定義是一個singleton還是一個prototype。
* 如果與給定名字相應(yīng)的bean定義沒有被找到,將會拋出一個異常(NoSuchBeanDefinitionException)
* @param name
* @return boolean
* @throws NoSuchBeanDefinitionException
*/
public static boolean isSingleton(String name) {
return applicationContext.isSingleton(name);
}
/**
* @param name
* @return Class 注冊對象的類型
* @throws NoSuchBeanDefinitionException
*/
public static Class<?> getType(String name) {
return applicationContext.getType(name);
}
/**
* 如果給定的bean名字在bean定義中有別名,則返回這些別名
* @param name
* @return
* @throws NoSuchBeanDefinitionException
*/
public static String[] getAliases(String name) {
return applicationContext.getAliases(name);
}
}此工具類主要用于獲取Spring管理的Bean
http調(diào)用dubbo服務(wù)通用Controller DubboProxyController :
package com.kowalski.proxy;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.weimob.proxy.common.ProxyErrorResponse;
import com.weimob.proxy.common.ProxyErrorReturnEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Created by Kowalski on 2017/5/18
* Updated by Kowalski on 2017/5/18
*
* 使用規(guī)則:
* 1.被調(diào)用方提供的是單個對象類型入?yún)?
* 2.參數(shù)數(shù)量必須等于1(暫不支持無參與多參)
* 3.不支持泛型入?yún)?
*/
@Slf4j
@RestController
public class DubboProxyController {
public static final ObjectMapper objectMapper = new ObjectMapper();
static {
/**忽略unknow屬性*/
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
private final ConcurrentMap<String, Method> methods = new ConcurrentHashMap<>();
@RequestMapping("/proxy/{instanceName}/{methodName}")
public Object runMethod(@PathVariable String instanceName,
@PathVariable String methodName,
HttpServletRequest request) {
Object bean;
try {
/**從bean factory獲取bean實例*/
bean = ProxySpringContextsUtil.getBean(instanceName);
}catch (Exception e) {
log.error("未找到對應(yīng)實例, instanceName:{} e:{}", instanceName, e);
return new ProxyErrorResponse(ProxyErrorReturnEnum.NO_INSTANCE.getReturnCode(),
String.format("未找到對應(yīng)實例,instanceName:%s", instanceName));
}
/**從本地緩存拿取緩存方法*/
Method methodToDo = methods.get(instanceName + methodName);
/**如果緩存中沒有 則根據(jù)方法名從實例中拿*/
if (methodToDo == null) {
Method[] declaredMethods;
try {
declaredMethods = bean.getClass().getDeclaredMethods();
}catch (Exception e) {
log.error("獲取接口定義方法失敗, instanceName:{} methodName:{} e:{}", instanceName, methodName, e);
return new ProxyErrorResponse(ProxyErrorReturnEnum.ERROR_GET_DECLARED_METHODS.getReturnCode(),
String.format("獲取接口定義方法失敗,instanceName:%s methodName:%s", instanceName, methodName));
}
/**根據(jù)方法名拿方法*/
for (Method method : declaredMethods) {
if (methodName.equals(method.getName())) {
methodToDo = method;
methods.putIfAbsent(instanceName + methodName, methodToDo);
break;
}
}
}
if (methodToDo == null) {
return new ProxyErrorResponse(ProxyErrorReturnEnum.NO_METHOD.getReturnCode(),
String.format("未找到對應(yīng)方法,instanceName:%s methodName:%s", instanceName, methodName));
}
/**獲取參數(shù)類型*/
Type[] types = methodToDo.getParameterTypes();
/**暫不支持無參方法*/
if(types == null || types.length == 0) {
return new ProxyErrorResponse(ProxyErrorReturnEnum.NO_PARAM_TYPE.getReturnCode(),
String.format("未獲取到方法參數(shù), instanceName:%s methodName:%s", instanceName, methodName));
}
/**暫不支持參數(shù)數(shù)量大于1*/
if(types.length > 1) {
return new ProxyErrorResponse(ProxyErrorReturnEnum.TOO_MANY_PARAM_ARGS.getReturnCode(),
String.format("方法參數(shù)過多, instanceName:%s methodName:%s", instanceName, methodName));
}
/**根據(jù)request請求內(nèi)容 轉(zhuǎn)換成對應(yīng)形式的參數(shù)*/
ServletInputStream inputStream;
try {
inputStream = request.getInputStream();
}catch (Exception e){
log.error("獲取輸入流失敗, instanceName:{} methodName:{} e:{}", instanceName, methodName, e);
return new ProxyErrorResponse(ProxyErrorReturnEnum.GET_INPUT_STREAM_FAILED.getReturnCode(),
String.format("獲取輸入流失敗, instanceName:%s methodName:%s", instanceName, methodName));
}
/**獲取入?yún)㈩愋?/
TypeFactory tf = objectMapper.getTypeFactory();
JavaType javaType = tf.constructType(types[0]);
/**將輸入流轉(zhuǎn)化成對應(yīng)類型的參數(shù)*/
Object param;
try {
param = objectMapper.readValue(inputStream, javaType);
}catch (Exception e){
log.error("輸入流轉(zhuǎn)化入?yún)⑹? instanceName:{} methodName:{} e:{}", instanceName, methodName, e);
return new ProxyErrorResponse(ProxyErrorReturnEnum.INPUT_STREAM_EXCHANGE_FAILED.getReturnCode(),
String.format("輸入流轉(zhuǎn)化入?yún)⑹? instanceName:%s methodName:%s", instanceName, methodName));
}
/**執(zhí)行方法*/
Object result;
try {
result = methodToDo.invoke(bean, param);
}catch (Exception e){
log.error("方法執(zhí)行錯誤, instanceName:{} methodName:{} e:{}", instanceName, methodName, e);
return new ProxyErrorResponse(ProxyErrorReturnEnum.METHOD_INVOKE_ERROR.getReturnCode(),
String.format("方法執(zhí)行錯誤, instanceName:%s methodName:%s", instanceName, methodName));
}
/**成功返回*/
return result;
}
}由于已經(jīng)將dubbo的代理Bean交由Spring Bean管理,因此通過ProxySpringContextsUtil 拿到代理,通過方法名拿到方法,通過方法拿到入?yún)㈩愋?,再將入?yún)⑥D(zhuǎn)化成對應(yīng)類型的參數(shù)invoke(之前有考慮過不管參數(shù)類型直接交給dubbo代理類去invoke,但好像必須要制定類型的入?yún)ⅲ蝗粓箦e)
Dubbo調(diào)用Http的通用Service HttpProviderProxyService :
package com.kowalski.proxy.service;
/**
* Created by Kowalski on 2017/7/17
* Updated by Kowalski on 2017/7/17
* http代理實現(xiàn)類
*/
public interface HttpProviderProxyService {
/**
* 處理dubbo調(diào)用http代理請求
* @param request
* @return
*/
Object httpProxyHandle(HttpProxyRequest request);
}Service實現(xiàn) HttpProviderProxyServiceImpl:
package com.kowalski.proxy.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.kowalski.proxy.Enum.HttpProxyReqTypeEnum;
import com.kowalski.proxy.common.ProxyErrorResponse;
import com.kowalski.proxy.common.ProxyErrorReturnEnum;
import com.kowalski.proxy.service.HttpProviderProxyService;
import com.kowalski.proxy.service.HttpProxyRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.client.RestTemplate;
/**
* Created by Kowalski on 2017/7/17
* Updated by Kowalski on 2017/7/17
*/
@Service
@Slf4j
public class HttpProviderProxyServiceImpl implements HttpProviderProxyService{
@Value("${zuul.internal.url}")
String zuuInternalUrl;
@Autowired
RestTemplate restTemplate;
/**
* 處理dubbo調(diào)用http代理請求
* @param request
* @return
*/
@Override
public Object httpProxyHandle(HttpProxyRequest request) {
/**入?yún)⑿r?/
if(request == null){
return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_REQUEST_NULL);
}
if(request.getReqType() == null){
return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_REQUEST_TYPE_NULL);
}
if(StringUtils.isEmpty(request.getProxyUrl())){
return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_URL_NULL);
}
if(request.getRequest() == null){
return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_REQUEST_NULL);
}
/**根據(jù)不同入?yún)㈩愋吞幚聿煌埱?/
switch (HttpProxyReqTypeEnum.getEnumByCode(request.getReqType())){
case INTERNAL_ZUUL_COMMON:
return internalZuulProxy(request);
case INTERNAL_ZUUL_CUSTOMIZED:
return internalZuulProxy(request);
case OUTSIDE_FULL_URL:
return outsideFullProxy(request);
default:
return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_REQUEST_TYPE_UNDEFIND);
}
}
/**處理經(jīng)由內(nèi)網(wǎng)網(wǎng)關(guān)的代理請求*/
private Object internalZuulProxy(HttpProxyRequest request){
String url = zuuInternalUrl + request.getProxyUrl();
Object result;
try {
result = restTemplate.postForObject(url, request.getRequest(), Object.class);
}catch (Exception e){
log.error("HttpProviderProxyServiceImpl internalZuulProxy failed: reqType:{},url:{}, e:{}",
request.getReqType(), url, e);
return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_GETRETURN_FAILED);
}
return result;
}
/**處理全路徑的代理請求*/
private Object outsideFullProxy(HttpProxyRequest request){
Object result;
try {
result = restTemplate.postForObject(request.getProxyUrl(), request.getRequest(), Object.class);
}catch (Exception e){
log.error("HttpProviderProxyServiceImpl internalZuulProxy failed: reqType:{},proxyUrl:{}, e:{}",
request.getReqType(), request.getProxyUrl(), e);
return new ProxyErrorResponse(ProxyErrorReturnEnum.PROXY_GETRETURN_FAILED);
}
return result;
}
}這里要注意一下這里的@Service注解,該注解采用的是dubbo的@Service注解而不是Spring的
通用請求 HttpProxyRequest :
package com.kowalski.proxy.service;
import lombok.Data;
/**
* Created by Kowalski on 2017/7/17
* Updated by Kowalski on 2017/7/17
* 代理Http
* 備注:只接受單個非泛型對象入?yún)?
*/
@Data
public class HttpProxyRequest {
/**請求類型 @see HttpProxyReqTypeEnum*/
private Integer reqType;
/**reqType->0:根據(jù)內(nèi)網(wǎng)網(wǎng)關(guān)地址請求定制controller requestMapping地址
* reqType->1:根據(jù)內(nèi)網(wǎng)網(wǎng)關(guān)地址請求commonController {serviceId}/{instanceName}/{methodName}
* reqType->2:請求proxyUrl地址*/
private String proxyUrl;
/**請求request*/
private Object request;
}配置文件:dubbo-consumer.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 消費方應(yīng)用名,用于計算依賴關(guān)系,不是匹配條件,不要與提供方一樣 -->
<dubbo:application name="proxy" />
<dubbo:consumer timeout="1800000" retries="0" />
<dubbo:registry protocol="zookeeper" address="${dubbo.consumer.zookeeper.AA.address}" id="A" />
<!--<dubbo:registry protocol="zookeeper" address="${dubbo.consumer.zookeeper.BB.address}" id="B" />-->
<dubbo:registry protocol="zookeeper" address="${dubbo.consumer.zookeeper.C.address}" id="C"/>
<dubbo:reference id="aaFacade" registry="A"
interface="com.kowalski.facade.AaFacade" check="false"/>
<!--<dubbo:reference id="bbFacade" interface="com.kowalski.facade.BbFacade"-->
<!--check="false" registry="B"/>-->
<dubbo:reference id="ccFacade" registry="C"
interface="com.kowalski.facade.CcFacade" check = "false"/>
</beans>這里的${dubbo.consumer.zookeeper.AA.address}上產(chǎn)者注冊到的zk地址可以直接在application.yml中配置
其余枚舉類等:
ProxyErrorResponse:
package com.kowalski.proxy.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* Created by Kowalski on 2017/7/4
* Updated by Kowalski on 2017/7/4
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProxyErrorResponse implements Serializable{
private static final long serialVersionUID = -8379940261456006476L;
private long code;
private long status;
private String message;
public ProxyErrorResponse(long codeOrStatus, String message) {
this.code = codeOrStatus;
this.status = codeOrStatus;
this.message = message;
}
public ProxyErrorResponse(ProxyErrorReturnEnum errorReturnEnum) {
this.code = errorReturnEnum.getReturnCode();
this.status = errorReturnEnum.getReturnCode();
this.message = errorReturnEnum.getDiscribe();
}
}ProxyErrorReturnEnum:
package com.kowalski.proxy.common;
/**
* Created by Kowalski on 2017/6/26
* Updated by Kowalski on 2017/6/26
*/
public enum ProxyErrorReturnEnum {
SUCCESS (0, "SUCCESS"),
METHOD_INVOKE_ERROR (-1, "方法執(zhí)行錯誤"),
NO_METHOD (-2, "未找到對應(yīng)方法"),
NO_INSTANCE (-3, "未找到對應(yīng)實例"),
ERROR_GET_DECLARED_METHODS (-4, "獲取接口定義方法失敗"),
NO_INSTANCE_BY_CLASS_NAME (-5, "根據(jù)全路徑獲取實例失敗"),
NO_PARAM_TYPE (-6, "未獲取到方法參數(shù)"),
TOO_MANY_PARAM_ARGS (-7, "方法參數(shù)過多"),
GET_INPUT_STREAM_FAILED (-8, "獲取輸入流失敗"),
INPUT_STREAM_EXCHANGE_FAILED(-9, "輸入流轉(zhuǎn)化入?yún)⑹?),
RETURN_JSON_TO_MY_FAILED (-10, "返回結(jié)果解析錯誤"),
SYSTEM_ERROR (-11, "系統(tǒng)錯誤"),
REQUEST_FAILED (-12, "請求失敗"),
/**http代理錯誤*/
PROXY_REQUEST_TYPE_NULL (-13, "代理http類型不能為空"),
PROXY_URL_NULL (-14, "代理地址不能為空"),
PROXY_REQUEST_ARGS_NULL (-15, "請求入?yún)榭?),
PROXY_REQUEST_TYPE_UNDEFIND (-16, "代理http類型非法"),
PROXY_GETRETURN_FAILED (-17, "請求失敗"),
PROXY_REQUEST_NULL (-18, "請求不能為空");
private long returnCode;
private String discribe;
ProxyErrorReturnEnum(int returnCode, String discribe) {
this.returnCode = returnCode;
this.discribe = discribe;
}
public long getReturnCode() {
return returnCode;
}
public void setReturnCode(long returnCode) {
this.returnCode = returnCode;
}
public String getDiscribe() {
return discribe;
}
public void setDiscribe(String discribe) {
this.discribe = discribe;
}
}HttpProxyReqTypeEnum :
package com.kowalski.proxy.Enum;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Kowalski on 2017/7/17
* Updated by Kowalski on 2017/7/17
* 傳輸類型枚舉類
*/
public enum HttpProxyReqTypeEnum {
/**請求走內(nèi)網(wǎng)網(wǎng)關(guān) 不經(jīng)由外網(wǎng)復(fù)雜驗證 定制controller處理*/
INTERNAL_ZUUL_CUSTOMIZED(0, "內(nèi)網(wǎng)網(wǎng)關(guān)定制"),
/**請求走內(nèi)網(wǎng)網(wǎng)關(guān) 不經(jīng)由外網(wǎng)復(fù)雜驗證 通用controller處理(instanceName methodName request)*/
INTERNAL_ZUUL_COMMON(1, "內(nèi)網(wǎng)網(wǎng)關(guān)通用"),
/**全路徑處理 不走網(wǎng)關(guān)(或者直接配置網(wǎng)關(guān)全路徑)*/
OUTSIDE_FULL_URL(2, "全路徑");
private int code;
private String description;
HttpProxyReqTypeEnum(int code, String description) {
this.code = code;
this.description = description;
}
private static final Map<Integer, HttpProxyReqTypeEnum> map = new HashMap<Integer, HttpProxyReqTypeEnum>();
static {
for (HttpProxyReqTypeEnum enums : HttpProxyReqTypeEnum.values()) {
map.put(enums.getCode(), enums);
}
}
public static HttpProxyReqTypeEnum getEnumByCode(int code){
return map.get(code);
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}至此結(jié)束~
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- 利用SpringBoot實現(xiàn)一個基于本地代理模式的RPC調(diào)用框架
- SpringBoot利用RestTemplate實現(xiàn)反向代理
- 基于SpringBoot實現(xiàn)一個JAVA代理HTTP/ WS的方法
- SpringBoot利用Undertow實現(xiàn)高可用的反向代理配置
- SpringBoot設(shè)置HTTP代理訪問
- SpringBoot中5種動態(tài)代理的實現(xiàn)方案
- SpringBoot項目中JDK動態(tài)代理和CGLIB動態(tài)代理的使用詳解
- SpringBoot利用Junit動態(tài)代理實現(xiàn)Mock方法
相關(guān)文章
Spring Boot 項目啟動自動執(zhí)行方法的兩種實現(xiàn)方式
這篇文章主要介紹了Spring Boot 項目啟動自動執(zhí)行方法的兩種實現(xiàn)方式,幫助大家更好的理解和學(xué)習(xí)使用Spring Boot框架,感興趣的朋友可以了解下2021-05-05
Java實現(xiàn)數(shù)據(jù)庫連接池的方法
這篇文章主要介紹了Java實現(xiàn)數(shù)據(jù)庫連接池的方法,涉及java數(shù)據(jù)庫連接池的創(chuàng)建、連接、刷新、關(guān)閉及狀態(tài)獲取的常用技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07

