首页 文章 精选 留言 我的

精选列表

搜索[不可见索引],共10012篇文章
优秀的个人博客,低调大师

测试不趁早,“持续测试”搞不好

一说起 DevOps,闯入脑海的就是CI/CD(Continuous Integration/Continuous Delivery,即持续集成和持续交付)。 但是,要想构建能够支撑起数字化转型要求的软件研发能力,与之适配的软件测试能力必不可少。 随着敏捷宣言的发布,业界开始提倡测试驱动开发、验收测试驱动开发,开始关注自动化测试,加强测试工具和测试框架的开发,尽可能做到回归测试自动化、测试管理自动化。 而DevOps 的兴起之后,人们更是要求测试要有“左移”和“右移”的能力,还要求在线测试(Test in Production,TIP)等。 于是,Continuous Testing(持续测试)走进我们视线中。 01 测试已死?“持续测试”才是未来 在 GTAC 2011的一场名为《测试已死》的演讲中,Alberto Savoia 描述了几个测试人员必须要面对的开发趋势: 首先,所有的checking工作(包括validation和verification)都可以自动化;其次,随着云计算出现,部分用户可以在云上对开发版本做出测试;最后,开发者自己做测试几乎是不可避免的趋势。 在演讲中,Alberto Savoia 认为传统的QA方式隔离了开发和测试并不合理 在传统测试方法中,测试团队通常采用的实践是:测试管理- 规划测试工作-识别需要的测试-创建手动测试-收集现有测试-执行测试-跟踪和报告测试进度。 因此,传统测试需要关注的是:测试工作是否按计划完成? 其中的缺点也是显而易见的。首先,手动运行所有测试是低效或无效的,在许多情况下不可行;其次,这个方法并没有服务于软件开发项目,无法创建稳健且可维护的测试自动化框架,没法实现最终的项目目标。 而这些问题在敏捷和DevOps时代,得到了足够重视。 敏捷的出现,大大引入了自动化测试的比例。因此,敏捷时期的测试呈现出以底层运行速度快、消耗小的单元测试为主的正三角模式;强调测试持续进行,通过各部门的协同工作,持续发现缺陷并迅速修复。 到了DevOps时代,“持续测试”的概念涌现,在这个概念中,测试应该是开发过程的一部分,而不是在开发完成后的“保健”任务。 持续测试是持续交付流水线中的一环,让开发过程可随时且具有连续性的自动化测试流程。但是,持续测试并不等同于自动化测试,而是大于自动化测试。IBM认为,测试自动化与服务虚拟化的组合称为 “持续测试”。 在整个持续交付流程中,包括了计划、编码、构建、测试、发布、部署、运维和监控8个环节,既要求“速度”也要求“质量”。其中,持续测试保障的就是整个开发流程的质量问题。 总的来说,持续测试需要在正确的时机将可行的反馈意见提供给正确的利益相关,侧重于业务风险,并提供有关软件是否可以发布的见解。 因此,持续测试关注的是:提供的有关软件是否可以发布? 02 “持续测试” 的7个方法论 “持续测试”虽好,但建立持续测试文化需要投入人员、实践、工具和时间。 持续测试的完整时间,需要取得公司高层的支持,制定清晰的转型战略、建立指导团队。 建设内容包括:研发团队测试能力建设、工具与框架建设以及最佳实践落地。 整个过程需要通过合理量化跟踪体系,去量化持续测试建设的进展和影响。 以下七个方面,是构建持续测试的关键: 1、You can’t continuously if you don’t start testing early. 发现bug的时间越早越好。在测试中,尽早发现了风险元素,就可以不断重用这些测试、尽早地直接向开发团队提供代码质量的迭代式反馈。 在生命周期中,更早和更频繁地执行测试(“提前测试”),使团队能够持续地编译、部署、测试和收集反馈。 而在生产中发现问题时,不仅解决成本非常高,而且可能严重损害公司的声誉,甚至对客户忠诚度产生持久的影响。如果没有及时的测试和反馈,公司就无法真正地快速提高质量。 2、开发级测试至关重要 在一定程度上,开发与测试的确日渐融合,这样可以确保开发得到快速的反馈,而且保障了结果符合预期。 在持续集成中,们测试反馈越及时,越来越多的测试操作,例如静态或者动态分析、security checks、API validation……越可能被纳入其中,被集中的信息越丰富,项目的结果就越有保障。 3、自动化是提速降本的利器 执行过程要达到“足够快”,以满足整体迭代的效率。这要求每个阶段都引入尽可能多的自动化工具和能力。比如,云平台提倡基础设施即代码(IaC)和容器领域的编排技术(以K8s为代表)都在一定程度上实现了环境的自动化部署。 近来,国内外也涌现了不少针对测试的自动化工具及平台。以国内的飞算SoFlu全自动软件工程平台为例,近期上线的全自动测试平台主要通过创建项目—创建测试用例—编辑测试场景—创建测试计划—接口测试—性能测试这6步流程来实现。 据了解,该平台具备三大特性:一是测试生命周期管理。它提供测试用例管理、测试用例评审、测试计划跟踪和测试报告生成等测试生命周期管理相关功能。二是测试数据管理。全自动测试平台基于测试脚本与测试数据分离的思路,方便研发测试协同、方便自动化测试中的测试数据使用,支持 UI、接口等自动化工具中快速可重复地使用。三是精准回归测试。它在项目测试时,可以自动识别所有变动接口,自动查找接口关联的所有测试用例,进行精准回归测试。 值得注意的是,全自动测试平台随其全自动开发平台联动,开发测试一键关联,自动生成测试用例完成软件测试,1 人就即可完成开发、测试整套流程。 https://www.feisuanyz.com/testv/ 4、服务虚拟化是稳定性的重点 用户被测系统通常并不孤立存在,它依赖各种外部系统才能正常运行。但是在测试环节中,这些外部系统依赖可能会因为各种原因而不具备支持持续测试正常运行。 服务虚拟化是解决自动化测试中外部依赖不稳定或者不可得的关键手段。 服务虚拟化需要能够快速、准确地模拟外部依赖系统。从而,只需明确测试需求,而不需要太多关注真实的业务逻辑。此外,服务虚拟化尤其注重自身的稳定性。它的初衷是解决外部系统依赖的稳定性和可获得性,如果其自身不够稳定,就无法支撑持续测试的高效、高频运行。 5、快使用虚拟机和容器技术 虚拟机通过机器镜像解决整个运行机器的环境管理,而容器技术则采用更为轻量的运行时封装模式构建。 这样就可以屏蔽业务系统依赖的底层基础设施差异,研发团队只需要管理好虚拟机和容器的镜像版本,基本就能保障多个测试环境的一致性。 目前,好用的容器包括Docker、 Mesos或者 LXC等等,他们大多都拥有很高的适配性。 6、从产品的角度开展测试 正如前文所述,充分考虑到产品最终形态,也就是产品偏向,是“持续测试”的特点之一。因此,在测试设计阶段,就应该开始向生产思维的配置靠近,去模拟产品类似的测试氛围。 7、逐步进化测试数据管理 如何处理好测试数据,往往是一个重大的挑战。很多情况下,人们不知道什么样的数据是测试所需要的、什么样的数字管理是好的。 简单来说,测试数据管理随着DevOps实践的深入程度,呈现出4个阶段: 第一阶段:测试数据以个人管理为主,生成机制随机,质量无法保障。 第二阶段:通过文件或者数据库形成初步的测试数据集中管理,但是无合理的版本管理机制,无数据生成和质量保障机制。 第三阶段:有集中的测试数据管理平台,平台数据进行合理的版本管理,有较为丰富的数据生成和生产数据脱敏机制,数据质量稳定。 第四阶段:测试数据集中管理,数据版本和质量可靠,测试数据与测试用例和自动化测试场景形成良好关联和互动。 当然,随着持续测试实践的程度越高,我们所要求的测试数据管理能力自然也就越高。

