spring作用域protoType踩过的坑

大家都知道在非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,

到此问题解决,重走测试方法,测试通过

 

 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值