大家都知道在非web环境下spring的作用域有两种 singleton prototype
singleton 单例 在容器中只创建一个bean对象
prototype 容器中存在多个Bean对象,每次访问容器获取对象,都会生成一个新对象,即每次调用getBean都会获取一个新的对象
prototype 容易被误认为 只要设定scope为模式,每次请求都是不同的对象,其实不是,还应该有一个前提,每次请求都有从容器中去取对象了
笔者有一个需求,访问一个接口(直接访问http方式),测试环境是http 生产环境为https,
笔者想到使用profile根据目前配置的环境动态选择创建对象
@Configuration
public class HttpClientConfig {
@Bean(name="httpClient")
@Profile("dev")
public CloseableHttpClient buildHTTPClient(){
CloseableHttpClient httpclient = HttpClients.createDefault();
return httpclient;
}
@Bean(name="httpClient")
@Profile("pro")
public CloseableHttpClient buildHTTPSClient(){
// Trust own CA and all self-signed certs
SSLContext sslcontext = null;
CloseableHttpClient httpclient = null;
try {
sslcontext = createIgnoreVerifySSL();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return httpclient;
}
然后想到我们这个连接每次完成请求后都要关闭,下次再过来连接请求,需要重新创建个httpClient对象,于是加上了@Scope,定义作用域为protoType,想实现每次请求过来都创建新httpClient的需求
@Configuration
public class HttpClientConfig {
@Bean(name="httpClient")
@Profile("dev")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public CloseableHttpClient buildHTTPClient(){
CloseableHttpClient httpclient = HttpClients.createDefault();
return httpclient;
}
@Bean(name="httpClient")
@Profile("pro")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public CloseableHttpClient buildHTTPSClient(){
// Trust own CA and all self-signed certs
SSLContext sslcontext = null;
CloseableHttpClient httpclient = null;
try {
sslcontext = createIgnoreVerifySSL();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return httpclient;
}
然后就开始使用,开始了引用
@Component
public class HttpClientService {
protected static final Logger logger = LoggerFactory.getLogger(HttpClientService.class);
@Autowired
CloseableHttpClient httpClient ;
public String post(Map params , String url){
String str = "";
try {
HttpPost httppost = new HttpPost(url);
//设置超时10s
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();//设置请求和传输超时时间
httppost.setConfig(requestConfig);
List<NameValuePair> list = new ArrayList<NameValuePair>();
Iterator<?> iterator = params.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> elem = (Map.Entry<String, String>) iterator.next();
list.add(new BasicNameValuePair(elem.getKey(), elem.getValue()));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httppost.setEntity(entity);
CloseableHttpResponse response;
try {
response = httpClient.execute(httppost);
HttpEntity httpentity = response.getEntity();
if(httpentity != null) {
str = EntityUtils.toString(httpentity, "UTF-8");
}
} catch (ClientProtocolException e) {
logger.error("httpclient请求失败",e);
} catch (IOException e) {
logger.error("httpclient请求超时",e);
return null;
} finally {
try {
httpClient.close();
} catch (IOException e) {
logger.error("httpclient连接关闭失败",e);
}
}
} catch (UnsupportedEncodingException e) {
logger.error("httpclient获取结果失败,编码异常",e);
}
return str;
}
然后开始了测试
public class HttpsReqTest {
@Test
public void testd(){
System.setProperty("spring.profiles.active","pro");
//初始化spring容器
SpringContainer.getInstance().init();
HttpClientService httpClientService = (HttpClientService) SpringContainer.springContainer.context.getBean("httpClientService");
String result = httpClientService.post(new HashMap<>(),"https://XXXXXXXXXXXXXXXXXXXXXX");
String result2 = httpClientService.post(new HashMap<>(),"https://XXXXXXXXXXXXXXXXXX");
}
}
结果发现获取result2发出的请求总是报错,连接中断。 connect pool shut down ,分析原因应该是两次访问httpClient,spring 注入了相同的一个对象
然后发现httpClient的引用者HttpClientService是单例的,在容器启动的时候创建对象并注入其所有的引用,然后每次请求都使用此对象,对象httpClient只有这一次注入机会,
所以两次不同的请求,使用的httpClient是同一个对象,
怎么解决呢,不让spring管理我们的httpClient注入了 ,@Lookup 此注解来自己管理
@Component
public class HttpClientService {
protected static final Logger logger = LoggerFactory.getLogger(HttpClientService.class);
@Lookup
public CloseableHttpClient getHttpClient() {
return (CloseableHttpClient) SpringContainer.springContainer.context.getBean("httpClient");
}
public String post(Map params , String url){
CloseableHttpClient httpClient = getHttpClient();
String str = "";
try {
HttpPost httppost = new HttpPost(url);
//设置超时10s
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();//设置请求和传输超时时间
httppost.setConfig(requestConfig);
List<NameValuePair> list = new ArrayList<NameValuePair>();
Iterator<?> iterator = params.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> elem = (Map.Entry<String, String>) iterator.next();
list.add(new BasicNameValuePair(elem.getKey(), elem.getValue()));
}
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httppost.setEntity(entity);
CloseableHttpResponse response;
try {
response = httpClient.execute(httppost);
HttpEntity httpentity = response.getEntity();
if(httpentity != null) {
str = EntityUtils.toString(httpentity, "UTF-8");
}
} catch (ClientProtocolException e) {
logger.error("httpclient请求失败",e);
} catch (IOException e) {
logger.error("httpclient请求超时",e);
return null;
} finally {
try {
httpClient.close();
} catch (IOException e) {
logger.error("httpclient连接关闭失败",e);
}
}
} catch (UnsupportedEncodingException e) {
logger.error("httpclient获取结果失败,编码异常",e);
}
return str;
}
每次使用HttpClientService 都来从容器中重新获取httpClient,
到此问题解决,重走测试方法,测试通过