优秀的个人博客,低调大师

Spring为啥不推荐使用@Autowired注解?

引言 使用IDEA开发时,同组小伙伴都喜欢用@Autowired注入,代码一片warning,看着很不舒服,@Autowired作为Spring的亲儿子,为啥在IDEA中提示了一个警告:Field injection is not recommended 想搞清楚这个问题之前,首先先了解一下依赖注入的几种方式 Spring的三种注入方式 属性(filed)注入 这种注入方式就是在bean的变量上使用注解进行依赖注入。本质上是通过反射的方式直接注入到field。这是我平常开发中看的最多也是最熟悉的一种方式。 @Autowired UserDao userDao; 复制代码 构造器注入 将各个必需的依赖全部放在带有注解构造方法的参数中,并在构造方法中完成对应变量的初始化,这种方式,就是基于构造方法的注入。比如: final UserDao userDao; @Autowired public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } 复制代码 set方法注入 通过对应变量的setXXX()方法以及在方法上面使用注解,来完成依赖注入。比如: private UserDao userDao; @Autowired public void setUserDao (UserDao userDao) { this.userDao = userDao; } 复制代码 属性注入可能出现的问题 问题一 基于 field 的注入可能会带来一些隐含的问题。来我们举个例子: @Autowired private User user; private String company; public UserDaoImpl(){ this.company = user.getCompany(); } 复制代码 编译过程不会报错,但是运行之后报NullPointerException Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Constructor threw exception; nested exception is java.lang.NullPointerException 复制代码 Java 在初始化一个类时,是按照静态变量或静态语句块 –> 实例变量或初始化语句块 –> 构造方法 -> @Autowired 的顺序。所以在执行这个类的构造方法时,user对象尚未被注入,它的值还是 null。 问题二 不能有效的指明依赖。相信很多人都遇见过一个bug,依赖注入的对象为null,在启动依赖容器时遇到这个问题都是配置的依赖注入少了一个注解什么的。这种方式就过于依赖注入容器了,当没有启动整个依赖容器时,这个类就不能运转,在反射时无法提供这个类需要的依赖。 问题三 依赖注入的核心思想之一就是被容器管理的类不应该依赖被容器管理的依赖,换成白话来说就是如果这个类使用了依赖注入的类,那么这个类摆脱了这几个依赖必须也能正常运行。然而使用变量注入的方式是不能保证这点的。 spring建议 Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. 翻译过来就是: 强制依赖就用构造器方式 可选、可变的依赖就用setter注入 使用@Resource代替@Autowired @Resource有2个属性name和type。在spring中name属性定义为bean的名字,type这是bean的类型。如果属性上加@Resource注解那么他的注入流程是 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。 如果指定了name,则从上下文中查找名称匹配的bean进行装配,找不到则抛出异常。 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。 如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。 @Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰。 使用@RequiredArgsConstructor构造器方式注入 这种形式就是Spring推荐使用的构造器方式注入,此种方式是lombok包下的注解,如果使用此种方式,需要项目中引入lombok,例如: @RequiredArgsConstructor public class UserDaoImpl{ private final User user; } 复制代码 最后 如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163相互学习,我们会有专业的技术答疑解惑 如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star:http://github.crmeb.net/u/defu不胜感激!

