能够不会,可是得知道java
做为一名默默无闻辛苦搬砖的程序员,搬砖和造轮子都不是最终目的。拜读过许多优秀的文章,心想本身有什么骚操做值得拿出来分享一下的,结合本身平时工做的遇到的问题,因而总结出这篇线上问题解决方案之偷梁换柱,免升级解决线上问题程序员
在一个风雨交加的晚上,本身躺在床上心神不宁,彷佛在暗示着什么。因而一成天的经历在脑海里像电影同样放映着,因而画面定格在了那一秒,下午的那一次升级,What's wrong with that,到底有什么不对,因而赶忙打开本身的人脑debug模式,一行一行的去回忆的本身写的每一行代码。卧*,灵感到来忽然之间,不知道是该感叹本身记忆的强大,仍是该为这个NullPointerException而惧怕。事故、绩效、年终奖、线上用户多个词同时映入在本身的脑海里,越想越心神不宁,写出去的代码泼出去的水,还有什么方式能够补救,因而又一番思绪涌上心头…web
假设线上有一个service(spring bean)的其中一个方法出现了空指针异常或者获得的并非咱们想要的结果,如今用一个ErrorService(本身程序里面的一个类模拟一下),假设这就是那个service,errorMethod方法是那个让我躺在床上久久不能入眠的那个方法,接下来经过代码展现一下罪魁祸首spring
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Random;
/**
* 用来模拟线上异常service
*
* @author: ghb
* @date 2020/1/4
*/
@Service
public class ErrorService {
@Autowired
protected PrintService printService;
private int id;
public ErrorService() {
Random random = new Random();
id = random.nextInt(100);
System.out.println("调用ErrorService的构造方法");
}
/**
* 这个是须要进行修改的目标方法
*/
public String errorMethod(String msg) {
return printService.print("[ErrorService]-print-" + msg + id);
}
}
复制代码
先来看一下接口的返回结果>>>json
罪魁祸首已经找到errorMethod获得的结果并非我想要的,因而提出如下问题:api
想着想着脑海里又浮现出复联4中复联大军PK灭霸的场景,面对errorMethod,我何德何能,到底谁才是带给我但愿的美国队长和Iron Man 呢,形势朝不保夕,接下来有请他们上场架构
下面经过一段代码演示一下groovy 的stream遍历跟java中不一样的地方,其余绝技会再其余的文章进行介绍app
final personList = [
new Person("Regina", "Fitzpatrick", 25),
new Person("Abagail", "Ballard", 26),
new Person("Lucian", "Walter", 30),
]
assertTrue(personList.stream().filter { it.age > 20 }.findAny().isPresent())
assertFalse(personList.stream().filter { it.age > 30 }.findAny().isPresent())
assertTrue(personList.stream().filter { it.age > 20 }.findAll().size() == 3)
assertTrue(personList.stream().filter { it.age > 30 }.findAll().isEmpty())
复制代码
官网请参考www.groovy-lang.org/dom
阿里巴巴在2018年7月份发布Nacos, Nacos 支持几乎全部主流类型的服务的发现、配置和管理Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。jvm
Nacos具有服务优雅上下线和流量管理(API+后台管理页面),而Eureka的后台页面仅供展现,须要使用api操做上下线且不具有流量管理功能。Nacos具备分组隔离功能,一套Nacos集群能够支撑多项目、多环境。nacos具备Apollo大部分功能,最重要的是配置中心与注册中心打通,能够省去咱们在微服务治理方面 的一些投入
由于这篇位置奇异博士只是做为配角出场,在此先不对其进行过多的介绍,这里只是提供它传送门的做用,先来了解一下spring cloud项目如何引入nacos
<!--配置中心-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
<!--服务注册与发现-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.1.RELEASE</version>
</dependency>
复制代码
这个dataId为偷梁换柱就是咱们的传送门,目的就是经过它来将咱们用于干掉ErrorService(灭霸)的那段代码传送过来,至于为何选择它做为奇异博士还纯属我的喜爱。那么咱们来看看传送门内部长什么样吧
传送门里面是一个list列表,里面的每一项对应着咱们须要进行替换的Service的名称,已经知道了敌人是谁,那咱们就得把打boss的秘密武器释放出来,那么欢迎钢铁侠上场
import com.ghb.book.model.A
import com.ghb.book.service.ErrorService
import org.springframework.beans.factory.annotation.Autowired
class CorrectService extends ErrorService {
private int id;
/**
* 这里注入一个spring bean而且调用它的get方法
*/
@Autowired
A a
public CorrectService() {
Random random = new Random();
id = random.nextInt(100);
System.out.println("调用CorrectService的构造方法");
}
@Override
String errorMethod(String msg) {
return printService.print("[CorrectService]-print" + msg + id);
}
}
复制代码
先来介绍下这段代码,这是用groovy声明的一个类,其保存在nacos配置中内心面,为何使用它保存咱们的代码,由于做为配置中心,它支持各类格式文件的保存,而且历史版本能够支持代码回滚,以前一直使用apollo做为配置中心,值得遇到了nacos,我才发现原来曾经深入认为的并非我想要的…
该类继承自ErrorService,从新了errorMethod方法,返回须要的正确的结果,如今它只是一个普通的类,里面为何能注入@Autowired一个spring bean呢,就是由于那是钢铁侠吗,固然不是,若是剧情只有这么简单那岂不是很不过瘾
刚才奇异博士的传送门咱们已经看到了,那么这段代码就是须要被传送的秘密武器了,接下来请观看奇异博士搞出传送门这个东西,到底修炼了什么功法
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.ghb.book.service.RegisterBeanService;
import com.ghb.book.util.GroovyScriptFactory;
import com.ghb.book.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosConfigProperties;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.Executor;
/**
* @author: ghb
* @date 2019/12/24
*/
@Component
@Slf4j
public class InitConfig {
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private RegisterBeanService registerBeanService;
@PostConstruct
public void init() throws NacosException {
nacosConfigProperties.configServiceInstance()
.addListener("偷梁换柱", "DEFAULT_GROUP", new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String s) {
log.info("监听到数据更新:{}", s);
JSONArray jsonArray = JSON.parseArray(s);
jsonArray.forEach(beanName -> {
try {
//惊奇队长上线 注册新的bean到spring容器中
register(String.valueOf(beanName));
} catch (NacosException e) {
log.info("偷梁换柱失败");
}
});
}
});
}
/**
* 1 从nacos中获取配置类
* 2 解析类
* 3 偷梁换柱
*
* @param beanName spring bean 命名
* @throws NacosException e
*/
public void register(String beanName) throws NacosException {
//读取groovy配置
String groovy = nacosConfigProperties.configServiceInstance()
.getConfig(beanName, "groovy", 1000);
//加载groovy类 并获取groovy class类类型
Class groovyClass = GroovyScriptFactory.getInstance().parseClass(groovy);
//注册bean
Object bean = registerBeanService.registerBean(beanName, groovyClass);
log.info("bean---{}", bean.getClass().getName());
}
}
复制代码
在spring容器启动的时候,执行声明周期回调方法,经过@PostConstruct去添加一个监听器,去监听传送门里面的数据,这里这个传送门是代码里面的偷梁换柱,若是监听到数据有变化,去遍历传送门里面的数据,而后将每一项交给美国队长,注册新的bean到spring容器中,关于如何解析一个groovy类本片文章先不作过多的赘述
美国漫画中最“主旋律”的超级英雄非美国队长莫属。他用国名当作头衔,制服是红白蓝加上明亮的星星;一面一样颜色的盾牌就是他的武器。接下来期待下他的操做
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
/**
* @author: ghb
* @date 2020/1/4
*/
@Component
@Slf4j
public class RegisterBeanService implements ApplicationContextAware {
ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public <T> T registerBean(String name, Class<T> clazz, Object... args) {
ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
// 经过BeanDefinitionBuilder建立bean定义
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
for (Object arg : args) {
beanDefinitionBuilder.addConstructorArgValue(arg);
}
//bean定义
BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
//从spring容器中获取bean工厂
BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) context.getBeanFactory();
if (context.containsBean(name)) {
//先从spring容器中获取该bean
Object bean = context.getBean(name);
if (bean.getClass().isAssignableFrom(clazz)) {
log.info("bean:[{}]在系统中已存在,接下来对其进行替换操做", name);
if (clazz.getGenericSuperclass() == bean.getClass()) {
log.info("新bean是被替换bean的子类,符合逻辑,开始替换操做.....");
beanFactory.removeBeanDefinition(name);
beanFactory.registerBeanDefinition(name, beanDefinition);
} else {
throw new RuntimeException("偷梁换柱失败,非法操做");
}
} else {
log.info("bean:[{}]系统中不存在存在,建立bean", name);
beanFactory.registerBeanDefinition(name, beanDefinition);
}
}
return applicationContext.getBean(name, clazz);
}
}
复制代码
这段代码就是注册bean到spring容器中的核心操做了,这里只是一个用于实现功能的简化版的方法,咱们来看看它到底干了什么
上述操做完成后咱们已经实现了偷梁换柱而且灭霸最终被战胜,弹响指的过程就是忘nacos配置中心中偷梁换柱中添加errorService的过程,固然只是使用nacos最为一个媒介,经过接口调用也是能够,咱们来看看从新回归和平后的模样,终于守的云开见月明,一切问题都迎刃而解
思考几个问题
通过上述操做,结合groovy、nacos和spring实现了对问题的动态修复,灭霸页最终被战胜,今后不再用担忧本身线上写出来的bug了。在此我的只是提出了一种解决问题的方法和思路。代码也并不完善,若是存在任何错误,欢迎你们指正。