SpringBoot根据yml配置信息动态生成bean并加入Spring容器

Posted 敲代码的小小酥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot根据yml配置信息动态生成bean并加入Spring容器相关的知识,希望对你有一定的参考价值。

背景

yml里配置经纬度信息,然后通过WebSocket接收配置的经纬度范围内的车辆数据。代码如下:

@Component
public class CarInitTask implements CommandLineRunner,WebSocketHandler {
    Logger logger= LoggerFactory.getLogger(CarInitTask.class);

    @Value("${websoket.car.url}")
    private String webSocketUrl;

    @Value("${websoket.car.zone}")
    private String zone;//全局范围内车辆

    @Autowired
    ISjfzCarService carService;

     @Autowired
     ISjfzInformationboardService informationboardService;

    @Autowired
    CarMesageHandler carMesageHandler;

    @Autowired
    EmqClient emqClient;

    Map<String,String> carsMap=new HashMap();

    int count=0;






    @Override
    public void run(String... args) throws Exception {
         try {
              String timestap = DateUtils.dateTimeNow("yyyy-MM-dd");
              String tablename="`sjfz_car_" + timestap+"`";
              String Informationboardtablename = "`sjfz_informationboard_" + timestap+"`";
              carService.createDayCarsTable(tablename);
              informationboardService.createDayInformationboardTable(Informationboardtablename);

         }catch (Exception e){
              e.printStackTrace();
         }
        logger.error("全局连接车辆websocket...........");
        System.out.println(zone);
        WsWebSocketContainer wsWebSocketContainer = new WsWebSocketContainer();
        wsWebSocketContainer.setDefaultMaxBinaryMessageBufferSize(5120000);
        wsWebSocketContainer.setDefaultMaxTextMessageBufferSize(5120000);
        // wsWebSocketContainer.setDefaultMaxSessionIdleTimeout(300000);
        StandardWebSocketClient client = new StandardWebSocketClient(wsWebSocketContainer);
        WebSocketHandler webSocketHandler=this;
        String uriTemplate = webSocketUrl;
        Object uriVars = null;
        ListenableFuture<WebSocketSession> future =client.doHandshake(webSocketHandler, uriTemplate, uriVars);
        try {
            WebSocketSession session = future.get();
            System.out.println(session);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
        TextMessage message=new TextMessage(zone.getBytes());
        webSocketSession.sendMessage(message);

    }

    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
      

        count++;
       
        Map map = JSONObject.fromObject(webSocketMessage.getPayload());
        Map map1=JSONObject.fromObject( map.get("result"));
        JSONArray jsonArray = JSONArray.fromObject(map1.get("perList"));
        List<Map> perList = JSONArray.toList(jsonArray, Map.class);
        for (int i=0;i<perList.size();i++){
            JSONArray dataArray=   JSONArray.fromObject(perList.get(i).get("data"));
            List<Map> dataList =  JSONArray.toList(dataArray,Map.class);
            for(int j=0;j<dataList.size();j++){
                String confidence=dataList.get(j).get("confidence").toString();
                String devId=dataList.get(j).get("devId").toString();
                String gpsTime=dataList.get(j).get("gpsTime").toString();
                Date date = new Date();
                date.setTime(Long.valueOf(gpsTime));
                String temp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(date);
                String heading=dataList.get(j).get("heading").toString();
                String latitude=dataList.get(j).get("latitude").toString();
                String longitude=dataList.get(j).get("longitude").toString();
                String objColor=dataList.get(j).get("objColor").toString();
                String plateNo=dataList.get(j).get("plateNo").toString();
                String speed=dataList.get(j).get("speed").toString();
                String targetType=dataList.get(j).get("targetType").toString();
                String vehicleId=dataList.get(j).get("vehicleId").toString();
              
                try {
                   // carMesageHandler.handleCarMessage(plateNo, vehicleId);
                }catch (Exception e){
                    e.printStackTrace();
                }
                 String tablename="`sjfz_car_" +DateUtils.parseDateToStr("yyyy-MM-dd",DateUtils.parseDate(temp,"yyyy-MM-dd HH:mm:ss SSS"))+"`";
               
                SjfzCar car=new SjfzCar();
                car.setConfidence(confidence);
                car.setDevId(devId);
                car.setGpsTime(temp);
                car.setHeading(heading);
                car.setLatitude(latitude);
                car.setLongitude(longitude);
                car.setObjColor(objColor);
                car.setPlateNo(plateNo);
                car.setSpeed(speed);
                car.setTargetType(targetType);
                car.setVehicleId(vehicleId);
                car.setTablename(tablename);
                String time=carsMap.get(plateNo);
                if(StringUtils.isEmpty(time)){
                    carsMap.put(plateNo,temp);
                    carService.insertSjfzCar(car);
                    emqClient.publish("jd/12", JSON.toJSONString(car), QosEnum.Qos0,false);
                }else{
                  
                    if(DateUtils.parseDate(temp,"yyyy-MM-dd HH:mm:ss SSS").getTime()-DateUtils.parseDate(time,"yyyy-MM-dd HH:mm:ss SSS").getTime()>=500){
                        carsMap.put(plateNo,temp);
                        carService.insertSjfzCar(car);
                        emqClient.publish("jd/12", JSON.toJSONString(car), QosEnum.Qos0,false);
                    }
                }



            }
        }
        if(count==100){
            carsMap.clear();
        }



    }

    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {

    }

    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        logger.info("断开了websocket连接:"+closeStatus.toString());
        reconnect();
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }

    public void reconnect(){
  
        WsWebSocketContainer wsWebSocketContainer = new WsWebSocketContainer();
        wsWebSocketContainer.setDefaultMaxBinaryMessageBufferSize(5120000);
        wsWebSocketContainer.setDefaultMaxTextMessageBufferSize(5120000);
        // wsWebSocketContainer.setDefaultMaxSessionIdleTimeout(300000);
        StandardWebSocketClient client = new StandardWebSocketClient(wsWebSocketContainer);
        WebSocketHandler webSocketHandler=this;
        String uriTemplate = webSocketUrl;
        Object uriVars = null;
        ListenableFuture<WebSocketSession> future =client.doHandshake(webSocketHandler, uriTemplate, uriVars);
        try {
            WebSocketSession session = future.get();
            System.out.println(session);
          
        }catch (Exception e){
          
            e.printStackTrace();
            reconnect();
        }

    }

    

    public String getZone() {
        return zone;
    }

    public void setZone(String zone) {
        this.zone = zone;
    }
}