优秀的个人博客,低调大师

Spring Cloud Alibaba到底坑不坑?

之前我发过一篇《说说我为什么看好Spring Cloud Alibaba》,然后这两天有网友给我转了这篇文章《坑爹项目spring-cloud-alibaba,我们也来一个》,问我的看法是怎么样的,聊天时候简单说了一下。今天在家休息,抽空整理一下内容,逐点说一下我的看法,主要还是觉得这篇文章博眼球的成分高一些,因为这篇文章的解读与之前其他某些自媒体发布的《Eureka 2.0 开源工作宣告停止,继续使用风险自负》一文有异曲同工之“妙”,如果读者没有真正的理解Spring Cloud与Spring Cloud Alibaba,就很有可能会对它们有什么误解,然后产生这样的想法: 感觉很有道理,这东西真垃圾 标题很燃,必须转发 下面具体来说说该文章中,那些我认为不太正确的解读: 第一点:远程调用RPC 看看这篇文章的解读: SpringCloud默

优秀的个人博客,低调大师

Atomic你了解不?

前言 只有光头才能变强 之前已经写过多线程相关的文章了,有兴趣的同学可以去了解一下: https://github.com/ZhongFuCheng3y/3y/blob/master/src/thread.md 在阅读《阿里巴巴 Java开发手册》读后感时,还有未解决的问题: 如果是count++操作,使用如下类实现: AtomicInteger count = new AtomicInteger(); count.addAndGet(1);如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。 之前在学习的时候也看过AtomicInteger类很多次了,一直没有去做相关的笔记。现在遇到问题了,于是就过来写写笔记,并希望在学习的过程中解决掉问题。 一、基础铺垫 首先我们来个例子: public class AtomicMain { public static void main(String[] args) throws InterruptedException { ExecutorService service = Executors.newCachedThreadPool(); Count count = new Count(); // 100个线程对共享变量进行加1 for (int i = 0; i < 100; i++) { service.execute(() -> count.increase()); } // 等待上述的线程执行完 service.shutdown(); service.awaitTermination(1, TimeUnit.DAYS); System.out.println("公众号:Java3y---------"); System.out.println(count.getCount()); } } class Count{ // 共享变量 private Integer count = 0; public Integer getCount() { return count; } public void increase() { count++; } } 你们猜猜得出的结果是多少?是100吗? 多运行几次可以发现:结果是不确定的,可能是95,也可能是98,也可能是100 根据结果我们得知:上面的代码是线程不安全的!如果线程安全的代码,多次执行的结果是一致的! 我们可以发现问题所在:count++并不是原子操作。因为count++需要经过读取-修改-写入三个步骤。举个例子: 如果某一个时刻:线程A读到count的值是10,线程B读到count的值也是10 线程A对count++,此时count的值为11 线程B对count++,此时count的值也是11(因为线程B读到的count是10) 所以到这里应该知道为啥我们的结果是不确定了吧。 要将上面的代码变成线程安全的(每次得出的结果是100),那也很简单,毕竟我们是学过synchronized锁的人: 在increase()加synchronized锁就好了 public synchronized void increase() { count++; } 无论执行多少次,得出的都是100: 从上面的代码我们也可以发现,只做一个++这么简单的操作,都用到了synchronized锁,未免有点小题大做了。 Synchronized锁是独占的,意味着如果有别的线程在执行,当前线程只能是等待! 于是我们原子变量的类就登场了! 1.2CAS再来看看 在写文章之前,本以为对CAS有一定的了解了(因为之前已经看过相关概念,以为自己理解了)..但真正敲起键盘写的时候,还是发现没完全弄懂...所以再来看看CAS吧。 来源维基百科: 比较并交换(compare and swap, CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题。 该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值。 CAS有3个操作数: 内存值V 旧的预期值A 要修改的新值B 当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值(A和内存值V相同时,将内存值V修改为B),而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试(或者什么都不做)。 我们画张图来理解一下: 我们可以发现CAS有两种情况: 如果内存值V和我们的预期值A相等,则将内存值修改为B,操作成功! 如果内存值V和我们的预期值A不相等,一般也有两种情况: 重试(自旋) 什么都不做 我们再继续往下看,如果内存值V和我们的预期值A不相等时,应该什么时候重试,什么时候什么都不做。 1.2.1CAS失败重试(自旋) 比如说,我上面用了100个线程,对count值进行加1。我们都知道:如果在线程安全的情况下,这个count值最终的结果一定是为100的。那就意味着:每个线程都会对这个count值实质地进行加1。 我继续画张图来说明一下CAS是如何重试(循环再试)的: 上面图只模拟出两个线程的情况,但足够说明问题了。 1.2.2CAS失败什么都不做 上面是每个线程都要为count值加1,但我们也可以有这种情况:将count值设置为5 我也来画个图说明一下: 理解CAS的核心就是:CAS是原子性的,虽然你可能看到比较后再修改(compare and swap)觉得会有两个操作,但终究是原子性的! 二、原子变量类简单介绍 原子变量类在java.util.concurrent.atomic包下,总体来看有这么多个: 我们可以对其进行分类: 基本类型: AtomicBoolean:布尔型 AtomicInteger:整型 AtomicLong:长整型 数组: AtomicIntegerArray:数组里的整型 AtomicLongArray:数组里的长整型 AtomicReferenceArray:数组里的引用类型 引用类型: AtomicReference:引用类型 AtomicStampedReference:带有版本号的引用类型 AtomicMarkableReference:带有标记位的引用类型 对象的属性: AtomicIntegerFieldUpdater:对象的属性是整型 AtomicLongFieldUpdater:对象的属性是长整型 AtomicReferenceFieldUpdater:对象的属性是引用类型 JDK8新增DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder 是对AtomicLong等类的改进。比如LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。 Atomic包里的类基本都是使用Unsafe实现的包装类。 Unsafe里边有几个我们喜欢的方法(CAS): // 第一和第二个参数代表对象的实例以及地址,第三个参数代表期望值,第四个参数代表更新值 public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6); 从原理上概述就是:Atomic包的类的实现绝大调用Unsafe的方法,而Unsafe底层实际上是调用C代码,C代码调用汇编,最后生成出一条CPU指令cmpxchg,完成操作。这也就为啥CAS是原子性的,因为它是一条CPU指令,不会被打断。 2.1原子变量类使用 既然我们上面也说到了,使用Synchronized锁有点小题大作了,我们用原子变量类来改一下: class Count{ // 共享变量(使用AtomicInteger来替代Synchronized锁) private AtomicInteger count = new AtomicInteger(0); public Integer getCount() { return count.get(); } public void increase() { count.incrementAndGet(); } } // Main方法还是如上 修改完,无论执行多少次,我们的结果永远是100! 其实Atomic包下原子类的使用方式都不会差太多,了解原子类各种类型,看看API,基本就会用了(网上也写得比较详细,所以我这里果断偷懒了)... 2.2ABA问题 使用CAS有个缺点就是ABA的问题,什么是ABA问题呢?首先我用文字描述一下: 现在我有一个变量count=10,现在有三个线程,分别为A、B、C 线程A和线程C同时读到count变量,所以线程A和线程C的内存值和预期值都为10 此时线程A使用CAS将count值修改成100 修改完后,就在这时,线程B进来了,读取得到count的值为100(内存值和预期值都是100),将count值修改成10 线程C拿到执行权,发现内存值是10,预期值也是10,将count值修改成11 上面的操作都可以正常执行完的,这样会发生什么问题呢??线程C无法得知线程A和线程B修改过的count值,这样是有风险的。 下面我再画个图来说明一下ABA的问题(以链表为例): 2.3解决ABA问题 要解决ABA的问题,我们可以使用JDK给我们提供的AtomicStampedReference和AtomicMarkableReference类。 AtomicStampedReference: An {@code AtomicStampedReference} maintains an object referencealong with an integer "stamp", that can be updated atomically. 简单来说就是在给为这个对象提供了一个版本,并且这个版本如果被修改了,是自动更新的。 原理大概就是:维护了一个Pair对象,Pair对象存储我们的对象引用和一个stamp值。每次CAS比较的是两个Pair对象 // Pair对象 private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair; // 比较的是Pari对象 public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } 因为多了一个版本号比较,所以就不会存在ABA的问题了。 2.4LongAdder性能比AtomicLong要好 如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。 去查阅了一些博客和资料,大概的意思就是: 使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS会成功,所以其他线程会不断尝试自旋尝试CAS操作,这会浪费不少的CPU资源。 而LongAdder可以概括成这样:内部核心数据value分离成一个数组(Cell),每个线程访问时,通过哈希等算法映射到其中一个数字进行计数,而最终的计数结果,则为这个数组的求和累加。 简单来说就是将一个值分散成多个值,在并发的时候就可以分散压力,性能有所提高。 参考资料: AtomicLong与LongAdder性能对比https://zhuanlan.zhihu.com/p/45489739 LongAdder源码详解https://zhuanlan.zhihu.com/p/38288416 最后 参考资料: Java并发编程札记-目录https://blog.csdn.net/panweiwei1994/article/details/78483167 《Java并发编程实战》 《Java并发编程的艺术》 如果你觉得我写得还不错,了解一下: 坚持原创的技术公众号:Java3y。回复 1 加入Java群 文章的目录导航(精美脑图+海量视频资源):https://github.com/ZhongFuCheng3y/3y

