日常开发写SQL的时候,尽量养成这个好习惯:写完SQL后,分析一下,尤其注意走不走索引。
-- 分析方式 -- 1. explain explain select userid,name,age from user where userid =10086 or age =18;-- 2. descdesc select userid,name,age from user where userid =10086 or age =18;-- 3. desc format=json desc format=json select userid,name,age from user where userid =10086 or age =18;-- 4. desc analyzedesc analyze select userid,name,age from user where userid =10086 or age =18;
注意:尤其是范围删除
delete from user where age > 30 limit 200;
CREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id', `name` varchar(255) DEFAULT NULL COMMENT '账户名', `balance` int(11) DEFAULT NULL COMMENT '余额', `create_time` datetime NOT NULL COMMENT '创建时间', `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_name` (`name`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT COMMENT='账户表';
SELECT stu.name, sum(stu.score) FROM Student stu WHERE stu.classNo = '1班' GROUP BY stu.name
-- 提倡insert into Student values ('666','小孩子真可爱','100');-- 不提倡insert into Student(student_id,name,score) values ('666','小孩子真可爱','100');
CREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id', `name` varchar(255) DEFAULT NULL COMMENT '账户名', `balance` int(11) DEFAULT NULL COMMENT '余额', `create_time` datetime NOT NULL COMMENT '创建时间', `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_name` (`name`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT COMMENT='账户表';
当一个属性类型为字符串
// userid 是varchar字符串类型 // 错误select * from user where userid = 123;// 正确select * from user where userid = '123';
因为不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会做隐式的类型转换,把它们转换为浮点数再做比较,最后导致索引失效
// 错误delete from account limit 100000;// 正例for each(200次) { delete from account limit 500; }
Gosper's Hack 是一种生成 N 元集合所有 K 元子集的算法,它巧妙地利用了位运算。
假如把所有符合要求的二进制数 排序 后得到的数组是 S,通过任意给定的 Si,都可以计算出 Si+1。这样就可以递推出所有可能得二进制数。
而这其实很容易实现:我们只需要把最后一个 01 变成 10 ,然后把它右边的 1 全部集中到最右边即可。例如, 00111→01011 , 10110→11001 。
示例,求 0 ~ 128 之间所有二进制表示中1的数量为4的集合:
// 满足条件的数在以下区间// 初始数 00001111// 末尾数 11110000// 根据上述思想可以看出, 最开始从 00001111 递推到 11110000 即可获得所有符合要求的结果集
// 计算初始值int subset = (1 << numSelect) - 1; while (subset < (1 << n)) { // 总体可以分成 两个部分,一个左边,一个右边// 计算 lowbit int lb = subset & -subset; // 左边 部分计算 // subset + lb 达到的效果就是 将 最右边的 01 变成 10 int x = subset + lb; // (subset ^ x) / lb >> 2 右边部分公式。 也就是将 左边部分 之后的 1 全部集中到 最右边。右移 两位 是把 左边部分改变的两位影响 去除 // | x 将两个 数 的 1 合并起来,不使用 + 避免影响 1 的位置 subset = ((subset ^ x) / lb >> 2) | x; }
leetcode: https://leetcode.cn/problems/maximum-rows-covered-by-columns/
lowbit ( n ) 定义为非负整数 n 在二进制表示下 “ 最低位的 1 及其后面的所有的 0 ” 的二进制构成的数值。
int lb = subset & -subset;
]]>
本文所呈现的问题,是实际编码中出现的,只是删除和精简了一部分,但可以呈现出问题。
参考文章: https://yhsblog.cn/archives/ji-yi-ci-stream-bao-cuo-dao-zhi-de-dui-zhan-yi-chu
在执行以下代码时,抛出 java.lang.StackOverflowError 异常。
Map<Long, Map<String, Post>> pageItemMap = pages.stream().collect(Collectors.toMap(e -> e.getDeptId(), e -> e.getPostList().stream().collect(Collectors.toMap(t -> t.getPostName(), Function.identity(), (v1, v2) -> v2))));
Exception in thread "main" java.lang.StackOverflowErrorat com.StreamStackTest2$Dept.hashCode(StreamStackTest2.java:58)at com.StreamStackTest2$Post.hashCode(StreamStackTest2.java:74)at java.util.AbstractList.hashCode(AbstractList.java:541)at com.StreamStackTest2$Dept.hashCode(StreamStackTest2.java:58)at com.StreamStackTest2$Post.hashCode(StreamStackTest2.java:74)at java.util.AbstractList.hashCode(AbstractList.java:541)at com.StreamStackTest2$Dept.hashCode(StreamStackTest2.java:58)at com.StreamStackTest2$Post.hashCode(StreamStackTest2.java:74)at java.util.AbstractList.hashCode(AbstractList.java:541)at com.StreamStackTest2$Dept.hashCode(StreamStackTest2.java:58)at com.StreamStackTest2$Post.hashCode(StreamStackTest2.java:74)
// u -> map merge 时的 oldValue// v -> map merge 时的 newValue private static <T> BinaryOperator<T> throwingMerger() { return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); }; }
// 在 输出的时候 会 首先调用 super.toString()public String toString() { return "StreamStackTest2.Dept(super=" + super.toString() + ", deptId=" + this.getDeptId() + ", deptName=" + this.getDeptName() + ", postList=" + this.getPostList() + ")"; }// callSuper=false 的情况public String toString() { return "StreamStackTest2.Dept(deptId=" + this.getDeptId() + ", deptName=" + this.getDeptName() + ", postList=" + this.getPostList() + ")"; }
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; public class StreamStackTest2 { public static void main(String[] args) { List<Dept> pages = new ArrayList<>(); ArrayList<Post> postList1 = new ArrayList<>(); Dept dept1 = new Dept(1L, "山西省防空部门", postList1); ArrayList<Post> postList2 = new ArrayList<>(); Dept dept2 = new Dept(2L, "山西省消防部门", postList2); Post post1 = new Post(1L, "机长", dept1); Post post2 = new Post(2L, "乘务员", dept1); postList1.add(post1); postList1.add(post2); Post post3 = new Post(3L, "消防员", dept2); Post post4 = new Post(4L, "消防员", dept2); postList2.add(post3); postList2.add(post4); pages.add(dept1); pages.add(dept2); System.out.println("数据组装完成"); Map<Long, Map<String, Post>> pageItemMap = pages.stream().collect(Collectors.toMap(e -> e.getDeptId(), e -> e.getPostList().stream().collect(Collectors.toMap(t -> t.getPostName(), Function.identity())))); System.out.println(pageItemMap); } private static class Parent { @Override public String toString() { return super.toString(); } } @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) private static class Dept extends Parent { private Long deptId; private String deptName; private List<Post> postList; public Dept(Long deptId, String deptName, List<Post> postList) { this.deptId = deptId; this.deptName = deptName; this.postList = postList; } } @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) private static class Post extends Parent { private Long postId; private String postName; private Dept dept; public Post(Long postId, String postName, Dept dept) { this.postId = postId; this.postName = postName; this.dept = dept; } } }
]]>
社会的压力是大众对于成功的定义。没有车、没有房、没有钱,即使过得很开心,即使你能解决温饱,但你不是成功的。你不会是别人口中用来攀比的对象(也许会成为挤兑的那个),也不是大众眼中的显眼包。你默默无闻,政府不会给你低保,社会不会给你正眼。唯一能证明你还在这个社会上存在,只有社保的状态还是启用。
家人的压力更是来自于 钱、权、车、房。就像《人世间》电视剧中那样,混得好的孩子永远都是被偏爱的那个,混得不好的 只是饭后谈笑的对象。但每个孩子的感受和痛楚与 周秉昆 一样:差就应该被说吗?混得差是事实,但事实就该说吗? 从小听的最多的就是“这是为你好”,“你要好好学习”,”不敢做这啊,不敢做那啊“。将你的错误、糗事围着一大桌,说出你的不好,指出你的错误,美名都是为了让你变得更好,有什么不该说。一切的一切都是事实。
自己的压力无可厚非的就是迎合家人的期望。生活小心翼翼,如履薄冰。不敢犯一丝一毫的错误,只能一头扎进赚钱的深坑,不断地给自己施加压力。人的精力是有限的,资质也是有限的。不是所有人都是天才。人能赚到的钱都是自己认知以内的。盲目的羡慕别人,只会让自己背负着大山前行。累,且没有结果。
有一个亲身经历。从我记事起,我的父亲就是一个很孝顺的人。但他不像我的叔叔伯伯,有的是医生,有的是正式工人,有的是公务员。他逢年过节都会给爷爷奶奶去送礼物,让我无法释怀的是,叔叔伯伯的礼物爷爷奶奶会很坦然的收下,但是父亲的礼物,每一次都会加上一句:你不用买这,你也没有钱。这话的效果和《人世间》中父亲的效果一样。作为混的不好的孩子,一生的追求都是父母的认可。
有一个好的物质生活是幸福的前提。但没有 豪宅、名车、年入百万,也同样没有犯罪。茅草屋顶下住着自由的人,大理石和黄金下栖息这奴隶。这句话我们不能百分百的赞同,如果不明白过分追求物质,过分贪婪所带来的危害,那也是身患流行性物欲病。
我时常在想,桃花源在什么地方?什么时候能够到达?在那里,简单的生活不被视为一种堕落。在那里,可以坐在时间的溪水里垂钓天上的星星,不必终日奔波风尘。坐看寒来暑往,笑谈四季消长。
压力,无时无刻。学会释放自己,学会善待自己。
2023年,我依旧没有买房子,没有买车子。我也会嫉妒别人,也会受到家人的督促。我不再那么不开心,一只耳朵进,一只耳朵出。健康快乐才应该是不变的追求。
]]>curl -L -w "time_namelookup: %{time_namelookup}\ntime_connect: %{time_connect}\ntime_appconnect: %{time_appconnect}\ntime_pretransfer: %{time_pretransfer}\ntime_redirect: %{time_redirect}\ntime_starttransfer: %{time_starttransfer}\ntime_total: %{time_total}\n" https://www.baidu.com/
# 新建 一个文本 format.txt 并填入以下内容# 常用一\ntime_namelookup: %{time_namelookup}\ntime_connect: %{time_connect}\ntime_appconnect: %{time_appconnect}\ntime_pretransfer: %{time_pretransfer}\ntime_redirect: %{time_redirect}\ntime_starttransfer: %{time_starttransfer}\ntime_total: %{time_total}\n# 常用二\nResponse Time for: %{url_effective}\n\nDNS Lookup Time:\t\t%{time_namelookup}s\nRedirection Time:\t\t%{time_redirect}s\nConnection Time:\t\t%{time_connect}s\nApp Connection Time:\t\t%{time_appconnect}s\nPre-transfer Time:\t\t%{time_pretransfer}s\nStart-transfer Time:\t\t%{time_starttransfer}s\n\nTotal Time:\t\t\t%{time_total}s\n
curl 提供了很多置换变量,可以在格式化字符串中通过
%{var}
的形式使用。
完整的可以查看 curl 的 manpage 。
链接: https://www.man7.org/linux/man-pages/man1/curl.1.html
介绍几个比较常用的:
# 请求百度的最终结果 # 此处展示完整结果,请忽略 HTML 部分<!DOCTYPE html><!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>'); </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a> 京ICP证030173号 <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>time_namelookup: 0.029time_connect: 0.040time_appconnect: 0.304time_pretransfer: 0.304time_redirect: 0.000time_starttransfer: 0.317time_total: 0.317
注意:上述 time_redirect 指标为 0,说明本次访问没有发生重定向。
当碰见 web 服务器的延迟问题时,可以选择在 client 上执行这个命令,能够快速了解导致延迟是哪一个层面的问题。
]]>javax.annotation.Resource 归属 Java 源码包,主流的服务容器都支持使用它进行依赖注入。
再次进行 spring 源码解析,进一步加深理解。
@Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { // 查询注入点 InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs); try { // 完成注入 metadata.inject(bean, beanName, pvs); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex); } return pvs; }
private InjectionMetadata findResourceMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) { // Fall back to class name as cache key, for backwards compatibility with custom callers. String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); // Quick check on the concurrent map first, with minimal locking. // 先查看缓存是否存在 InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); // 判断是否为空 或者 是否是 Class 对象 if (InjectionMetadata.needsRefresh(metadata, clazz)) { synchronized (this.injectionMetadataCache) { metadata = this.injectionMetadataCache.get(cacheKey); if (InjectionMetadata.needsRefresh(metadata, clazz)) { if (metadata != null) { metadata.clear(pvs); } // 查询并构建 注入点 metadata = buildResourceMetadata(clazz); // 加入缓存 this.injectionMetadataCache.put(cacheKey, metadata); } } } return metadata; }
private InjectionMetadata buildResourceMetadata(Class<?> clazz) { if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) { return InjectionMetadata.EMPTY; } List<InjectionMetadata.InjectedElement> elements = new ArrayList<>(); Class<?> targetClass = clazz; do { final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>(); // 属性 依据不同的注解 解析 并构建 InjectedElement 对象 ReflectionUtils.doWithLocalFields(targetClass, field -> { if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields"); } currElements.add(new WebServiceRefElement(field, field, null)); } else if (ejbClass != null && field.isAnnotationPresent(ejbClass)) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("@EJB annotation is not supported on static fields"); } currElements.add(new EjbRefElement(field, field, null)); } else if (field.isAnnotationPresent(Resource.class)) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("@Resource annotation is not supported on static fields"); } if (!this.ignoredResourceTypes.contains(field.getType().getName())) { currElements.add(new ResourceElement(field, field, null)); } } }); // 方法 依据不同的注解 解析 并构建 InjectedElement 对象 ReflectionUtils.doWithLocalMethods(targetClass, method -> { Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { return; } if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) { if (Modifier.isStatic(method.getModifiers())) { throw new IllegalStateException("@WebServiceRef annotation is not supported on static methods"); } if (method.getParameterCount() != 1) { throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method); } PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); currElements.add(new WebServiceRefElement(method, bridgedMethod, pd)); } else if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) { if (Modifier.isStatic(method.getModifiers())) { throw new IllegalStateException("@EJB annotation is not supported on static methods"); } if (method.getParameterCount() != 1) { throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method); } PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); currElements.add(new EjbRefElement(method, bridgedMethod, pd)); } else if (bridgedMethod.isAnnotationPresent(Resource.class)) { if (Modifier.isStatic(method.getModifiers())) { throw new IllegalStateException("@Resource annotation is not supported on static methods"); } Class<?>[] paramTypes = method.getParameterTypes(); if (paramTypes.length != 1) { throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method); } if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) { PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); currElements.add(new ResourceElement(method, bridgedMethod, pd)); } } } }); elements.addAll(0, currElements); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); return InjectionMetadata.forElements(elements, clazz); }
此处只关注 对于 @Resource 注解的解析。
public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) { super(member, pd); Resource resource = ae.getAnnotation(Resource.class); String resourceName = resource.name(); Class<?> resourceType = resource.type(); // 查看 Resource 注解中是否指定了 name this.isDefaultName = !StringUtils.hasLength(resourceName); if (this.isDefaultName) { resourceName = this.member.getName(); // 判断 是否是 标注在 方法上 if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) { resourceName = Introspector.decapitalize(resourceName.substring(3)); } } else if (embeddedValueResolver != null) { resourceName = embeddedValueResolver.resolveStringValue(resourceName); } if (Object.class != resourceType) { checkResourceType(resourceType); } else { // No resource type specified... check field/method. resourceType = getResourceType(); } this.name = (resourceName != null ? resourceName : ""); this.lookupType = resourceType; String lookupValue = resource.lookup(); this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName()); Lazy lazy = ae.getAnnotation(Lazy.class); this.lazyLookup = (lazy != null && lazy.value()); }
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Collection<InjectedElement> checkedElements = this.checkedElements; Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) { for (InjectedElement element : elementsToIterate) { // 调用上一步 构建的元素的 注入方法 element.inject(target, beanName, pvs); } } }protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs) throws Throwable { // 判断是否是属性,如果是,则反射赋值 if (this.isField) { Field field = (Field) this.member; ReflectionUtils.makeAccessible(field); field.set(target, getResourceToInject(target, requestingBeanName)); } else { // 检查是否需要跳过该属性,并且 标记已经处理 if (checkPropertySkipping(pvs)) { return; } try { // 执行方法进行赋值 Method method = (Method) this.member; ReflectionUtils.makeAccessible(method); method.invoke(target, getResourceToInject(target, requestingBeanName)); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } } }
此处不进行懒加载分析
@Override protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) { return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) : getResource(this, requestingBeanName)); }// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#getResourceprotected Object getResource(LookupElement element, @Nullable String requestingBeanName) throws NoSuchBeanDefinitionException { // JNDI lookup to perform? String jndiName = null; if (StringUtils.hasLength(element.mappedName)) { jndiName = element.mappedName; } else if (this.alwaysUseJndiLookup) { jndiName = element.name; } if (jndiName != null) { if (this.jndiFactory == null) { throw new NoSuchBeanDefinitionException(element.lookupType, "No JNDI factory configured - specify the 'jndiFactory' property"); } return this.jndiFactory.getBean(jndiName, element.lookupType); } // Regular resource autowiring if (this.resourceFactory == null) { throw new NoSuchBeanDefinitionException(element.lookupType, "No resource factory configured - specify the 'resourceFactory' property"); } // 最后会走到这里 // this.resourceFactory 默认情况下为 org.springframework.beans.factory.support.DefaultListableBeanFactory return autowireResource(this.resourceFactory, element, requestingBeanName); }// org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#autowireResourceprotected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName) throws NoSuchBeanDefinitionException { Object resource; Set<String> autowiredBeanNames; String name = element.name; if (factory instanceof AutowireCapableBeanFactory) { AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory; DependencyDescriptor descriptor = element.getDependencyDescriptor(); // 判断 是否通过类型匹配,名称匹配,是否已经创建 if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) { // 该类型的所有名称 autowiredBeanNames = new LinkedHashSet<>(); // 解析获取 符合注入条件的对象 // 注解中无特殊指定,会走到这里 resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null); if (resource == null) { throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object"); } } else { // 如果在 @Resource 注解中指定了 name,则只会根据 名称注入 resource = beanFactory.resolveBeanByName(name, descriptor); autowiredBeanNames = Collections.singleton(name); } } else { resource = factory.getBean(name, element.lookupType); autowiredBeanNames = Collections.singleton(name); } if (factory instanceof ConfigurableBeanFactory) { ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory; for (String autowiredBeanName : autowiredBeanNames) { if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) { beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName); } } } return resource; }
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); if (Optional.class == descriptor.getDependencyType()) { return createOptionalDependency(descriptor, requestingBeanName); } else if (ObjectFactory.class == descriptor.getDependencyType() || ObjectProvider.class == descriptor.getDependencyType()) { return new DependencyObjectProvider(descriptor, requestingBeanName); } else if (javaxInjectProviderClass == descriptor.getDependencyType()) { return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName); } else { Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); if (result == null) { // 执行真正的方法 result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; } }// org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency@Nullable public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); try { Object shortcut = descriptor.resolveShortcut(this); if (shortcut != null) { return shortcut; } Class<?> type = descriptor.getDependencyType(); Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String) { String strVal = resolveEmbeddedValue((String) value); BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); value = evaluateBeanDefinitionString(strVal, bd); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); try { return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor()); } catch (UnsupportedOperationException ex) { // A custom TypeConverter which does not support TypeDescriptor resolution... return (descriptor.getField() != null ? converter.convertIfNecessary(value, type, descriptor.getField()) : converter.convertIfNecessary(value, type, descriptor.getMethodParameter())); } } Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter); if (multipleBeans != null) { return multipleBeans; } // 根据 类型和 名称查找符合条件的对象 Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { if (isRequired(descriptor)) { raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); } return null; } String autowiredBeanName; Object instanceCandidate; // 查找到多个符合条件的 进行下一步判断 如 @Primary 和 优先级 @Order 等注解 if (matchingBeans.size() > 1) { autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (autowiredBeanName == null) { if (isRequired(descriptor) || !indicatesMultipleBeans(type)) { return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans); } // 无法确定明确对象的返回 null else { // In case of an optional Collection/Map, silently ignore a non-unique case: // possibly it was meant to be an empty collection of multiple regular beans // (before 4.3 in particular when we didn't even look for collection beans). return null; } } instanceCandidate = matchingBeans.get(autowiredBeanName); } else { // 只有 一个符合的 记录名称 和 对象 // We have exactly one match. Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); autowiredBeanName = entry.getKey(); instanceCandidate = entry.getValue(); } if (autowiredBeanNames != null) { autowiredBeanNames.add(autowiredBeanName); } if (instanceCandidate instanceof Class) { instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); } Object result = instanceCandidate; if (result instanceof NullBean) { if (isRequired(descriptor)) { raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); } result = null; } if (!ClassUtils.isAssignableValue(type, result)) { throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass()); } return result; } finally { ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); } }
protected Map<String, Object> findAutowireCandidates( @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) { // 获取该类型 所有的名称 String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this, requiredType, true, descriptor.isEager()); Map<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length); for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) { Class<?> autowiringType = classObjectEntry.getKey(); if (autowiringType.isAssignableFrom(requiredType)) { Object autowiringValue = classObjectEntry.getValue(); autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType); if (requiredType.isInstance(autowiringValue)) { result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue); break; } } } // 判断是否是自引用 是否符合 // isAutowireCandidate -> Determine whether the specified bean definition qualifies as an autowire candidate, to be injected into other beans which declare a dependency of matching type. 确定指定的bean定义是否有资格作为自动候选对象,以注入声明匹配类型依赖的其他bean中。 for (String candidate : candidateNames) { if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) { addCandidateEntry(result, candidate, descriptor, requiredType); } } // 第一轮没有找到符合的对象,在进行额外的处理 if (result.isEmpty()) { boolean multiple = indicatesMultipleBeans(requiredType); // Consider fallback matches if the first pass failed to find anything... DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch(); for (String candidate : candidateNames) { if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) && (!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) { addCandidateEntry(result, candidate, descriptor, requiredType); } } if (result.isEmpty() && !multiple) { // Consider self references as a final pass... // but in the case of a dependency collection, not the very same bean itself. for (String candidate : candidateNames) { if (isSelfReference(beanName, candidate) && (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) && isAutowireCandidate(candidate, fallbackDescriptor)) { addCandidateEntry(result, candidate, descriptor, requiredType); } } } } return result; }
在工作中避免不了需要从别的系统(本公司、或者别的数据平台)获取数据。在自己的项目中需要发送 Http 请求。这就引申出一个问题,如何优雅的去发送这些 http 请求?
此处使用 org.apache.httpcomponents:httpclient 作为发送 http 的工具。
默认以 tomcat 为部署项目的容器
每当我们有需要请求别的平台的需求时,我们在自己项目中新建一个接口,自己做转发。别的平台需要什么参数,我们的系统就需要定义对应的参数接收。
// org.apache.http.client.HttpClient#execute(org.apache.http.HttpHost, org.apache.http.HttpRequest)@PostMapping("/send")public Object sendRequest(Object requestParam){httpClient = createHttpClient();return httpClient.execute(URIUtils.extractHost(new URI(targetUri)),new BasicHttpRequest(method, targetUri));}
上述只是极其简单的一种方式,真是的场景中我们会做点别的事情。比如:记录请求日志,或者添加固定的 Headers。
使用一种类似代理的方式封装起来。具体的实现可以参考下面的开源项目。
<groupId>org.mitre.dsmiley.httpproxy</groupId> <artifactId>smiley-http-proxy-servlet</artifactId> <version>1.12.1</version><!-- https://github.com/dsmiley/HTTP-Proxy-Servlet -->
简单阐述一下思路。以 springboot 为例,默认会启动 DispatcherServlet 作为寻找我们编写的 controller 接口的入口。 它的 urlMapping 如果不进行特殊配置,默认会是 "/"。
这里的方式其实是 自己另外在 spring 容器中注册一个 httpServlet ,并且封装一些对外的请求和响应方法。urlMapping 设置为自己想要请求的第三方 比如:"/baidu/**"。
不像第二种方式那样,自己重新注册一个 servlet ,而是自己提供一个 controller ,将要请求的地址作为参数。和第一种差不多,进行了一次统一得封装。
第二种方式对于自己项目来说更加简单,明面上是请求自己的系统,其实和直接去请求第三方系统一样,通常对这样的方式进行拦截和验证都比较麻烦,而且这里和第三方的交互毕传参数会抛出给对接系统,造成同一份配置 维护在多处,改动成本比较大。
第一种的话又需要编写大量的代码,耗时耗力。
第三种更综合一些,拦截方便,而且代码量也不会很大,但是需要很好的封装技巧。
仅以个人拙见,叙述自己了解到的方式,如有更好的方式,欢迎交流。
]]>整篇小说以一位90多岁的老人自述的口吻展开。耄耋之年,世间的事早已看的通透。娓娓道来的那些事,有生离有死别有重逢有分开。但每一句都透露着生活,让人感觉无比真实的生活。生活从来都不是天堂,明天和意外,如意不如意 皆有可能。
整部小说以 清晨、正午、黄昏、尾声 为线索。对应这人的一生,也是整个 鄂温克族 的缩影。借用小人物的口,讲述宏大苍凉的历史。读完小说最大的疑问就是 主人公 的名字叫什么?
清晨,是一天之中最有活力的时候。缓缓升起的太阳,逐渐温暖的大地,森林一片一片的披上金色的外衣。鄂温克族的居住地矗立着一座座 希楞柱 。营地中有孩童,有壮年,有优秀的猎手,也有手工活优秀的女人。小小营地生机勃勃。最喜欢的书中的一句话:没有孩子的家庭总是显得没有活力。此话一点 都不假。现在的社会到处是,生孩子养孩子多么多么累,宣传孩子所带来的欢乐却越来越少。我曾和一位两个孩子的父亲沟通过,他说,虽然有时候确实累,也确实麻烦,但是他们带来的欢乐总体上是要更多的,幸福的时候就觉得做什么都值了。
正午,是一天当中最炙热的时间。对应人的一生,也是一个人最青春,身体机能最强,情感迸发最为强烈的时间段。这时间段内会寻找自己的伴侣,和伴侣在 希楞柱中 制造即温柔又狂烈的风,诞下最漂亮的孩子,赐予他世上最动听的名字。所有的悲伤都被拥有的美好驱逐。父亲离世,也遇到了与父亲一样的男子。福兮祸所依,祸兮福所倚。日子一天一天的过,不会因为你的悲伤选择跳过,时间才是最好的橡皮,无论什么最后都会擦得干干净净。
黄昏,也许很美,但是多了一份凄凉。身边的人都离开了,独自守着从母亲传承而来的火。到黄昏的时候才会想起那么多的人都离去了。死亡这件事真的贯穿全书。不是所有的死亡都轰轰烈烈,有的只是平平淡淡。就像书中所说 :我已经说了太多太多死亡的故事,这是没办法的事情。因为每个人都会死亡。人们出生是大同小异的,死亡却是各有各的走法 。活着的人所能做的就是接受他们的离去,并更加珍惜自己,努力生活。
尾声,皎洁的月亮多么像雪白的驯鹿。撒在地上的雪白,恍若梦中仙境。耳畔的鹿铃声,声声依旧。
回忆整篇小说之后,我之拙见:生活本就平凡。死亡,新生 本就是世间再平凡不过。接受平凡,才是最应该上的一堂课。
我们生活在24小时不停歇的社会,却没有24小时不休息的身体,所以必须学会放弃。
努力的生活。
]]>我不知道为什么会消失,到今天我依然会不信邪一般的打开他的网站,只能说一句可惜。节奏越来越快的社会也许阅读的人越来越少,这种类型的网站运维的成本过高,是人都是要吃饭。观察过每天早上的地铁,大多数人一路看手机,偷偷瞄过去,基本都是抖音、快手等等短视频。确实一段视频对人的吸引力要比一段文字的吸引力大得多。但是短视频给人带来的震撼与影响远远不如一段文字。比如:未若柳絮因风起。读到这句的时候,脑海里自然而然能带出一段美妙的景。但是看到一段视频的时候却不一定能想到这么美的诗句。
每日一文消失后,我也找了很多类似的软件。如:岛读、小独,飞地等。最终还是选择了小独(溺与安宁,独伴时光)。每天一首歌,一句话,一篇美文。有一天他也出现过 服务器错误 的提示,我曾写信联系过官方。有点害怕他又突然的消失,让我在地铁上的时光又恢复无聊。每天如果读到一篇长文,整个地铁上的时光匆匆;如果读的是一篇诗,那我还有时间听一听他推荐的歌曲。歌曲悠悠,心情美美。
我并不是一个爱读书的人,但有段时间我非常非常想写点什么,把脑海里所想所思写下来,我发现提笔一片空白。用词,造句甚至不如一个小学生,写出的字狗爬似的。一瞬间我惶恐,曾经我也是会写文章,会写诗,妻子也是间接通过我写的诗才走到一起。我开始思考,我怎么了?我是一名程序员,长久以来都是用电脑,用笔的时间早已在我记忆中遗忘。这不对。我那么喜欢苏轼,如何能不会文绉绉几句呢?怎么能不会写字呢?也有过大文豪的梦想,只是天赋所限,脑海中的美好只能化为笔下生硬的语调。我不想晨起侍花,闲来煮茶,午后读书就此成为泡影。于是,开始强迫自己读书,并不拘泥于一种风格的书,有的书是从短视频推荐过来的(此时对短视频的好感大大提升),有的是京东或者当当 销售量第一,也有的是别人推荐,朋友赠送。也坚持写写,没思路的时候就摘抄一些觉得好的、感人的、有格调的 作为自己随笔。大半年来,也小有成就,读了七八本书,随笔也有很多,具体数量有待考究。不能说完全恢复手感,百字短文感想还是可以的。
每天坚持读点,也许转眼就想不起来。第二次读到的时候却油然而生一种我读过的感觉,记忆从犄角旮旯里钻出来。真奇妙。
无法成为文坛大家,也要尽力做 “今宵有酒今宵醉,明日愁来明日忧” 的打油诗家。
感谢每日一文,感谢小独。
]]>HikariCP是一个“零开销”的 JDBC 连接池。快速、简单、可靠 是它的特性
-- todo
数据库连接池已经发展了很久了,也算是比较成熟的技术,使用比较广泛的类库有 DBCP、C3P0、Druid 等等。眼看着数据库连接池已经发展到了瓶颈,所谓的性能提升也仅仅是一些代码细节的优化,HikariCP 出现后并快速地火了起来,与其他连接池相比,它的快不是普通的快,而是跨越性的快。
首先是模型上的变化,要知道对连接池的操作不外乎四个:borrow、requite、add、remove。
之前的数据库都是 “实做模型”,从抽象层面讲,它非常符合我们的现实生活,例如,某人借走我的钱,钱就不在我的钱包里了。我们熟知的 DBCP、C3P0、Druid 等等都是这样。
但是 HikariCP 就不一样了,在“实做模型”中,borrow、return、add、remove 四个动作都需要加同一把锁,即同一时刻只允许一个线程操作池,并发高时线程切换将非常频繁。因为多个线程操作同一个池塘,连接出入池需要加锁来保证线程安全。
HikariCP 是这样做的,borrow 的连接不会从池塘里取出,而是打上“已借出”的标记,return 的时候,再把这个连接的“已借出”标记去掉。可以把这种做法称为“标记模型”。“标记模型”可以实现 borrow 和 return 动作不加锁。具体怎么做到的呢?
先看 borrow 时的操作:
// 连接池中保存 连接 的集合private final CopyOnWriteArrayList<T> sharedList;
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException { // Try the thread-local list first final var list = threadList.get(); for (int i = list.size() - 1; i >= 0; i--) { final var entry = list.remove(i); @SuppressWarnings("unchecked") final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry; if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { return bagEntry; } } // Otherwise, scan the shared list ... then poll the handoff queue final int waiting = waiters.incrementAndGet(); try { for (T bagEntry : sharedList) { if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { // If we may have stolen another waiter's connection, request another bag add. if (waiting > 1) { listener.addBagItem(waiting - 1); } return bagEntry; } } listener.addBagItem(waiting); timeout = timeUnit.toNanos(timeout); do { final var start = currentTime(); final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS); if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) { return bagEntry; } timeout -= elapsedNanos(start); } while (timeout > 10_000); return null; } finally { waiters.decrementAndGet(); } }
public void requite(final T bagEntry) { bagEntry.setState(STATE_NOT_IN_USE); for (var i = 0; waiters.get() > 0; i++) { if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) { return; } else if ((i & 0xff) == 0xff) { parkNanos(MICROSECONDS.toNanos(10)); } else { Thread.yield(); } } final var threadLocalList = threadList.get(); if (threadLocalList.size() < 50) { threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry); } }
池中不存在的
public void add(final T bagEntry) { if (closed) { LOGGER.info("ConcurrentBag has been closed, ignoring add()"); throw new IllegalStateException("ConcurrentBag has been closed, ignoring add()"); } sharedList.add(bagEntry); // spin until a thread takes it or none are waiting while (waiters.get() > 0 && bagEntry.getState() == STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) { Thread.yield(); } }
public boolean remove(final T bagEntry) { if (!bagEntry.compareAndSet(STATE_IN_USE, STATE_REMOVED) && !bagEntry.compareAndSet(STATE_RESERVED, STATE_REMOVED) && !closed) { LOGGER.warn("Attempt to remove an object from the bag that was not borrowed or reserved: {}", bagEntry); return false; } final boolean removed = sharedList.remove(bagEntry); if (!removed && !closed) { LOGGER.warn("Attempt to remove an object from the bag that does not exist: {}", bagEntry); } threadList.get().remove(bagEntry); return removed; }
位置: com.zaxxer.hikari.util.ConcurrentBag
ConcurrentBag 可以算是 HikariCP 最核心的一个类,它是 HikariCP 底层真正的连接池,上述思考中的操作 就是靠它来实现的。
属性 | 描述 |
---|---|
CopyOnWriteArrayList sharedList | 存放着状态为使用中、未使用和保留三种状态的资源对象 |
ThreadLocal threadList | 存放着当前线程归还的资源对象 |
SynchronousQueue handoffQueue | 这是一个无容量的阻塞队列,出队和入队都可以选择是否阻塞 |
AtomicInteger waiters | 当前等待获取元素的线程数 |
位置:com.zaxxer.hikari.pool.HikariPool
HikariPool 是用来管理连接池的。
属性类型和属性名 | 说明 |
---|---|
DataSource dataSource | 用于获取原生连接对象的数据源。一般我们不指定的话,使用的是DriverDataSource |
ThreadPoolExecutor addConnectionExecutor | 执行创建连接任务的线程池。只开启一个线程执行任务。 |
ThreadPoolExecutor closeConnectionExecutor | 执行关闭原生连接任务的线程池。只开启一个线程执行任务。 |
ScheduledExecutorService houseKeepingExecutorService | 用于执行检查 idleTimeout、leakDetectionThreshold、keepaliveTime、maxLifetime 等任务的线程池。 |
ProxyLeakTaskFactory leakTaskFactory | 用于检测连接是否泄露(即:不受连接池管理的连接)。默认是空实现,可设置一个 阈值(毫秒时间)。会打印一个警告信息 |
ScheduledFuture<?> houseKeeperTask | 用于保持最小连接数,以及清除一些不活跃的连接 |
注意:HikariPool 还会开启一些 监控任务
- 心跳机制
- 指标监控
位置:com.zaxxer.hikari.HikariDataSource
HikariDataSource 实现 javax.sql.DataSource 。配置装配,供用户获取连接等。
private final HikariPool fastPathPool; private volatile HikariPool pool;
HikariDataSource 持有两个 HikariPool 。为什么要这样做呢?
首先,从性能方面考虑,使用 fastPathPool 来创建连接会比 pool 更好一些,因为 pool 被 volatile 修饰了,为了保证可见性不能使用缓存。那为什么还要用到 pool 呢?
排查了一下,发现 fastPathPool 其实只有在 getConnection 时才会使用。在获取监控指标等信息的时候还是 pool 。
个人认为有以下原因:
持有 HikariPool 的两种情况。
情况一:fastPathPool = pool = new HikariPool(this)。使用有参构造new HikariDataSource(HikariConfig configuration)来创建HikariDataSource;
情况二:fastPathPool = null;pool = new HikariPool(this)。使用无参构造new HikariDataSource()来创建 HikariDataSource 。
当然,更推荐使用 情况一 ,可以更加定制化配置信息。
public Connection getConnection() throws SQLException { if (fastPathPool != null) { return fastPathPool.getConnection(); } // 双重检查 pool 的初始化 HikariPool result = pool; if (result == null) { synchronized (this) { result = pool; if (result == null) { validate(); LOGGER.info("{} - Starting...", getPoolName()); try { pool = result = new HikariPool(this); this.seal(); } catch (PoolInitializationException pie) { if (pie.getCause() instanceof SQLException) { throw (SQLException) pie.getCause(); } else { throw pie; } } LOGGER.info("{} - Start completed.", getPoolName()); } } } return result.getConnection(); }
位置: com.zaxxer.hikari.util.PropertyElf
PropertyElf 主要是 设置 Hikari 的配置,主要方法 setTargetFromProperties 。
在项目中直接使用 Hikari 会发现它的 jar 包中 HikariProxyCallableStatement 、HikariProxyConnection 、HikariProxyDatabaseMetaData 等等 Hikari 开头的类,但是下载的源码当中却没有。 并且在 com.zaxxer.hikari.pool.ProxyFactory 中提供的方法 全部是直接抛出异常。方法的注释中提示查看 com.zaxxer.hikari.util.JavassistProxyFactory 。
查看 JavassistProxyFactory 后发现 它使用 javassist 将 ProxyFactory 中的方法进行了替换。并且继承了 com.zaxxer.hikari.pool 包下 Proxy* 的一些类,动态的生成 Hikari 开头的类。
利用到了 maven 插件。
<plugin> <!-- Generate proxies --> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.6.0</version> <extensions>true</extensions> <executions> <execution> <phase>compile</phase> <!-- phase>generate-test-sources</phase --> <goals> <goal>exec</goal> </goals> </execution> </executions> <configuration> <executable>java</executable> <arguments> <argument>-cp</argument> <argument>${project.build.outputDirectory}${path.separator}${maven.compile.classpath}</argument> <!-- 会执行 JavassistProxyFactory 类的 main 方法 --> <argument>com.zaxxer.hikari.util.JavassistProxyFactory</argument> <argument>${project.basedir}${file.separator}</argument> </arguments> </configuration> </plugin>
暂时还不明白这么做的原因是什么,等到以后再补充。
个人感觉是 拆分开 会速度快一点,毕竟 连接的开销 在项目启动的时候已经完成,使用的时候多次发送sql 并不会影响速度。反而是一个复杂的sql 可能会导致 mysql 宕机。
此说法缺乏有效数据验证,待验证后补充
$ ping 0PING 0 (127.0.0.1) 56(84) bytes of data.64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.053 ms64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.037 ms
$ ping 0PING 0 (0.0.0.0): 56 data bytesping: sendto: No route to hostping: sendto: No route to host
> ping 0正在 Ping 0.0.0.0 具有 32 字节的数据:PING:传输失败。常见故障。PING:传输失败。常见故障。PING:传输失败。常见故障。PING:传输失败。常见故障。0.0.0.0 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 0,丢失 = 4 (100% 丢失),
就像 IPv6,IPv4 中也有一部分 0 是可以省略不写的。
$ ping 127.1PING 127.1 (127.0.0.1): 56 data bytes64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.033 ms64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.085 ms
$ ping 192.168.51正在 Ping 192.168.0.51 具有 32 字节的数据:
它将 10.50.1 转换为 10.50.0.1,在最后一位数字之前添加必要的零。
IP 的最后以为超过 255,并且前面短缺,形如:10.513、10.0.513 等
$ ping 10.0.513PING 10.0.513 (10.0.2.1): 56 data bytes64 bytes from 10.0.2.1: icmp_seq=0 ttl=61 time=10.189 ms64 bytes from 10.0.2.1: icmp_seq=1 ttl=61 time=58.119 ms
ping 10.0.513,转换为 10.0.2.1。最后一位数字可以解释为 2 x 256 + 1。它把值移到左边。
$ ping 167772673PING 167772673 (10.0.2.1): 56 data bytes64 bytes from 10.0.2.1: icmp_seq=0 ttl=61 time=15.441 ms64 bytes from 10.0.2.1: icmp_seq=1 ttl=61 time=4.627 ms
$ ping 0xA000201PING 0xA000201 (10.0.2.1): 56 data bytes64 bytes from 10.0.2.1: icmp_seq=0 ttl=61 time=7.329 ms64 bytes from 10.0.2.1: icmp_seq=1 ttl=61 time=18.350 ms
$ ping 10.0.2.010PING 10.0.2.010 (10.0.2.8): 56 data bytes
末尾的 010 会以八进制进行转换。
有一个有用的命令行IP计算器叫做 sipcalc (开源的),你可以用它来进行十进制和十六进制的转换。
sipcalc: https://github.com/sii/sipcalc
]]>Multipart 或者 "form-encoded data" 是个什么东西,我从来没有深入研究过,但是我却经常使用,主要是 http 库已经封装了他的使用。正确的使用能够使上传文件更快,使用更少的内存。
Multipart 使上传文件更加高效。在 Multipart 之前,上传文件的标准是 "application/x-www-form-urlencoded",其实这种方式要求客户端在上传文件之前先进行 url 编码。如果文件是 ASCII 文本,编码有效,但如果是二进制数据,必须对每一个字节进行编码,相应的服务器端也会进行解码,这使得上传效率极低。如果想要上传多个文件,这就需要发起多个 http 请求。很显然,多个请求会增加连接时间,也就会有更多的延迟。
在 1998 年, RFC 2388 提出一个新的标准,"multipart/form-data",它允许一个 http 主体中发送许多文件,而且无需编码。不编码意味着可以节省大量的 CPU 时间,并且整体的大小并不会膨胀。
这个协议是为从 HTML 表单中上传文件而设计的,"multipart/form-data" 因此的名。但实际上可以用来上传任何想要上传的文件。规范中没有任何要求必须使用 <form> 或者 其他 HTML 标签。也就是说可以用这个协议从任何 Http 客户端向任何 Http 服务器上传文件。
multipart 的另一个好处是,服务器可以单独对每个部分进行流处理。例如:你如果将 5 个文件编码为 JSON 对象来上传(JSON 不能直接处理二进制文件,所以需要将每个文件转换为 base64 字符串),服务器需要把整个 JSON 对象缓冲到内存中,然后解码。如果是 multipart 可以流式处理每个文件,减少内存的使用并改善延迟,因为第一个文件传输完成的时候,就可以直接处理,不需要等待其他四个传输完成。
MIME 类型分为两种:discrete and multipart.
discrete 包含一个文件。例如 application/ (binary), image/, text/ 等。
multipart 是有许多文件,并且每个文件都可以有自己的 MIME 类型。
有两种 multipart 类型: message/ and multipart/(multipart 可以是一种类别,也可以是一种类型,但是无需困惑,常说的 multipart 指的就是 "multipart/form-data")。message/ 类型基本上不再用于任何东西,但 multipart/ 却非常重要。"multipart/form-data" 用于通过 HTML 表单将文件从浏览器发送到服务器。
注意: multipart 不一定要包含多个文件,也可以只包含一个。主要是使用 multipart 进行有效的二进制编码。
如果内容类型为 "multipart/form-data",那么在 HTTP 主体中包含多个部分(也就是多个文件)。每个部分都会由 "边界分隔符(boundary delimiter)"来分隔。而在 Http 消息中有一个 header 定义了 分隔符,所以服务器可以知道每个文件的边界在何处。
每个部分也有定义一些特殊的 header :
不能使用其他的 Header !
引用 RFC 7578
"The multipart/form-data media type does not support any MIME header fields in parts other than Content-Type, Content-Disposition, and (in limited circumstances) Content-Transfer-Encoding. Other header fields MUST NOT be included and MUST be ignored.".
下面是一个来自 Stack Overflow 的关于 HTTP 正文的实际例子。使用 multipart ,包含3个 GIF 文件。
POST /cgi-bin/qtest HTTP/1.1Content-Type: multipart/form-data; boundary=2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0fContent-Length: 514--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0fContent-Disposition: form-data; name="datafile1"; filename="r.gif"Content-Type: image/gifGIF87a.............,...........D..;--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0fContent-Disposition: form-data; name="datafile2"; filename="g.gif"Content-Type: image/gifGIF87a.............,...........D..;--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0fContent-Disposition: form-data; name="datafile3"; filename="b.gif"Content-Type: image/gifGIF87a.............,...........D..;--2a8ae6ad-f4ad-4d9a-a92c-6d217011fe0f--
可以对整个 Multipart 响应进行 gzip 压缩,但是不能指定其中某一部分进行压缩。这是因为 HTTP 主体定义了整个消息的压缩头 。因此客户端没办法告诉服务器 哪个部分是压缩的,哪个部分不是。而且 Multipart 只允许有 3 个特定的 HTTP header,而压缩标识不在其中。
"multipart "或 "form-encoded data "是一种包含多个文件的MIME类型。每个文件都有自己的MIME类型和名称。从历史上看,这比其他上传多个文件的方式有很大的改进,因为它可以将每个文件作为原始二进制文件发送,不需要额外的编码或转义。
在这片文章之前,我并不觉得这是什么很先进的东西,毕竟自首次发布以来,已经过去 25 年。也许我们现在有更好的方法来上传文件了。
其实 JSON 的方式也能满足日常的需求,上传文件也很容易组成 JSON。如果每个文件都是一个 JSON 体,并且也可以很容易进行组合,只需要把 n 个独立的 JSON 体 组合成一个 有 n 个字段的大体,但是这样性能不好。
必须对文件内容进行base64处理,因为JSON只能处理文本,不能处理二进制。在解码之前,服务器必须将整个JSON体缓冲到RAM中。
multipart/form-data 可以有效地将多个文件上传组合在一起。而这种权衡带来了一些复杂性,比如边界和内容处置。显然,multipart/form-data 已经足够好,使用它的地方也有很多。
期待 能够了解替代 multipart 的方案,或者更好的上传方式。如您知道,请联系我。
]]>参考文献:Linux 就该这么学(第 2 版)
目前广泛使用的开源软件许可协议之一,用户享有运行、学习、共享和修改软件的自由。
GPL 最初是自由软件基金会创始人 Richard Stallman 起草的,其版本目前已经发展到了第 3 版。GPL的目的是保证程序员在开源社区中所做的工作对整个世界是有益的,所开发的软件也是自由的,并极力避免开源软件被私有化以及被无良软件公司所剥削。现在,只要软件中包含了遵循 GPL 许可证的产品或代码,该软件就必须开源、免费,因此这个许可证并不适合商业收费软件。遵循该许可证的开源软件数量极其庞大,包括 Linux 内核在内的大多数的开源软件都是基于GPL 许可证的。GPL 赋予了用户著名的五大自由。
允许用户根据需要自由使用这个软件。
允许把软件复制到任何人的计算机中,并且不限制复制的数量。
允许开发人员增加或删除软件的功能,但软件修改后必须依然基于
允许用户深度定制化软件后,为软件注册自己的新商标,再发行衍生品的自由。
允许在各种媒介上出售该软件,但必须提前让买家知道这个软件是可以免费获得的。因此,一般来讲,开源软件都是通过为用户提供有偿服务的形式来营利的。
一个主要为保护类库权益而设计的GPL 开源协议。
与标准 GPL 许可证相比,LGPL 允许商业软件以类库引用的方式使用开源代码,而不用将其产品整体开源,因此普遍被商业软件用来引用类库代码。简单来说,就是针对使用了基于 LGPL 许可证的开源代码,在涉及这部分代码,以及修改过或者衍生出来的代码时,都必须继续采用 LGPL 协议,除此以外的其他代码则不强制要求。如果您觉得 LGPL 许可证更多地是关注对类库文件的保护,而不是软件整体,那就对了。因为该许可证最早的名字是 Library GPL,即 GPL 类库开源许可证,保护的对象有 glibc、GTK widget toolkit 等类库文件。
另一款被广泛使用的开源软件许可协议。相较于 GPL 许可证,BSD 更加宽松,适合于商业用途。用户可以使用、修改和重新发布遵循该许可证的软件,并且可以将软件作为商业软件发布和销售,前提是需要满足下面 3 个条件。
如果再发布的软件中包含开源代码,则源代码必须继续遵循 BSD 许可证。
如果再发布的软件中只有二进制程序,则需要在相关文档或版权文件中声明原始代码遵循了 BSD 许可证。
不允许用原始软件的名字、作者名字或机构名称进行市场推广。
顾名思义,是由 Apache 软件基金会负责发布和维护的开源许可协议。作为当今世界上最大的开源基金会,Apache 不仅因此协议而出名,还因市场占有率第一的 Web 服务器软件而享誉行业。目前使用最广泛的Apache 许可证是 2004 年发行的 2.0 版本,它在为开发人员提供版权及专利许可的同时,还允许用户拥有修改代码及再发布的自由。该许可证非常适合用于商业软件,现在热门的 Hadoop、Apache HTTP Server、MongoDB 等项目都是基于该许可证研发的。
程序开发人员在开发遵循该许可证的软件时,要严格遵守下面 4 个条件。
该软件及其衍生品必须继续使用 Apache 许可证。
如果修改了程序源代码,需要在文档中进行声明。
若软件是基于他人的源代码编写而成的,则需要保留原始代码的许可证、商标、专利声明及原作者声明的其他内容信息。
如果再发布的软件中有声明文件,则需在此文件中注明基于了 Apache 许可证及其他许可证。
源于麻省理工学院,又称为 X11 协议。MIT 许可证是目前限制最少的开源许可证之一,用户可以使用、复制、修改、再发布软件,而且只要在修改后的软件源代码中保留原作者的许可信息即可,因此普遍被商业软件(例如 jQuery 与 Node.js)所使用。也就是说,MIT 许可证宽松到一个新境界,即用户只要在代码中声明了 MIT 许可证和版权信息,就可以去做任何事情,而无须承担任何责任。
于 1998 年初由 Netscape 公司的 Mozilla 小组设计,原因是它们认为 GPL 和 BSD 许可证不能很好地解决开发人员对源代码的需求和收益之间的平衡关系,因此便将这两个协议进行融合,形成了 MPL。2012 年年初,Mozilla 基金会发布了 MPL 2.0 版本(目前为止也是最新的版本),后续被用在 Firefox、Thunderbird 等诸多产品上。最新版的 MPL 公共许可证有以下特点。
在使用基于 MPL 许可证的源代码时,后续只需要继续开源这部分特定代码即可,新研发的软件不用完全被该许可证控制。
开发人员可以将基于 MPL、GPL、BSD 等多种许可证的代码一起混合使用。
开发人员在发布新软件时,必须附带一个专门用于说明该程序的文件,内容要有原始代码的修改时间和修改方式。
众所周知,绝大部分的开源软件在安装完毕之后即可使用,很难在软件界面中找到相关的收费信息。所以经常会有人提问:“开源社区的程序员总要吃饭的呀,他们是靠什么营利呢?”针对这个问题,网络上有两种声音:
这两种解释都各有道理,但是不够全面。但其实就开源软件来讲,营利模式具体包括以下 5 种:
多条产品线 :如 MySQL 数据库便有个人版和企业版两个版本—个人版完全免费,起到了很好的推广作用;企业版则通过销售授权许可来营利。
技术服务型 :JBoss 应用服务器便是典型代表,JBoss 软件可自由免费使用,软件提供方通过技术文档、培训课程以及定制开发服务来营利。
软硬件结合 :比如 IBM 公司在出售服务器时,一般会为用户捆绑销售 AIX 或 Linux系统来确保硬件设施的营利。
技术出版物 :比如 O'Reilly 既是一家开源公司,也是一家出版商,诸多优秀图书都是由 O'Reilly 出版的。
口碑和品牌 :微软公司曾多次表示支持开源社区。大家对此可能会感到意外,但这是真的!Visual Studio Code、PowerShell、TypeScript 等软件均已开源。大家是不是瞬间就对微软公司好感倍增了呢?买一份正版系统表示支持也就是人之常情了。
# 更改计算机的 执行策略set-ExecutionPolicy RemoteSigned # 接下来 输入: Y# 查资料之后发现是在计算机上启动 Windows PowerShell 时,执行策略是 Restricted(默认设置)。# Restricted 执行策略不允许任何脚本运行。 # AllSigned 和 RemoteSigned 执行策略可防止 Windows PowerShell 运行没有数字签名的脚本。# 想了解 计算机上的现用执行策略,打开PowerShell 然后输入 get-executionpolicy
PS G:\pico> $Profile C:\Users\leo\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
Set-Alias ll ls
再次打开 PowerShell,输入 ll 已经成为可以运行的命令
打开 IDEA -> file -> settings -> Tools -> Terminal
在 开始菜单 找到 PowerShell 右键 打开文件所在位置,此时可能是一个快捷方式,再次打开文件位置即可。
注意:如果想知道系统都有哪些alias,PowerShell 中输入 get-alias 。
]]>HashMap 有两个 computeIfAbsent 调用,不同的 Key 但是 hashCode 相同,equals 不相等。
// 测试代码public class ComputeIfAbsentTest { public static void main(String[] args) { Map<Key, String> m = new HashMap<>(); m.computeIfAbsent(new Key("firstKey"), k -> { m.computeIfAbsent(new Key("secondKey"), sk -> "secondKey"); return "firstValue"; }); System.out.println("Map.size(): " + m.size()); // size == 2// Map.entrySet().toArray().length: 1// firstKey System.out.println("Map.entrySet().toArray().length: " + m.entrySet().toArray().length); for (Key k : m.keySet()) { System.out.println(k.value); } } private static class Key { private final String value; private Key(String val) { value = val; } @Override public int hashCode() { return 1; // 模拟 hashCode 相同的情况 } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Key other = (Key) obj; return Objects.equals(value, other.value); } }}
// java.util.HashMap#computeIfAbsent// jdk 8public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { // ...... V v = mappingFunction.apply(key); if (v == null) { // 如果旧的值和新值都为null 直接返回 return null; } else if (old != null) { // 替换旧的值 old.value = v; afterNodeAccess(old); return v; } else if (t != null) // 如果是 红黑树 节点 进行特殊操作 t.putTreeVal(this, tab, hash, key, v); else { // 对 相应 数组位置直接更换 // 上述问题出现的关键就是该行代码,两个 Key 的HashCode相同时,此处已经有值,会出现覆盖情况 tab[i] = newNode(hash, key, v, first); if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); } ++modCount; // SIZE +1 ++size; afterNodeInsertion(true); return v; }// 实际上 jdk 9 已经修复问题,下面截取 jdk 17 的代码做对比// jdk 17public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { // ...... // 此处先 保留一份 快照,如果 apply 操作 使值发生了变化,则抛出异常,该操作不被允许 // modCount 作用: 数量或者其他方式使结构发生变化的标记 int mc = modCount; V v = mappingFunction.apply(key); if (mc != modCount) { throw new ConcurrentModificationException(); } if (v == null) { return null; } else if (old != null) { old.value = v; afterNodeAccess(old); return v; } else if (t != null) t.putTreeVal(this, tab, hash, key, v); else { tab[i] = newNode(hash, key, v, first); if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); } modCount = mc + 1; ++size; afterNodeInsertion(true); return v; }
频繁加锁,性能问题
// java.util.concurrent.ConcurrentHashMap#computeIfAbsent// jdk 8public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { if (key == null || mappingFunction == null) throw new NullPointerException(); int h = spread(key.hashCode()); V val = null; int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; if (tab == null || (n = tab.length) == 0) tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & h)) == null) { // key 不存在 且 hash 对应的位置还没值 Node<K,V> r = new ReservationNode<K,V>(); synchronized (r) { // cas 操作设置值 if (casTabAt(tab, i, null, r)) { binCount = 1; Node<K,V> node = null; try { if ((val = mappingFunction.apply(key)) != null) node = new Node<K,V>(h, key, val, null); } finally { setTabAt(tab, i, node); } } } if (binCount != 0) break; } else if ((fh = f.hash) == MOVED) // 扩容 tab = helpTransfer(tab, f); else { boolean added = false; // 可以看到,每次的查找都会加锁(synchronized), synchronized (f) { if (tabAt(tab, i) == f) { if (fh >= 0) { // 在链表中查找 key } else if (f instanceof TreeBin) { // 在红黑树中查找 key } } } // 树化判断 } } // 增加数量操作 return val; }// 如果在 Java 8 的环境下使用 ConcurrentHashMap,一定要注意是否会并发对同一个 key 调用 computeIfAbsent,如果存在需要先尝试调用 get。// Map<String, String> m = new ConcurrentHashMap<>();// Object result = m.get("Key");// if (result == null){// result = m.computeIfAbsent("key",k->"value");// }// jdk 17public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) { if (key == null || mappingFunction == null) throw new NullPointerException(); int h = spread(key.hashCode()); V val = null; int binCount = 0; for (Node<K,V>[] tab = table;;) { // ...... else if (fh == h // 增加1: 对首个节点进行判断,如果是目标值直接返回 && ((fk = f.key) == key || (fk != null && key.equals(fk))) && (fv = f.val) != null) return fv; else { boolean added = false; synchronized (f) { if (tabAt(tab, i) == f) { if (fh >= 0) { // ...... if ((e = e.next) == null) { if ((val = mappingFunction.apply(key)) != null) { // 增加2: 如果节点类型是 ReservationNode,直接抛出异常 if (pred.next != null) throw new IllegalStateException("Recursive update"); // ...... } break; } } else if (f instanceof TreeBin) { // ...... } // 增加2: 如果节点类型是 ReservationNode,直接抛出异常 else if (f instanceof ReservationNode) throw new IllegalStateException("Recursive update"); } } // ...... } } // ...... return val; }
通过这些地址可以看到,请求都是 http 而不是 https。
思考:
为什么不用https?
郭霖:《第一行代码——Android(第3版)》
彭成寒:《新一代垃圾回收器 ZGC 设计与实现》
黄俊:《深入理解Java高并发编程》
江荣波:《Mybatis 3 源码深度解析》
李春 罗小波:《千金良方:MYSQL性能优化金字塔法则》
翟陆续:《Java 异步编程实战》
尼恩:《Netty、Redis、Zookeeper 高并发实战》
翟志军:《Jenkins 2.x实践指南》
《gRPC官方文档中文版》http://doc.oschina.net/grpc?t=58008
彭成寒:《JVM G1 源码分析和调优》
杨易:《深入解析Java虚拟机HotSpot》
《Http/2 in action 中文版》
周志明:《深入理解Java虚拟机 第三版》
周志明:《凤凰架构》
Ben Forta/ 钟鸣,刘晓霞 译:《SQL必知必会(第四版)》
朱忠华:《深入理解 Kafka:核心设计与实践原理》
中村成洋,相川光 / 丁灵 译《垃圾回收的算法与实现》
《设计模式之禅》
钱文品:《Redis深度历险核心原理与应用实践》
《Mysql是怎样运行的:从根上理解Mysql》
《Java 8 实战》
刘遄:《Linux 就该这么学》
汪文君:《Java 高并发编程详解:多线程与架构设计》
汪文君:《Java 高并发编程详解:深入理解并发核心库》
杰伊·温格罗(Jay Wengrow):《数据结构与算法图解》
翟陆续 薛宾田:《Java 并发编程之美》
黄俊:《Tomcat源码全解与架构思维》
西游日记 - 今何在
峡江的转弯处 陈行甲
羽来信
你的灯亮着吗?
长安的荔枝 - 马伯庸
显微镜下的大明 - 马伯庸
时生 - 东野圭吾
挪威的森林 - 村上春树
刺杀骑士团长 - 村上春树
天黑以后 - 村上春树
偷影子的人
蛤蟆先生去看心理医生
大自然治愈了我的抑郁症
点与线 - 松本清张
沉默大多数 - 王小波
说爱情 - 李银河
明朝那些事 - 当年明月
额尔古纳河右岸 - 迟子建
]]>工作以来一直在观察,职称为高级的开发工程师具有什么样的特点,为什么我不是高级?
工作中碰见很多的高级开发,他们对一些新的技术不了解,写出的代码也并不是那么优秀,不会用更高级的语法特性。但是他们在一些处理系统方面有自己的理解。比如,他们会更加注重监控系统,会花费一定的时间去观察服务的运行,会在意 qps,服务器的配置,接口的安全性,信息是否会泄露等等一系列。
纵观所遇到的初级工程师,普遍会关心软件的编写,重视代码的质量,采用最先进的技术,也会投入大量的时间区学习新的技术,以创建优雅的、可执行的、可维护的软件为最终目标。
而高级工程师普遍更关心建立系统。对于他们而言,创建软件只是其中的一个步骤。首先,他们质疑是否需要首先建立这个软件。他们会问它能解决什么问题,为什么解决这些问题很重要。他们询问谁将会使用这个软件,在什么规模上使用。他们考虑软件将在哪里运行,以及他们将如何监测它是否正常工作。他们还决定如何衡量该软件是否真正解决了它应该解决的问题。
建立系统要比建立软件难的多。作为一个工程师,待在一个地方,专注的打磨一段代码,写的优雅漂亮是非常有成就感。理所应当的认为确定需求是产品经理的工作,部署软件由运营团队负责。然而,恰恰是参与构建系统这些方面,工程师能够带来更大的价值。因为工程师才是最了解这个软件的人,知道怎样运行它,怎样监控它,怎样更容易地扩展等等。更重要的是,工程师的分析能力和解决问题的能力使工程师对产品需求的洞察力非常有价值。
不能说技术是不重要的,它能创建优雅的,可执行的,可维护的软件,使软件更容易运行,更少发生故障,更容易扩展等。然而,客户却可能不会喜欢这个软件,即使软件能够帮助解决很多问题。不喜欢的原因有很多种,比如:性能问题。而这些没有合适的监控手段,工程师一无所知。
询问过一些高级工程师,也看过一些网络上的文章,个人总结了一些建立系统所需要的清单(并非详尽无遗):
我见过不少的工程师,他们确信他们的职业生涯的唯一途径是投资他们的技术。这虽然很重要,但对公司来说,唯一重要的是你对业务的影响是多大。将重点从构建软件到构建系统,应该可以让你处于一个更好的位置。
]]>通过引入一个干净的垃圾收集器接口,加大不同垃圾收集器源代码的隔离力度。
每个垃圾收集器实现都在 src/hotspot/share/gc/$NAME 目录的源文件,例如 G1 在 src/hotspot/share/gc/g1 中,CMS 在 src/hotspot/share/gc/cms 等。但是,在 HotSpot 源中散落着点点滴滴。例如,大多数 GC 需要一定的屏障,需要在运行时、解释器、C1 和 C2 中实现。这些屏障不在 GC 特定的目录中,而是在 shared interpreter、C1 和 C2 源代码中(通常是 大量的 if-else 链)。同样的问题适用于 例如 MemoryMXBeans 等 诊断代码。这种源代码布局有以下几个缺点:
更干净的 GC 接口将会使实现新的收集器变得更加容易,它会使代码更加干净,并且在构建时排除一个或多个垃圾收集器更简单。添加新的垃圾收集器应该是实现一组有据可查的接口,而不是找出 HotSpot 中所有需要更改的地方。
GC 接口将定义已存在类 CollectedHeap ,并且每个垃圾收集器都必须实现。CollectedHeap 类将驱动垃圾收集器和 HotSpot 的交互。具体地说,垃圾收集器实现必须提供:
在多个垃圾收集器之间共享实现细节的代码应该存在于帮助器类中。这样它就可以很容易的被不同的 GC 实现所使用。例如:可能有一个帮助器类实现了 卡表 (card table)支持的各种屏障,并且任何需要卡表后屏障的 GC 都将可以调用该帮助器类的相应方法。通过这种方式,接口提供了实现全新屏障的灵活性,允许同时以混合搭配的方式重用现有代码。
]]>