依赖配置中心实现注有@ConfigurationProperties的bean相关属性刷新
配置中心是什么
配置中心,通过key=value的形式存储环境变量。配置中心的属性做了修改,项目中可以通过配置中心的依赖(sdk)立即感知到。需要做的就是如何在属性发生变化时,改变带有@ConfigurationProperties的bean的相关属性。
配置中心原理
在读配置中心源码的时候发现,里面维护了一个Environment,以及ZookeeperPropertySource。当配置中心属性发生变化的时候,清空ZookeeperPropertySource,并放入最新的属性值。
public class ZookeeperPropertySource extends EnumerablePropertySource<Properties>
ZookeeperPropertySource重写了equals和hahscode方法,根据这两个方法可以判定配置中心是否修改了属性。
动态刷新bean属性原理
实现原理图
动态刷新bean父类
public abstract class BaseConfigCenterBean implements InitializingBean { private static Logger LOGGER = LoggerFactory.getLogger(BaseConfigCenterBean.class); //配置中心是否生效
protected boolean cfgCenterEffect = false; public boolean isCfgCenterEffect() {
this.checkCfgCenterEffect();
return cfgCenterEffect;
} private void checkCfgCenterEffect() {
boolean tmpCfgCenterEffect = !Objects.isNull(ConfigHelper.getEnvironment());
if (tmpCfgCenterEffect) {// NOSONAR
String value = (String) ConfigHelper.getZookeeperPropertySource().getProperty("cfg.center.effect");
if (StringUtils.isBlank(value)) {
tmpCfgCenterEffect = false;
} else {
tmpCfgCenterEffect = Boolean.valueOf(value);
}
} cfgCenterEffect = tmpCfgCenterEffect; if (cfgCenterEffect) {
String prefix = this.getConfigPrefix();
cfgCenterEffect = Arrays.stream(ConfigHelper.getZookeeperPropertySource().getPropertyNames())
.filter(keyName -> keyName.indexOf(prefix) == 0)
.count() > 0;
if (!cfgCenterEffect) {
LOGGER.info(String.format("配置中心没有发现模块=%s, prefix=%s的配置,将使用本地配置...", this.getModuleName(), prefix));
}
}
} /**
* 绑定自身目标
**/
protected void doBind() {
Class<? extends BaseConfigCenterBean> clazz = this.getClass();
if (AopUtils.isCglibProxy(this)) {
clazz = (Class<? extends BaseConfigCenterBean>) AopUtils.getTargetClass(this);
}
BaseConfigCenterBean target = binding(isCfgCenterEffect(), clazz, this.getDefaultResourcePath());
this.copyProperties(target);
} private void copyProperties(BaseConfigCenterBean target) {
ReflectionUtils.doWithFields(this.getClass(), field -> {
field.setAccessible(true);
field.set(this, field.get(target));
}, field -> AnnotatedElementUtils.isAnnotated(field, ConfigField.class));
} /**
* 绑定其他目标
*
* @param clazz 目标类
**/
protected <T> T doBind(Class<T> clazz) {
T target = binding(isCfgCenterEffect(), clazz, this.getDefaultResourcePath());
if (target instanceof InitializingBean) {
try {
((InitializingBean) target).afterPropertiesSet();
} catch (Exception e) {
LOGGER.error(String.format("属性初始化失败[afterPropertiesSet], class=%s", ClassUtils.getSimpleName(clazz), e));
}
}
return target;
} private <T> T binding(boolean cfgCenterEffect, Class<T> clazz, String defaultResourcePath) {
Optional<PropertySource> propertySource = Optional.empty(); if (cfgCenterEffect) {
propertySource = Optional.ofNullable(ConfigHelper.getZookeeperPropertySource());
} else {
Optional<ResourcePropertySource> resourcePropertySource = ResourceUtils.getResourcePropertySource(defaultResourcePath);
if (resourcePropertySource.isPresent()) {
propertySource = Optional.ofNullable(resourcePropertySource.get());
}
} if (propertySource.isPresent()) {
T target;
try {
target = RelaxedConfigurationBinder
.with(clazz)
.setPropertySources(propertySource.get())
.doBind();
} catch (GeneralException e) {
LOGGER.error(String.format("属性绑定失败, class=%s", ClassUtils.getSimpleName(clazz)), e);
return null;
}
return target;
}
return null;
} @Override
public void afterPropertiesSet() {
Class<?> target = this.getClass();
if (AopUtils.isAopProxy(this)) {
target = AopUtils.getTargetClass(this);
}
LOGGER.info(String.format("%s->%s模块引入配置中心%s...", this.getModuleName(), ClassUtils.getSimpleName(target), (isCfgCenterEffect() ? "生效" : "无效")));
} public String getModuleName() {
return StringUtils.EMPTY;
} @Subscribe
public void listenRefreshEvent(ConfigCenterUtils.ConfigRefreshEvent refreshEvent) {
if (!refreshEvent.getModuleName().equals(this.getModuleName())) {
this.refreshForEvent();
}
} //通过事件进行刷新
public abstract void refreshForEvent(); //获取本地配置默认路径
public abstract String getDefaultResourcePath(); //获取配置属性的公共前缀
public abstract String getConfigPrefix();
}
1、isCfgCenterEffect方法主要判断项目是否接入了配置中心并且配置中心配有bean中相关的属性。
2、binding方法主要根据isCfgCenterEffect方法的返回值去加载配置中心的properties还是本地的properties。
3、getDefaultResourcePath是主要是获取本地资源的默认路径(在没有接入配置中心的情况下)。
4、getConfigPrefix方法返回bean中配置属性的公共前缀(等同于@ConfigurationProperties中的prefix属性)。
5、refreshForEvent方法主要是在某个bean感知到配置中心更新属性时异步通知其他bean进行属性的更新。
bean属性绑定工具类
动态将propertysource绑定到带有@ConfigurationProperties注解的bean中。
参考 org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
public class RelaxedConfigurationBinder<T> {
private final PropertiesConfigurationFactory<T> factory; public RelaxedConfigurationBinder(T object) {
this(new PropertiesConfigurationFactory<>(object));
} public RelaxedConfigurationBinder(Class<?> type) {
this(new PropertiesConfigurationFactory<>(type));
} public static <T> RelaxedConfigurationBinder<T> with(T object) {
return new RelaxedConfigurationBinder<>(object);
} public static <T> RelaxedConfigurationBinder<T> with(Class<T> type) {
return new RelaxedConfigurationBinder<>(type);
} public RelaxedConfigurationBinder(PropertiesConfigurationFactory<T> factory) {
this.factory = factory;
ConfigurationProperties properties = getMergedAnnotation(factory.getObjectType(), ConfigurationProperties.class);
javax.validation.Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
factory.setValidator(new SpringValidatorAdapter(validator));
factory.setConversionService(new DefaultConversionService());
if (!Objects.isNull(properties)) {//NOSONAR
factory.setIgnoreNestedProperties(properties.ignoreNestedProperties());
factory.setIgnoreInvalidFields(properties.ignoreInvalidFields());
factory.setIgnoreUnknownFields(properties.ignoreUnknownFields());
factory.setTargetName(properties.prefix());
factory.setExceptionIfInvalid(properties.exceptionIfInvalid());
}
} public RelaxedConfigurationBinder<T> setTargetName(String targetName) {
factory.setTargetName(targetName);
return this;
} public RelaxedConfigurationBinder<T> setPropertySources(PropertySource<?>... propertySources) {
MutablePropertySources sources = new MutablePropertySources();
for (PropertySource<?> propertySource : propertySources) {
sources.addLast(propertySource);
}
factory.setPropertySources(sources);
return this;
} public RelaxedConfigurationBinder<T> setPropertySources(Environment environment) {
factory.setPropertySources(((ConfigurableEnvironment) environment).getPropertySources());
return this;
} public RelaxedConfigurationBinder<T> setPropertySources(PropertySources propertySources) {
factory.setPropertySources(propertySources);
return this;
} public RelaxedConfigurationBinder<T> setConversionService(ConversionService conversionService) {
factory.setConversionService(conversionService);
return this;
} public RelaxedConfigurationBinder<T> setValidator(Validator validator) {
factory.setValidator(validator);
return this;
} public RelaxedConfigurationBinder<T> setResolvePlaceholders(boolean resolvePlaceholders) {
factory.setResolvePlaceholders(resolvePlaceholders);
return this;
} public T doBind() throws GeneralException {
try {
return factory.getObject();
} catch (Exception ex) {
throw new GeneralException("配置绑定失败!", ex);
}
}
}
配置中心工具类
public class ConfigCenterUtils {
private static Logger LOGGER = LoggerFactory.getLogger(ConfigCenterUtils.class); private static AsyncEventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(8));//NOSONAR private static Properties cfgProperties; private static Environment environment; static {
cfgProperties = new Properties();
cfgProperties.putAll(ConfigHelper.getZookeeperPropertySource().getProperties());
} public static void setEnvironment(Environment environment) {
ConfigCenterUtils.environment = environment;
} public static String getValue(String name) {
try {
return PropertiesUtil.getValue(name);
} catch (Exception e) {
LOGGER.info("配置中心无效, property name=" + name, e);
}
if (Objects.isNull(environment)) {
LOGGER.info("environment无效,property name=" + name);
return StringUtils.EMPTY;
}
if (!environment.containsProperty(name)) {
LOGGER.info("environment无配置 property name=" + name);
return StringUtils.EMPTY;
}
return environment.getProperty(name);
} public synchronized static boolean propertySourceShouldRefresh(String moduleName, ZookeeperPropertySource newPropertySource) {
if (!cfgProperties.equals(newPropertySource.getProperties())) {
cfgProperties.clear();
cfgProperties.putAll(newPropertySource.getProperties());
eventBus.post(new ConfigRefreshEvent(moduleName));
return true;
}
return false;
} public static <T> T createToRefreshPropertiesBean(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
// 设置代理对象父类
enhancer.setSuperclass(clazz);
// 标识Spring-generated proxies
enhancer.setInterfaces(new Class[]{SpringProxy.class});
// 设置增强
enhancer.setCallback((MethodInterceptor) (target, method, args, methodProxy) -> {
ToRefresh toRefresh = AnnotationUtils.findAnnotation(method, ToRefresh.class);
if (Objects.isNull(toRefresh) || StringUtils.isBlank(toRefresh.method())) {
return methodProxy.invokeSuper(target, args);
}
Method refreshMethod = ReflectionUtils.findMethod(target.getClass(), toRefresh.method());
if (Objects.isNull(refreshMethod)) {
return methodProxy.invokeSuper(target, args);
}
refreshMethod = BridgeMethodResolver.findBridgedMethod(refreshMethod);
refreshMethod.setAccessible(true);
refreshMethod.invoke(target, null);
return methodProxy.invokeSuper(target, args);
}); T target = (T) enhancer.create();// 创建代理对象 MethodIntrospector.selectMethods(clazz, (ReflectionUtils.MethodFilter) method -> AnnotatedElementUtils.isAnnotated(method, ToInitial.class))
.stream().findFirst().ifPresent(method -> {
method.setAccessible(true);
try {
method.invoke(target, null);
} catch (Exception e) {
LOGGER.error(String.format("初始化异常,class=%s ...", ClassUtils.getSimpleName(clazz)), e);
}
}); return target;
} public static void registerListener(BaseConfigCenterBean refreshableBean) {
eventBus.register(refreshableBean);
} public static class ConfigRefreshEvent {
private String moduleName; public ConfigRefreshEvent(String moduleName) {
this.moduleName = moduleName;
} public String getModuleName() {
return moduleName;
} public void setModuleName(String moduleName) {
this.moduleName = moduleName;
}
}
}
这个工具主要作用:
1、判断配置中心的属性是否发生了变化
2、为BaseConfigCenterBean子类创建代理类,使属性在getter方法时检测属性是否应该刷新。
3、提供将BaseConfigCenterBean类型的对象的注册为guava eventbus的监听对象,使之具有根据刷新事件自动刷新自身属性。
bean后置处理器
public class ConfigCenterBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (AnnotatedElementUtils.isAnnotated(bean.getClass(), ConfigCenterBean.class)) {
BaseConfigCenterBean refreshableBean = (BaseConfigCenterBean) ConfigCenterUtils.createToRefreshPropertiesBean(bean.getClass());
ConfigCenterUtils.registerListener(refreshableBean);
return refreshableBean;
}
return bean;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
该后置处理器的作用是对所有BaseConfigCenterBean类型的bean进行处理,生成代理bean,并注册为guava eventbus相应的listener。
pojo属性绑定配置中心优雅方案1
@ConfigCenterBean
@ConfigurationProperties(prefix = "wx.temporary.qrcode")
@Component
public class QrcodeConstants extends BaseConfigCenterBean { private static Logger LOGGER = LoggerFactory.getLogger(QrcodeConstants.class); //渠道
@ConfigField //标识该属性来自配置中心
private List<Scene> channels; //业务
@ConfigField
private List<Scene> bizs; //业务和渠道映射关系
@ConfigField
private Map<String, String> biz2Channel; private Map<String, Scene> channelMap; private Map<String, Scene> bizMap; public List<Scene> getChannels() {
return channels;
} public void setChannels(List<Scene> channels) {
this.channels = channels;
} public List<Scene> getBizs() {
return bizs;
} public void setBizs(List<Scene> bizs) {
this.bizs = bizs;
} @ToRefresh(method = "toRefresh")
public Map<String, Scene> getChannelMap() {
return channelMap;
} @ToRefresh(method = "toRefresh")
public Map<String, Scene> getBizMap() {
return bizMap;
} @ToRefresh(method = "toRefresh")
public Map<String, String> getBiz2Channel() {
return biz2Channel;
} public void setBiz2Channel(Map<String, String> biz2Channel) {
this.biz2Channel = biz2Channel;
} @ToInitial
private void refreshQrcodeProperties() {
try {
super.doBind(); //属性处理
if (CollectionUtils.isEmpty(channels)) {
this.channelMap = Maps.newHashMap();
} else {
this.channelMap = channels.stream()
.collect(Collectors.toMap(channel -> channel.getType(), Function.identity()));
} if (CollectionUtils.isEmpty(bizs)) {
this.bizMap = Maps.newHashMap();
} else {
this.bizMap = bizs.stream()
.collect(Collectors.toMap(biz -> biz.getType(), Function.identity()));
} LOGGER.info(String.format("%s 刷新成功..., 当前配置=%s...", this.getModuleName(), this));
} catch (Exception e) {
LOGGER.error("QrcodeConstants 对象属性绑定失败...", e);
}
} private void toRefresh() {
try {
if (isCfgCenterEffect()) {
ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();
if (ConfigCenterUtils.propertySourceShouldRefresh(this.getModuleName(), propertySource)) {
this.refreshQrcodeProperties();
}
}
} catch (Exception e) {
LOGGER.error("QrcodeConstants 对象属性刷新失败", e);
}
} //刷新事件调用
@Override
public void refreshForEvent() {
this.refreshQrcodeProperties();
} //本地资源文件
@Override
public String getDefaultResourcePath() {
return "config/qrcode.properties";
} //属性配置 公共前缀(和@ConfigurationProperties prefix 属性一致)
@Override
public String getConfigPrefix() {
return "wx.temporary.qrcode";
} //模块名称
@Override
public String getModuleName() {
return "微信临时二维码配置";
} @Override
public String toString() {
return ReflectionToStringBuilder.toString(this
, ToStringStyle.JSON_STYLE
, false
, false
, QrcodeConstants.class);
} public static class Scene {
private String type;
private String desc; public String getType() {
return type;
} public void setType(String type) {
this.type = type;
} public String getDesc() {
return desc;
} public void setDesc(String desc) {
this.desc = desc;
} @Override
public String toString() {
return ReflectionToStringBuilder.toString(this
, ToStringStyle.JSON_STYLE
, false
, false
, Scene.class);
}
}
}
pojo属性绑定配置中心优雅方案2
@ConfigCenterBean
@Component
public class QrcodeConstants extends BaseConfigCenterBean { private static Logger LOGGER = LoggerFactory.getLogger(QrcodeConstants.class); //业务和渠道映射关系
private Map<String, String> biz2Channel; //渠道
private Map<String, Scene> channelMap; //业务
private Map<String, Scene> bizMap; private QrcodeProperties qrcodeProperties; @ToRefresh(method = "toRefresh")
public Map<String, Scene> getChannelMap() {
return channelMap;
} @ToRefresh(method = "toRefresh")
public Map<String, Scene> getBizMap() {
return bizMap;
} @ToRefresh(method = "toRefresh")
public Map<String, String> getBiz2Channel() {
return biz2Channel;
} public void setBiz2Channel(Map<String, String> biz2Channel) {
this.biz2Channel = biz2Channel;
} public QrcodeProperties getRawQrcodeProperties() {
return qrcodeProperties;
} @ToInitial
private void refreshQrcodeProperties() {
try {
QrcodeProperties qrcodeProperties = super.doBind(QrcodeProperties.class);
if (Objects.isNull(qrcodeProperties)) {
LOGGER.error(String.format("没有加载到%s配置,请检查配置...", this.getModuleName()));
return;
} this.qrcodeProperties = qrcodeProperties; //属性处理
if (CollectionUtils.isEmpty(qrcodeProperties.channels)) {
this.channelMap = Maps.newHashMap();
} else {
this.channelMap = qrcodeProperties.channels.stream()
.collect(Collectors.toMap(channel -> channel.getType(), Function.identity()));
} if (CollectionUtils.isEmpty(qrcodeProperties.bizs)) {
this.bizMap = Maps.newHashMap();
} else {
this.bizMap = qrcodeProperties.bizs.stream()
.collect(Collectors.toMap(biz -> biz.getType(), Function.identity()));
} if (CollectionUtils.isEmpty(qrcodeProperties.getBiz2Channel())) {
this.biz2Channel = Maps.newHashMap();
} else {
this.biz2Channel = qrcodeProperties.getBiz2Channel();
} LOGGER.info(String.format("%s 刷新成功..., 当前配置=%s...", this.getModuleName(), this));
} catch (Exception e) {
LOGGER.error("QrcodeConstants 对象属性绑定失败...", e);
}
} private void toRefresh() {
try {
if (isCfgCenterEffect()) {
ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();
if (ConfigCenterUtils.propertySourceShouldRefresh(this.getModuleName(), propertySource)) {
this.refreshQrcodeProperties();
}
}
} catch (Exception e) {
LOGGER.error("QrcodeConstants 对象属性刷新失败", e);
}
} @Override
public void refreshForEvent() {
this.refreshQrcodeProperties();
} @Override
public String getDefaultResourcePath() {
return "config/qrcode.properties";
} @Override
public String getConfigPrefix() {
return "wx.temporary.qrcode";
} @Override
public String getModuleName() {
return "微信临时二维码配置";
} @Override
public String toString() {
return new ToStringBuilder(this)
.append("biz2Channel", biz2Channel)
.append("channelMap", channelMap)
.append("bizMap", bizMap)
.toString();
} @ConfigurationProperties(prefix = "wx.temporary.qrcode")
public static class QrcodeProperties {
//渠道
private List<Scene> channels; //业务
private List<Scene> bizs; //业务和渠道映射关系
private Map<String, String> biz2Channel; public List<Scene> getChannels() {
return channels;
} public void setChannels(List<Scene> channels) {
this.channels = channels;
} public List<Scene> getBizs() {
return bizs;
} public void setBizs(List<Scene> bizs) {
this.bizs = bizs;
} public Map<String, String> getBiz2Channel() {
return biz2Channel;
} public void setBiz2Channel(Map<String, String> biz2Channel) {
this.biz2Channel = biz2Channel;
}
} public static class Scene {
private String type;
private String desc; public String getType() {
return type;
} public void setType(String type) {
this.type = type;
} public String getDesc() {
return desc;
} public void setDesc(String desc) {
this.desc = desc;
} @Override
public String toString() {
return ReflectionToStringBuilder.toString(this
, ToStringStyle.JSON_STYLE
, false
, false
, Scene.class);
}
}
}
方案1和方案2略有不同,针对一些属性,我们需要做一些逻辑处理。方案1中将源属性和逻辑之后的属性都放在了同一类中,方案二则是将源属性单独放到一个静态类中,最终处理过后的属性放在了目标类中。另外二者的doBind方法也是有区别的,仔细看一下BaseConfigCenterBean这个类就可以了。
就先分享这么多了,更多分享请关注我们的技术公众吧!!!
参考文章:算法和技术SHARING
依赖配置中心实现注有@ConfigurationProperties的bean相关属性刷新的更多相关文章
- Spring Boot 2.0 整合携程Apollo配置中心
原文:https://www.jianshu.com/p/23d695af7e80 Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够 ...
- SpringCloud学习系列之五-----配置中心(Config)和消息总线(Bus)完美使用版
前言 在上篇中介绍了SpringCloud Config的使用,本篇则介绍基于SpringCloud(基于SpringBoot2.x,.SpringCloud Finchley版)中的分布式配置中心( ...
- SpringCloud学习系列之四-----配置中心(Config)使用详解
前言 本篇主要介绍的是SpringCloud中的分布式配置中心(SpringCloud Config)的相关使用教程. SpringCloud Config Config 介绍 Spring Clou ...
- 基于zookeeper实现分布式配置中心(二)
上一篇(基于zookeeper实现分布式配置中心(一))讲述了zookeeper相关概念和工作原理.接下来根据zookeeper的特性,简单实现一个分布式配置中心. 配置中心的优势 1.各环境配置集中 ...
- Spring Cloud第十篇 | 分布式配置中心Config
本文是Spring Cloud专栏的第十篇文章,了解前九篇文章内容有助于更好的理解本文: Spring Cloud第一篇 | Spring Cloud前言及其常用组件介绍概览 Spring Clo ...
- spring cloud 入门系列七:基于Git存储的分布式配置中心
我们前面接触到的spring cloud组件都是基于Netflix的组件进行实现的,这次我们来看下spring cloud 团队自己创建的一个全新项目:Spring Cloud Config.它用来为 ...
- Spring Cloud(Dalston.SR5)--Config 集群配置中心
Spring Cloud Config 是一个全新的项目,用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,他分为服务端和客户端两个部分.服务端也称为分布式配置中心,是一个独立的微服务 ...
- spring boot 2.0.3+spring cloud (Finchley)6、配置中心Spring Cloud Config
https://www.cnblogs.com/cralor/p/9239976.html Spring Cloud Config 是用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持, ...
- spring cloud 入门系列七:基于Git存储的分布式配置中心--Spring Cloud Config
我们前面接触到的spring cloud组件都是基于Netflix的组件进行实现的,这次我们来看下spring cloud 团队自己创建的一个全新项目:Spring Cloud Config.它用来为 ...
随机推荐
- Confluence 6 其他 MBeans 和高 CPU 消耗线程
其他 MBeans 希望监控 Hibernate 和 Hazelcast(仅针对 Confluence 数据中心)你需要在你的 setenv.sh / setenv.bat 文件中添加下面的内容. s ...
- gulp前端工程化教程
gulp npm install -g gulp-concat 文件打包 npm install -g gulp-rename 文件重命名 npm install -g gulp-imagemin 图 ...
- Spark Streaming 实现思路与模块概述
一.基于 Spark 做 Spark Streaming 的思路 Spark Streaming 与 Spark Core 的关系可以用下面的经典部件图来表述: 在本节,我们先探讨一下基于 Spark ...
- TabLayout和ViewPager
这里就说下tablayout+viewpager的实现方式:tablayout是android5.0推出来的一个MaterialDesign风格的控件,是专门用来实现tab栏效果的:功能强大,使用方便 ...
- JPA整合Spring案例
目录 Spring-SpringMVC-JPA整合案例 三种整合方式 Spring整合JPA步骤 解决JPA懒加载问题 Spring-SpringMVC-JPA整合案例 author :SimpleW ...
- MySQL数据库之安装
一.基础部分 1.数据库是什么 之前所学,数据要永久保存,比如用户注册的用户信息,都是保存于文件中,而文件只能存在于某一台机器上. 如果我们不考虑从文件中读取数据的效率问题,并且假设我们的程序所有的组 ...
- 对于stark(curd)插件的使用简单介绍
一.创建表 from django.db import models from django.db import models class Department(models.Model): &quo ...
- 小学生都看得懂的C语言入门(6): 字符串
1.字符用 char 表示 #include<stdio.h> int main() { char c; char d; c=; d='; if (c==d){ printf(" ...
- bzoj 1222
比较简单的背包dp,设计状态f[i][j]表示到了前i个物品,第一台机器加工时间为j,第二台机器加工所用的最小时间,然后背包转移即可 本题卡空间,需要滚动数组优化 本题卡时间,稍微卡下常就行 #inc ...
- Nginx详解十五:Nginx场景实践篇之负载均衡
负载均衡 GSLB(全局的负载均衡,往往是以国家为单位,或者以省为单位) SLB Nginx就是一个典型的SLB模型, 分为四层负载均衡和七层负载均衡 七层负载均衡可以处理应用层,如thhp信息,Ng ...