上面的代码,将yml中经纬度的配置通过@Vaule注解注入了zone属性中,然后接收这个区间的webSocket数据。当经纬度区间写的很大时,数据会非常多,而且Websocket一个连接是单线程的,所以处理起来速度会很慢。
所以要把经纬度信息分成几段区间,进行数据接收。这样的话,分成了几段,就要写几个上述代码的类,来分别接收各自路段的zone经纬度范围。但是具体分成多少段,又不确定,每分一段,就添加一个类,也很麻烦。所以考虑根据yml里的配置,配置了几个路段,就动态生成几个上述类。

解决

动态生成bean,可以转化为动态的增加BeanDefinition对象。Spring容器中的bean都是由BeanDefinition来的。所以可以动态增加BeanDefinition。Spring为我们提供了这样的接口,来动态增加BeanDefinition对象,那就是BeanDefinitionRegistryPostProcessor接口。下面看代码:

@Component
public class CarInitPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {

    Environment env;



    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        String property = env.getProperty("websoket.car.zone");
        List<String> zones = JSONObject.parseArray(property,String.class);
        for(int i=0;i<zones.size();i++){
            String zone=zones.get(i);
            BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.rootBeanDefinition(CarInitTask.class);
            beanDefinitionBuilder.addPropertyValue("zone",zone);
            registry.registerBeanDefinition("carInitTask"+i,beanDefinitionBuilder.getBeanDefinition());
        }
    }



    @Override
    public void setEnvironment(Environment environment) {
        this.env=environment;
    }



}

上述该类,实现了BeanDefinitionRegistryPostProcessor接口和EnvironmentAware接口。
BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法用来注册新的BeanDefinition对象。
EnvironmentAware接口用来获取Environment对象。Environment对象获取yml里的配置信息,相关代码如下:

  String property = env.getProperty("websoket.car.zone");
  List<String> zones = JSONObject.parseArray(property,String.class);

可以看到,将yml中的经纬度转成了List集合。然后遍历这个集合,根据每个经纬度配置,动态生成一个BeanDefinition对象。
这里用到了BeanDefinitionBuilder类来创建BeanDefinition对象,参数就是我们要动态创建的类的class对象。如下代码:

BeanDefinitionBuilder beanDefinitionBuilder=BeanDefinitionBuilder.rootBeanDefinition(CarInitTask.class);

这里需要注意的是,我们动态创建CarInitTask的BeanDefinition对象,则CarInitTask类本身就无需再加@Component注解了,而且也无需用@Value注入zone属性了,而是需要动态添加zone属性,代码如下:

beanDefinitionBuilder.addPropertyValue("zone",zone);

通过addPropertyValue方法添加成员属性的值。这里也需要注意一点,用addPropertyValue方法注入值时,需要提供zone属性的get和set方法。

最后,将动态生成的BeanDefinition对象注册到BeanFactory中:

registry.registerBeanDefinition("carInitTask"+i,beanDefinitionBuilder.getBeanDefinition());

至此,完成了类的动态生成。在Spring初始化的后续过程,生成这几个类的实例,并执行里面的方法,进行数据的接收。

总结

这里几个知识点需要注意:
1.BeanDefinitionBuilder类生成BeanDefinition对象。
2.BeanDefinition可以通过addPropertyValue方法设置对象的成员属性和值。

以上是关于SpringBoot根据yml配置信息动态生成bean并加入Spring容器的主要内容,如果未能解决你的问题,请参考以下文章

springboot 整合RabbitMQ yml配置文件配置交换机 队列信息

spring boot 无法从 application.yml 获取配置

springboot+mybatis项目自动生成

SpringBoot使用配置类映射yml配置文件信息

springboot获取application.yml中的配置信息

springboot 根据用户ID切换动态数据源代码实现