使用@Value为静态变量导入并使用导入的静态变量进行初始化方式
作者:思影影思
1 问题描述
在南京出差时,在开始开发,自己把一些相对紧密联系的不变得配置放进一个类中,这些字段为static的,待交付时,由于这些配置也要是可以通过配置文件进行配置的,因此无形之中就引入了一个问题。
即使用@Value对静态变量进行导入的问题。并且还有一种更加复杂的情形,即需要在生成相关的Bean时,需要进行一些资源的初始化,在当时自己结结实实的踩了一把坑。
在项目开始时TomcatConfig类是如下的:
public class TomcatConfig { public static String ip = "192.168.1.112"; public static int port=8080; public static String username=admin; public static String password=admin; }
但在交付之前,要把这些配置值放入配置文件,例如application.properties
tomcat.ip=10.30.102.111 tomcat.port=8080 tomcat.username=admin tomcat.password=admin
并且要在该类再开始时通过HTTP调用,使用这些参数进行一次初始化。
2 问题结构
2.1 服务方
可以理解自己开发的模块依赖于其他的模块,模块之间通过HTTP通信获取数据和状态。可以通过Spring Boot起一个程序提供一个对外的接口,即相当于提供服务。
这就是服务提供的类。
该程序所在的服务器ip和端口即上边已经写进配置文件的ip和端口。
package com.wisely.ch6_2_3.controller; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.wisely.ch6_2_3.config.TomcatSetting; import lombok.extern.java.Log; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; /** * 测试注入 * * @Owner: * @Time: 2019/3/31-16:29 */ @RestController @Log public class TestValue { @Autowired private TomcatSetting serverConfig; @RequestMapping(value = "/getImg",produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public String getImgeLoc() { log.info("Enter getImgeLoc"); System.out.println("-- Handling --"); String fileName = UUID.randomUUID().toString()+".jpg"; JSONObject result = new JSONObject(); result.put("imgUrl", serverConfig.getUrl()+"/"+fileName); System.out.println("-- over --"); return result.toJSONString(); } @RequestMapping("/getmapping") public String service(@RequestParam("username") String username, @RequestParam("password") String password) { if (!username.equals("admin") ||!password.equals("admin")) { JSONObject res = new JSONObject(); res.put("state", -1); res.put("msg", "fail"); return res.toJSONString(); } JSONObject result = new JSONObject(); result.put("state", 0); result.put("msg", "success"); JSONObject data = new JSONObject(); data.put("aaa", "bbb"); data.put("ccc", "ddd"); result.put("data", data); return result.toJSONString(); } }
2.2 客户方
由于之前写的代码是硬编码,肯定要重构这部分的代码,@Value可以灵活的实现注入,但通过在互联网上查询相关的页面可以得到如下的知识:
- SpringBoot中使用@Value()只能给普通变量注入值,不能直接给静态变量赋值,并且@Value使用设值函数为成员注入值。
- 为了给静态变量注入值,需要使用特殊的语法
2.2.1核心类 TomcatConfig
package com.example.staticvalue.config; import com.alibaba.fastjson.JSONObject; import com.example.staticvalue.HttpClientUtil.HttpClientUtil; import com.fasterxml.jackson.databind.util.JSONPObject; import lombok.Getter; import lombok.extern.java.Log; import org.apache.http.protocol.HTTP; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.ComponentScan; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; /** * tomcat配置 * * @Owner: * @Time: 2019/3/31-19:25 */ @Component @Log public class TomcatConfig { @Getter private static String ip; @Getter private static int port; @Getter private static String username; @Getter private static String password; private static JSONObject data = new JSONObject(); # 在静态初始化块中调用set()是无效的,因为此时ip等变量尚未注入。应该是在该Bean已经生成了,即ip,port,usernam,password已经注入之后再调用set()这样才有效。 static { log.info("静态化块"); log.info("TomcatConfig.ip: "+ TomcatConfig.ip); ~~~~try { ~~set();~~ ~~} catch (Exception e) {~~ ~~e.printStackTrace();~~ }~~~~ } @Value("${tomcat.ip}") public void setIp(String ip) { TomcatConfig.ip = ip; } @Value("${tomcat.password}") public void setPassword(String password) { TomcatConfig.password = password; } @Value("${tomcat.port}") public void setPort(int port) { TomcatConfig.port = port; } @Value("${tomcat.username}") public void setUsername(String username) { TomcatConfig.username = username; } // 注意该函数应在类外进行调用,比如set()可以放置在其他标注了雷瑟@Componet的Bean类中,这样可以保证在TomcatConfig的bean已经生成。 public static void set() throws Exception { log.info("Enter set"); log.info("TomcatConfig.ip: "+ TomcatConfig.ip); String accessUrl = "http://"+getIp()+":"+getPort()+"/getmapping?"; accessUrl += "username="+username+"&"+"password="+password; log.info("accessUrl = "+accessUrl); String result = HttpClientUtil.postJson(accessUrl, "{}"); JSONObject ret = JSONObject.parseObject(result); if (ret.getIntValue("state") == 0) { data = ret.getJSONObject("data"); } } }
注意:set()方法完成了类似初始化的工作,即通过调用一次http请求,填充了static成员data的值。
set()的调用不可以放在本类的静态环境下,尤其在war包运行在tomcat之下更是这样,在jar包中set()函数的调用放在静态函数main之中是有效的。
这可以通过日志进行证明,如果是war包跑在tomcat环境下,若为如下代码:
public static void main(String[] args) throws Exception{ log.info("Enter main"); SpringApplication.run(StaticvalueApplication.class, args); log.info("after run"); TomcatConfig.set(); log.info("after set"); }
程序会仅执行到after run日志打印,不会执行TomcatConfig.set()的调用,切记,切记
如果真的是在war包跑在tomcat下,可以使用如下的方式解决,可以把TomcatConfig.set()的调用放在其他Bean类的静态初始化块中。
2.2.2 项目结构
2.2.3辅助类HttpClientUtil
package com.example.staticvalue.HttpClientUtil; import org.apache.http.HttpEntity; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.CharArrayBuffer; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; /** * 辅助类,使用http获取数据 * * @Owner: * @Time: 2019/3/31-19:49 */ public class HttpClientUtil { public static String postJson(String url,String jsonString) throws Exception { String result = null; CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost post = new HttpPost(url); CloseableHttpResponse response = null; try { post.setEntity(new ByteArrayEntity(jsonString.getBytes("UTF-8"))); post.setHeader("Content-Type","application/json" ); response = httpClient.execute(post); if(response != null && response.getStatusLine().getStatusCode() == 200) { HttpEntity entity = response.getEntity(); result = entityToString(entity); } return result; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { try { httpClient.close(); if(response != null) { response.close(); } } catch (IOException e) { e.printStackTrace(); } } return null; } private static String entityToString(HttpEntity entity) throws IOException { String result = null; if(entity != null) { long lenth = entity.getContentLength(); if(lenth != -1 && lenth < 2048) { result = EntityUtils.toString(entity,"UTF-8"); }else { InputStreamReader reader1 = new InputStreamReader(entity.getContent(), "UTF-8"); CharArrayBuffer buffer = new CharArrayBuffer(2048); char[] tmp = new char[1024]; int l; while((l = reader1.read(tmp)) != -1) { buffer.append(tmp, 0, l); } result = buffer.toString(); } } return result; } }
2.2.4 启动类
package com.example.staticvalue; import com.example.staticvalue.config.TomcatConfig; import lombok.extern.java.Log; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @Log public class StaticvalueApplication { public static void main(String[] args) throws Exception{ log.info("Enter main"); SpringApplication.run(StaticvalueApplication.class, args); log.info("after run"); //在Jar包中运行时,下句是可以生效的,但在war包运行在tomcat中时,TomcatConfig。set()以及之后的语句不会执行。把Tomcat.set()放在某个Bean的静态初始化块中即可 TomcatConfig.set(); log.info("after set"); } }
2.2.5 TaskService
package com.example.staticvalue.service; import com.example.staticvalue.config.TomcatConfig; import lombok.extern.java.Log; import org.springframework.stereotype.Component; /** * 任务类 * * @Owner: * @Time: 2019/3/31-19:39 */ @Component @Log public class TaskService { static { try { log.info("TaskService: before set"); TomcatConfig.set(); log.info("TaskService: after set"); } catch (Exception e) { e.printStackTrace(); } } }
3总结
上述注入静态变量的主要原因是静态变量因为一些遗留的原因,为了解决硬编码的问题而引入的,至于在静态变量通过@Value注入时同时在该类中进行初始化,则是非常不优雅的一种编程风格,这种初始化的方式更加建议放置在类似开机自启动的应用程序中,在Spring Boot中也确实有这样的实现场景。
即CommandLineRunner接口,实现该接口的类必须实现run()方法,而该方法会在所有Bean均已正确生成之后开始执行。
关于CommandLineRunner,可以参考CommandLineRunner或者ApplicationRunner接口,至于这种用法,改天再来写一篇阐述的文章吧。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。