优秀的个人博客,低调大师

freemarker模板修改后不生效

昨天用springboot+freemarker开发一个项目,已经设置热部署了,之前修改freemarker模板时,一刷新就会变化; 但是不知道为啥修改后,刷新没有变化,重新启动springboot就可以看到变化。 在网上查了半天都说是因为缓存,各种方法都试了,还是没有解决。偶然发现我修改了java,但是eclipse没有自动编译。 一看果然,eclipse的自动编译选项没有打开,开启后,修改模板,刷新就变化了。

优秀的个人博客,低调大师

eclipse tomcat下网页修改不生效

大家打开eclipse JavaEE新建一个动态网页工程(File,new,project,web,Dynamic Web Project)。新建一个html静态页面,输入如下代码: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> 哈哈 </body> </html> 然后用tomcat运行,访问它,会有“哈哈”显示出来。 然后把“哈哈”改成“呵呵”,保存,刷新一下网页,没反应......什么渣网。 干脆关闭tomcat,重启一次,还是这样,郁闷了。 然后我关掉eclipse,重新开过,好了。难道每次都要重启eclipse......没可能。 然后我在开了eclipse的情况下运行,显示“哈哈”,然后关掉tomcat,然后清除IE缓存(浏览器,工具,internet选项,常规,浏览历史记录,删除,全选,删除),然后修改“呵呵”,然后重启tomcat,成了。难道每次都要删除缓存......没可能。 然后我看见一个选项,浏览器,工具,internet选项,常规,浏览历史记录,设置,internet临时文件,检查所存网页的较新版本。郁闷,就是这个默认的自动搞死人。把它改成 每次访问网页时 ,一路确定。 继续那个html,“哈哈”显示成功,改成“呵呵”后重启tomcat也成功了!!!再进一步,改回“哈哈”,一定要保存,然后F5刷新网页,不用重启tomcat也成功了,神了!然后我测试了javascript的一个小程序也是不用重启tomcat就行了,不过先保存再刷新是必然的。

优秀的个人博客,低调大师

Android 手机不赚钱为啥还要做

每当苹果发布财报时,我们都能够看到iPhone带来的惊人利润,那么Android方面呢?现状是:三星还不错,LG和索尼可以做得更好,HTC似乎很迷茫,摩托罗拉基本上已经完了。 手机业务作为电子消费行业的重中之重,整个市场一直的令人垂涎的,但如今,真正赚钱的只有两家公司,便是苹果和三星。那么,为什么众多手机厂商仍在不断推出Android手机?The Verge分析了几大原因,可谓是精准剖析了目前智能手机行业现状,一起来了解一下。 智能手机是一种技术及品牌展示 以LG为例,其手机业务一直没有大红大紫,但LG一直尝试推陈出新,比如搭载曲面屏幕的G Flex系列。事实上,这是一种很好的技术展示、品牌宣传策略,因为LG本身还拥有IPS、OLED屏幕生产线,能够吸引其他OEM厂商购买其产品。另外,LG还涉足家电、智能家居等领域,手机也能作为生态环境的一部分,起到推波助澜的作用。 同样,索尼的Xperia智能手机也是起到类似作用。考虑到索尼集团庞大的业务线,包括数码相机、电子游戏、电影娱乐等等,其手机中塞满了索尼自家的服务功能,同时在硬件上也几乎都是使用集团内的资源,如液晶屏幕、拍照图像传感器、镜头技术等等。但遗憾的是,集结了众多自家精华的索尼手机本应该更出色,但现实情况却是低不成高不就。 智能手机作为购物门户 也有一些厂商,是将手机作为购物门户,发展其他业务。比如亚马逊的FirePhone,便是希望用户在手机上使用亚马逊账户购物、购买电子书、应用、音乐及视频内容,事实上Kindle系列平板已经做到了,但手机部分则由于价格昂贵、硬件缺乏吸引力、市场竞争激烈等多种原因,最终失败。小米,国人熟知的低价手机品牌,其实也是通过手机,逐渐发展壮大,形成生态链和其他产品线,甚至衍生出书包、拉杆箱、钱包这些配饰。 智能手机是最后的“救命稻草” 对于一些濒死的手机厂商,加入Android或是继续留在Android领域,实际上只是抓住最后一根“救命稻草”。比如黑莓,BB 10 OS机型几乎无人问津,但加入Android领域之后,反而吸引了一些关注,有关注就有销量。同样,HTC在智能手机市场每况愈下,甚至开始把精力放在VR领域,但只要有产品推出就会有曝光。 显然,苹果和三星已经占据了智能手机市场大量的份额和利润,留给其他厂商的空间并不多。不过,市场终归是多样化的,Android厂商们需要找到自己的位置,实现有效的利润转化,这可能是目前环境下最好的持续发展方向。 ====================================分割线================================文章转载自 开源中国社区[http://www.oschina.net]

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册