原文链接:
http://tianweili.github.io/blog/2015/03/17/linux-nexus-maven-private-server/
最新nexus下载地址:http://www.sonatype.org/nexus/go
解压后会在同级目录中,出现两个文件夹:nexus-oss-webapp-1.8.0
和sonatype-work
,前者包含了nexus的运行环境和应用程序,后者包含了你自己的配置和数据。
|
|
|
|
查看控制台:
|
|
显示未启动成功,报错如下:
|
|
原因:查找原因是JDK版本过低造成的,升级到最新的JDK7或者使用nexus-2.4-bundle.tar.gz
版本JDK6会支持.
Nexus所有版本下载地址:http://www.sonatype.org/nexus/archived
下载Nexus2.4重来
|
|
控制台显示启动成功。
查看nexus日志:
|
|
访问网址:http://yourhostname:8081/nexus
右上角以admin登陆,默认用户名/密码:admin/admin123。
3rd party、Snapshots、Releases这三个,分别用来保存第三方jar、项目组内部的快照、项目组内部的发布版.
将第三方的jar上传到nexus上面:
点击Upload Artifact(s)按钮提交后即上传。
查看上传的jar包如下:
在项目中使用私服的jar包配置pom.xml如下:
|
|
Maven在项目根目录下执行mvn eclipse:eclipse命令时,所依赖的jar包都会从私服中下载到本地并关联上项目,私服中没有就会从网络上下载到私服,本地再从私服下载。
在工程的pom.xml中添加:
|
|
进入maven的安装目录apache-maven-3.1.1\conf目录下,向settings.xml配置文件中的
|
|
进入windows命令行,在工程所在目录下执行
|
|
所部署的包就自动上传到了nexus安装目录下的/maven/nexus/sonatype-work/nexus/storage/releases/com/vclk/mkt/crawler/MarketingCrawler/0.3
目录
项目中的各种jar包和项目快照等都放在/nexus/sonatype-work/nexus/storage/
目录下,在这个目录下包括以下各种目录和存放相应文件。
/nexus/sonatype-work/nexus/storage/central
- 用于放置maven从中央仓库中下载下来的项目pom.xml中配置到的相关jar包;
/nexus/sonatype-work/nexus/storage/thirdparty
- 用于放置自己手动上传的第三方jar包;
/nexus/sonatype-work/nexus/storage/releases
- 用于放置项目deploy后的发布版。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/03/17/linux-nexus-maven-private-server/
转载请注明作者和文章出处,谢谢。
]]>原文链接:
http://tianweili.github.io/blog/2015/03/13/proxy-pattern/
代理模式属于结构性模式,使用频率很高。
定义:
Provide a surrogate or placeholder for another object to control access to it.
为其他对象提供一种代理以控制这个对象的访问。
装饰模式、状态模式、策略模式、访问者模式本质上都是在更特殊的场合采用了代理模式。
从以上UML类图中可以看出代理模式主要有三种角色:
抽象主题角色
可以是接口或抽象类,是某类通用业务的定义。
具体主题角色
作为被代理对象,是具体业务的真实执行者。
代理主题角色
是具体主题对象的代理,负责对具体对象的应用,把所有抽象主题定义的方法委托给具体主题对象来实现,它用来在具体主题对象业务处理的前后做一些处理工作。
|
|
从以上代码可以看出ProxySubject对象还有before和after方法,可以在RealSubject对象的someMethod业务方法前后做一些预处理和善后处理工作。
一个代理类可以代理多个被代理对象,只要是实现同一个接口。当然也可以一个被代理对象就有一个代理类,不过一般是一个接口有一个代理类就够了,在应用时具体是代理哪一个被代理对象,这是由场景类也就是高层模块定义的,根据构造方法的传入哪一个被代理对象参数来决定代理哪一个对象。
构造方法:
|
|
想代理哪个对象就要传入生成这个对象的实例。
各个角色职责清晰,比如被代理对象只需要实现属于自己具体的业务逻辑就行了,不用去关心非本职责的业务处理。其他的一些处理业务可以交给代理类来处理。这样做的好处是编程简洁清晰,业务分明。
扩展性好,具体实现对象的业务发生了变化,只需要修改自身业务处理逻辑,或者增加删减一个实现业务接口的对象,不会影响代理业务。
代理模式可以提供非常好的访问控制,由代理类来控制被代理对象,可以做一些预处理消息,过滤消息,消息转发和善后处理工作等等。
普通代理和强制代理是代理模式的两种不同结构,是根据调用者能够访问到代理对象还是具体对象来区分的。就好比网络上的代理服务器设置分为普通代理和透明代理。普通代理需要用户手动设置代理服务器的IP地址,用户必须知道代理的存在。透明代理就是用户不需要设置代理服务器地址,就可以直接访问,不用知道代理的存在。
普通代理是用户只能访问代理角色,而不能访问真实角色。
只需要对上面代码稍作改动即可实现普通代理的效果,代码如下:
|
|
运行程序结果如下:
在上面RealSubject的构造方法中是通过传入参数subject来限制用户不能实例化自己,当然也可以通过别的一些限制条件,比如类名必须有Proxy等等。
普通代理是通过代理角色找到真是角色,而强制代理是强制只能通过真实角色查找代理角色来访问,想直接通过实例化代理角色或真实角色都不能访问。
UML类图如下:
从以上UML类图可以看出Subject接口中添加了个获取代理的接口方法。
代码清单:
|
|
当客户端想通过真实角色来访问时,客户端代码如下:
|
|
执行结果:
访问被拒绝,因为它是通过真实角色来直接访问的,而不是通过真实角色来获取代理角色来访问。
当客户端想通过代理角色来访问时,客户端代码如下:
|
|
执行结果:
访问同样被拒绝,因为它是通过代理角色来直接访问的,而不是通过真实角色来获取代理角色来访问。
只有强制客户端通过真实角色来获取代理对象,才能访问。客户端代码如下:
|
|
执行结果:
通过真实角色来获取代理对象访问成功。
这一节之前所讲的代理其实都是静态代理,它有一个特点就是要在实现阶段就要指定代理类以及被代理者,很不灵活。而动态代理就是在实现阶段不用管代理具体对象,而在运行阶段指定代理哪个对象即可生产代理对象。
基本的UML类图如下所示:
从类图中可以看出,具体的业务逻辑和代理逻辑是两条线,两者之间没有必然的耦合关系。
InvocationHandler是JDK提供的接口,用来对被代理类的方法进行代理。
注意:被代理者必须实现一个接口,否则动态代理无法生成代理对象。
动态代理是根据被代理者的接口生成所有的方法。通过InvocationHandler接口,所有被代理的方法都由InvocationHandler来接管实际的处理逻辑。
代码清单:
|
|
invoke方法是接口InvocationHandler中定义必须实现的,它用来完成对真实方法的调用。
客户端调用代码:
|
|
执行结果:
从结果中可以看出我们已经达到了代理RealSubject对象的目的。
看了上面的客户端调用代码,我们可以优化一下,将Proxy封装起来,使得调用更简便一些。增加动态代理封装类:
|
|
客户端调用:
|
|
动态代理拥有以上静态代理所有优点,除此之外还有动态代理的代理对象是在需要的时候动态生成的。
在业务逻辑开发时可以不用管代理业务逻辑,这两条线不会耦合。比如在做具体的业务逻辑设计和实现时不用考虑日志、事务、权限等逻辑处理,这些可以通过动态代理来搞定。
Struts2的Form映射和Spring的AOP(Aspect Oriented Programming)就是动态代理的典型应用。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/03/13/proxy-pattern/
转载请注明作者和文章出处,谢谢。
]]>原文链接:
http://tianweili.github.io/blog/2015/03/11/abstract-factory-pattern/
抽象工厂模式定义:
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们具体的类。
抽象工厂模式是工厂方法模式的升级版本,主要是引入了一个产品族的概念,将针对单一产品升级到了针对多个产品品种和产品分类。
左边的UML图并不复杂,主要包括2个抽象产品和一个抽象工厂。两个具体实现工厂各对应创建两个不同类别产品。
抽象工厂模式关键点在于有了多个产品族。
如上图所示,ProductA1和ProductA2就是属于一个产品族。ProductA1和ProductB1分别属于两个不同的产品族。上面的UML图中有两个产品族。
有几个抽象产品类就有几个产品族。
有几个产品族,在工厂中就有几个创建方法。
继承于同一个抽象产品类的属于不同的产品等级。
ProductA1和ProductA2就是两个产品等级。ProductA1和ProductB1属于同一个产品等级。
有几个产品等级,就有几个实现工厂类。
在每个工厂类中,实现了不同产品族的创建方法。
|
|
客户端调用
|
|
在上面的客户端调用代码中,没有与具体的产品实现类有关的代码。所以在需要某个具体产品的时候,只需要知道与之对应的工厂来生产就可以了。
抽象工厂模式与工厂方法模式的关键不同在于引入了一个产品族的概念,工厂方法模式相当于只有一个产品族,而抽象工厂模式有多个产品族。
在有多个产品族的时候只能使用抽象工厂模式了。
针对多个产品族,每个实现工厂都有相应的创建对应产品的方法。而工厂方法模式中实现工厂中只会有一个创建产品的方法。
高层模块只需要知道生产相应产品的工厂类是谁,就能由工厂创建相应的产品对象。而他不用关心具体产品生产过程,符合迪米特法则。只依赖抽象产品,符合依赖倒置原则。使用产品子类替换产品父类,符合里氏替换原则。
不同产品族之间的约束放在工厂类中来实现,不对外公开,封装性好。
想较于工厂方法模式,可以应付产品更为复杂的场合。
在产品等级结构层面上符合开闭原则,增加一个产品等级结构扩展性好。
在产品族层面上不符合开闭原则,增加一个产品族,即相当于增加一个抽象产品时,需要修改大量的其他实现工厂,在产品族层面上扩展性不好。
当涉及到多个产品族的时候,就需要使用抽象工厂模式了。
据说抽象工厂模式最初应用于多个操作系统软件开发上,比如要开发一个系统桌面软件,要应用到Windows和Linux操作系统上。那么对于这样的情况我们是不是要分别为两种操作系统开发不同的软件呢?当然不是。对于开发一个桌面软件来说分为界面UI和功能代码等,那么就可以应用抽象工厂模式了,界面UI和功能代码都分别为Windows和Linux开发不同的一套,然后利用工厂在需要Windows的时候调用创建相应的Windows的界面UI和功能代码。
做过一个爬虫工程,需求是这样的,公司有多个站点,想要去根据搜索关键字获取它们在搜索页面一些要素,比如广告、文章、产品等信息。那么针对这种需求就采用了抽象工厂模式。首先将广告、文章等分别都作为一个产品族,每个网站都是一个产品等级。这样抽象出来的解析广告类、解析文章类等抽象接口,由每个具体产品去实现某个站点的解析广告,另一个产品族的具体产品去实现解析文章类,将具体解析过程封装在了产品内部。再使用相应的工厂来创建一个个产品族内的产品。
关键逻辑删减版类图如下
这样就实现了良好的封装性,高层模块想调用知道某个网站的一些统计分析数据时,只需要知道相应的工厂来生产就行了,不需要知道具体的实现过程和复杂的处理逻辑。在产品等级也就是网站层面上扩展性好,后来有新增站点的时候,直接增加产品等级,实现相应的抽象产品类,再增加一个具体实现工厂就好了。
不过它的缺点是在产品族也就是想新增解析需求的时候扩展性不好,比如后来想增加对搜索关键词后的首页文章内容进行统计,以便查看匹配度时。就需要增加一个产品族即抽象产品模块,需要修改每个工厂的代码。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/03/11/abstract-factory-pattern/
转载请注明作者和文章出处,谢谢。
]]>原文链接:
http://tianweili.github.io/blog/2015/03/09/factory-method-pattern/
工厂方法模式定义:
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses。
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法模式的对简单工厂模式进行了抽象。有一个抽象的Factory类(可以是抽象类和接口),这个类将不再负责具体的产品生产,而是只制定一些规范,具体的生产工作由其子类去完成。在这个模式中,工厂类和产品类往往可以依次对应。即一个抽象工厂对应一个抽象产品,一个具体工厂对应一个具体产品,这个具体的工厂就负责生产对应的产品。
工厂方法模式的核心和关键点是对工厂也进行了抽象。
工厂方法模式的UML类图如下所示:
在上面的UML图中主要包含了四中角色:
抽象工厂(Abstract Factory)角色
抽象工厂角色是工厂方法模式的核心,主要定义了具体工厂子类必须要实现的接口,并且所有创建对象的工厂类都必须实现该方法,与应用程序无关。
具体工厂(Concrete Factory)角色
这是实现抽象工厂的具体工厂类,包含与应用程序密切相关的逻辑,要受到应用程序的调用以创建产品对象。
抽象产品(Abstract Product)角色
具体产品对象的共同父类,由它来定义所有产品的共同接口。
具体产品(Concrete Product)角色
具体产品是简单工厂模式的创建目标,所有创建的对象都是某个具体产品类的实例。
|
|
运行结果:
工厂方法模式是简单功能工厂模式的衍生,它将工厂也抽象出来,定义了一个抽象工厂,将产品对象的实例化过程推到了下面的各个子类工厂,而且子类工厂与产品也是一一对应的。这是两者最大的区别。
工厂方法模式多了一个抽象工厂。
工厂方法模式支持多态。
解决了许多简单工厂模式中出现的问题。
完全符合”开闭原则”,具有可扩展性。
更为复杂的层次结构,可以应付产品结果更为复杂的场合。
工厂方法模式是典型的解耦框架。高层模块只需要知道产品的抽象类,其它实现类都不需要关心,符合迪米特法则。只依赖产品的抽象,符合依赖倒置原则。使用产品子类替换产品父类,符合里氏替换原则。
抽象出了一个抽象工厂,添加了具体工厂子类,会提高系统的复杂度。所以对于一些复杂的创建过程使用工厂方法模式才比较合适。
工厂方法模式是使用非常频繁的设计模式之一,在系统设计中几乎随处可见。简单工厂模式、工厂方法模式、抽象工厂模式这三个设计模式都有一些类似的特性,所以在适用场景上也都是类似的。
首先,作为一个创建型模式,在任何需要生成复杂对象的地方都可以使用工厂方法模式。但是要注意对于一些生成对象简单,特别是只需要通过new来生成对象的地方,就不需要使用工厂方法模式了。因为如果使用工厂方法模式,就需要引入一个工厂类,会增加系统的复杂度。
另外,在需要一个可扩展性强的系统设计中,可以考虑使用工厂方法模式。比如产品对象预期可能会经常造成增加或删减等,使用工厂方法模式来设计可以使系统更加灵活。
Java中的很多集合容器比如ArrayList等都不是线程安全的,在多线程并发情况下可能出现线程安全问题。
我们可以使用Collections.synchronizedList(new ArrayList())
获得一个线程安全的容器。这个就用到了工厂方法模式。
java.util.Collection
接口中的iterator()
方法就是一个工厂方法。对于iterator方法来说Collection就是一个根抽象工厂,下面还有List等接口作为抽象工厂,再往下有ArrayList等具体工厂。java.util.Iterator
接口是根抽象产品,下面有ListIterator等抽象产品,还有ArrayListIterator等作为具体产品。
获得产品代码:
|
|
基本的UML类图如下所示:
在使用JDBC进行数据库开发时,如果数据库由MySQL改为Oracle或其他,则只需要改一下数据库驱动名称就可以,其他都不用修改(前提是使用的都是标准SQL语句)。或者在Hibernate框架中,更换数据库方言也是类似道理。
如果需要设计一个连接邮件服务器的框架,那么就要考虑到连接邮件服务器有几种方式:POP3、SMTP、HTTP。就可以定义一个连接邮件服务器接口,在此接口中定义一些对邮件操作的接口方法,把这三种连接方式封装成产品类,实现接口中定义的抽象方法。再定义抽象工厂和具体工厂,当选择不同的工厂时,对应到产生相应的连接邮件产品对象。采用这种工厂方法模式的设计,就可以做到良好的扩展性。比如某些邮件服务器提供了WebService接口,只需要增加一个产品类和工厂类就可以了,而不需要修改原来代码。
在一个项目中,需要实现的需求是这样的:公司有很多站点,需要带着指定的一些关键字去网站上爬取广告、文章、产品等信息解析下来,而不同的站点风格各异,爬取和解析的方式也各不相同。
所以这里就用到了工厂方法模式,抽象出一个抽象产品,定义公共接口,爬取解析某个站点就封装成一个产品对象。再抽象出抽象工厂,使一些具体工厂实现抽象工厂定义的调用产品接口,每个具体工厂对应一个产品。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/03/09/factory-method-pattern/
转载请注明作者和文章出处,谢谢。
]]>原文链接:
http://tianweili.github.io/blog/2015/03/08/simple-factory-pattern/
简单工厂模式属于创建型模式,又叫做静态工厂方法(static factory method)。但是它并没有归为23种GOF设计模式其中。
简单工厂模式是由工厂对象来决定创建哪一种产品类的实例。
简单说就是工厂对象根据传入的参数,动态的决定创建哪一种产品类的实例,而这些产品类继承自一个父类或一个接口。
简单工厂模式的一个基本的UML类图如下所示:
在这个UML类图中包含以下角色:
工厂(Factory)
这是简单工厂模式的核心,由它来负责实现创建所有实例的逻辑。工厂对象用来被外界调用,根据传入的参数来决定创建哪一个产品对象。
抽象产品(Abstract Product)
抽象类或接口。是所有具体产品对象的父类,由它来定义所有具体产品的公共接口。
具体产品(Concrete Product)
具体产品是简单工厂模式的创建目标,所有创建的对象都是某个具体产品类的实例。
|
|
输出结果:
|
|
客户类和工厂类分开。消费者任何时候需要某种产品,只需向工厂请求即可。消费者无须修改就可以接纳新产品。
外界不用关注对象创建逻辑,产品对象具体的创建过程由工厂来实现。外界直接给定信息来决定创建哪个产品对象。
明确了各自的职责,有利于整个软件体系结构的优化。
违反高内聚责任分配原则,将所有的创建逻辑都集中在了工厂类身上。
如果需要添加新的产品类,则需要修改工厂类。
当产品类不断增多,工厂类对产品类型的判断条件过多交织在了一起,会造成逻辑过于复杂,对系统的扩展和维护不利。
工厂类创建的产品对象比较少。
外界只需要传入工厂类参数来获得产品对象,对于产品对象的创建过程不关心。
简单工厂模式很容易违反高内聚责任分配原则,所以只是在一些很简单的情况下使用。
在工作中,有一个项目模块中要求的功能是:登录邮箱,获取邮件,然后抓取邮件中的附件,下载下来,如果是压缩包,则进行解压,然后进行相应处理。在解压时就用到了简单工厂模式。附件压缩包格式不一,有zip,rar等格式,则把每一种格式的解压和处理都放到一个产品对象中,然后使用一个工厂类来决定创建哪一个产品进行相应的处理。
由于这个压缩包解压方式并不多,判断逻辑并不太复杂,所以也没必要用工厂方法模式,增加代码复杂度。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/03/08/simple-factory-pattern/
转载请注明作者和文章出处,谢谢。
]]>原文链接:
http://tianweili.github.io/blog/2015/03/03/log4j/
Log4j由三个重要的组件构成:日志信息的优先级,日志信息的输出目的地,日志信息的输出格式。日志信息的优先级从高到低有ERROR
、WARN
、INFO
、DEBUG
,分别用来指定这条日志信息的重要程度;日志信息的输出目的地指定了日志将打印到控制台还是文件中;而输出格式则控制了日志信息的显示内容。
其实也可以完全不使用配置文件,而是在代码中配置Log4j环境。但是,使用配置文件将使您的应用程序更加灵活。
Log4j支持两种配置文件格式,一种是XML格式
的文件,一种是properties格式
的文件。以下介绍使用properties格式做为配置文件的方法:
基本的格式如下:
|
|
语法为:
|
|
其中,level 是日志记录的优先级,分为OFF
、FATAL
、ERROR
、WARN
、INFO
、DEBUG
、ALL
或者你定义的级别。Log4j建议只使用四个级别,优先级从高到低分别是ERROR
、WARN
、INFO
、DEBUG
。通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关。比如在这里定义了INFO级别,则应用程序中所有DEBUG级别的日志信息将不被打印出来。
appenderName就是指定日志信息输出到哪个地方。您可以同时指定多个输出目的地。
Log4j提供的appender有以下几种:
org.apache.log4j.ConsoleAppender
(控制台),org.apache.log4j.FileAppender
(文件),org.apache.log4j.DailyRollingFileAppender
(每天产生一个日志文件),org.apache.log4j.RollingFileAppender
(文件大小到达指定尺寸的时候产生一个新的文件),org.apache.log4j.WriterAppender
(将日志信息以流格式发送到任意指定的地方)其中,每种appender可以定义的option1如下:
Threshold=WARN
:指定日志消息的输出最低层次。 ImmediateFlush=true
:默认值是true,意谓着所有的消息都会被立即输出。 Target=System.err
:默认情况下是:System.out,指定输出控制台 Threshold=WARN
:指定日志消息的输出最低层次。 ImmediateFlush=true
:默认值是true,意谓着所有的消息都会被立即输出。 File=mylog.txt
:指定消息输出到mylog.txt文件。 Append=false
:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。 Threshold=WARN
:指定日志消息的输出最低层次。 ImmediateFlush=true
:默认值是true,意谓着所有的消息都会被立即输出。 File=mylog.txt
:指定消息输出到mylog.txt文件。 Append=false
:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。 DatePattern='.'yyyy-ww
:每周滚动一次文件,即每周产生一个新的文件。当然也可以指定按月、周、天、时和分。即对应的格式如下: '.'yyyy-MM
: 每月 '.'yyyy-ww
: 每周 '.'yyyy-MM-dd
: 每天 '.'yyyy-MM-dd-a
: 每天两次 '.'yyyy-MM-dd-HH
: 每小时 '.'yyyy-MM-dd-HH-mm
: 每分钟 Threshold=WARN
:指定日志消息的输出最低层次。 ImmediateFlush=true
:默认值是true,意谓着所有的消息都会被立即输出。 File=mylog.txt
:指定消息输出到mylog.txt文件。 Append=false
:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。 MaxFileSize=100KB
: 后缀可以是KB, MB 或者是 GB. 在日志文件到达该大小时,将会自动滚动,即将原来的内容移到mylog.log.1文件。 MaxBackupIndex=2
:指定可以产生的滚动文件的最大数。Log4j提供的layout有以下几种:
org.apache.log4j.HTMLLayout
(以HTML表格形式布局),org.apache.log4j.PatternLayout
(可以灵活地指定布局模式),org.apache.log4j.SimpleLayout
(包含日志信息的级别和信息字符串),org.apache.log4j.TTCCLayout
(包含日志产生的时间、线程、类别等等信息)在配置文件中可以通过log4j.appender.A1.layout.ConversionPattern
设置日志输出格式。
参数:
%p
: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL, %d
: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921 %r
: 输出自应用启动到输出该log信息耗费的毫秒数 %c
: 输出日志信息所属的类目,通常就是所在类的全名 %t
: 输出产生该日志事件的线程名 %l
: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10) %x
: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。 %%
: 输出一个”%”字符 %F
: 输出日志消息产生时所在的文件名称 %L
: 输出代码中的行号 %m
: 输出代码中指定的消息,产生的日志具体信息 %n
: 输出一个回车换行符,Windows平台为”\r\n”,Unix平台为”\n”输出日志信息换行 可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:
%20c
:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。 %-20c
:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,”-”号指定左对齐。 %.30c
:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。 %20.30c
:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边交远销出的字符截掉。 在程序中使用Log4j之前,首先要将commons-logging.jar
和logging-log4j-1.2.9.jar
导入到classpath中,并将log4j.properties放于src根目录中。接下来就可以使用了。
使用Log4j,第一步就是获取日志记录器,这个记录器将负责控制日志信息。其语法为:
|
|
通过指定的名字获得记录器,如果必要的话,则为这个名字创建一个新的记录器。Name一般取本类的名字,比如:
|
|
注:推荐使用commons-logging结合log4j进行日志记录。
|
|
当上两个必要步骤执行完毕,您就可以轻松地使用不同优先级别的日志记录语句插入到您想记录日志的任何地方,其语法如下:
|
|
LOG4J的配置之简单使它遍及于越来越多的应用中了:Log4J配置文件实现了输出到控制台、文件、回滚文件、发送日志邮件、输出到数据库日志表、自定义标签等全套功能。择其一二使用就够用了。
|
|
本人在使用HtmlUnit组件的时候,这个组件输出的各种日志刷屏得那叫一个酸爽。即便在使用它提供的一些API禁掉了一些日志输出,但还是会输出很多无用的日志信息。
那么我们怎么能够限制只让指定的类才有资格输出日志呢?方法如下:
注:如果设置了日志输出log4j.rootLogger,并且后面设置了指定类输出,则对于指定类的输出配置会覆盖总的配置。举个栗子:总配置设置优先级为INFO,但是某个类具体设置为WARN,那么最后这个类输出优先级为WARN。
|
|
在系统上线后,有时候遇到系统故障,这时候就可以登录服务器查看系统日志来排查问题。但是需要登录服务器,下载查找相关异常日志比较麻烦。而且没有监控的话,也无法实时了解到系统是否正常运行。那么有没有一种好办法将系统异常信息实时反馈给相关人员呢?
下面讲的就是借助Log4J来记录程序运行日志,当一旦发现系统异常或者自己定义的其他一些情况发生时,及时通过邮件形式发送给相关负责人,并附上相关的系统日志信息,这样负责人就可以实时便捷的监控到系统的状态和相关异常信息。
采用这种方式的优点有:
导入依赖jar包:
1.log4j-1.2.17.jar
目前log4j最新版是1.2.17。低版本的log4j无法实现邮件发送功能,因为版本低于log4j-1.2.14.jar的不支持SMTP认证。
发送邮件的一个重要的类是SMTPAppender,在1.2.8的版本中,SMTPAppender没有SMTPUsername和SMTPPassword属性。这两个属性分别是登录SMTP服务器发送认证的用户名和密码。
2.mail-1.4.jar
发送邮件当然需要用到JavaMail包啦。
3.activation-1.1.jar
配置log4j.properties文件,如下:
|
|
每行配置详解:
如果需要抄送给某人,则添加如下配置:
|
|
想让邮件内容日志以HTML格式来输出,则配置:
|
|
输出到邮件中显示如下图:
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/03/03/log4j/
转载请注明作者和文章出处,谢谢。
]]>饿汉式
和懒汉式
这两种实现方式。但是除了这两种方式,本文还会介绍其他几种实现单例的方式,让我们来一起看看吧。
原文链接:
http://tianweili.github.io/blog/2015/03/02/singleton-pattern/
单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。
许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。
单例的实现主要是通过以下两个步骤:
单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。
|
|
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
|
|
这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
|
|
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
|
|
解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
|
|
由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
|
|
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
优点:线程安全;延迟加载;效率较高。
|
|
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
|
|
借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过。
系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/03/02/singleton-pattern/
转载请注明作者及出处,谢谢。
]]>原文链接:
http://tianweili.github.io/blog/2015/02/25/linux-sort/
sort命令是根据不同的数据类型以行为单位对数据进行排序。
sort的默认比较规则是从首字符向后,按照ASCII码值进行比较,将结果按照升序输出。
sort命令的基本格式如下:
|
|
sort命令可使用的参数有:
|
|
下面将会对这些参数进行介绍,其中简单的参数就不再赘述了。
sort是把排序后结果输出到标准输出,所以需要使用重定向将结果写入指定的文件,比如sort file > newfile
。
但是重定向的方式在遇到这种需求就无能为力了——把结果输出到原文件中。
如果还是使用重定向的方式,则会把原文件给清空。
而使用-o
参数则可以完美解决这个问题:
|
|
对于某些有固定格式的文件,比如:
如果想以第二列数值大小降序输出,则需要使用-t和-k参数了。其中-k指定分隔符,-k指定待排序的列。
|
|
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/02/25/linux-sort/
转载请注明作者及出处,谢谢。
]]>原文链接:http://tianweili.github.io/blog/2015/02/24/linux-awk/
awk命令主要用于文本分析。它的处理方式是读入文本,将每行记录以一定的分隔符(默认为空格)分割成不同的域,然后对不同的域进行各种处理与输出。
awk命令的一个基本格式如下:
|
|
无论awk命令简单还是复杂,基本的格式如上所示。其中引号为必须,引号内代表一个awk程序。大括号非必须,括起来用于根据特定的模式对一系列指令进行分组。pattern是在数据中查找内容,支持正则匹配。action对查找出来的记录执行相应的处理,比如打印和输出等。
|
|
其中的-F
指令是可选的,后面跟着指定的域分隔符,比如tab键等(默认是空格)。后面的commands
是真正的awk命令。input-file(s)
代表输入的一个或多个文件
命令行调用方式是最经常使用的一种方式,也是本文所讲的重点。
把平时所写的shell脚本的首行#!/bin/sh
换成#!/bin/awk
。把所有的awk命令插入脚本中,通过调用脚本来执行awk命令。
把所有的awk命令插入单独的文件中,然后通过以下命令调用awk:
|
|
其中-f
指定了要调用的包含awk命令的文件。
打印当前目录下所有的文件名和文件大小列表,以tab键分割:
|
|
$0变量是指当前一行记录,$1是指第一个域数据,$2指第二个域数据……以此类推。
awk提供了print与printf两种打印输出的函数。
print的参数可以是变量、数值和字符串。参数用逗号分割,字符串必须用双引号引用。
printf与C语言中的printf函数类似,可以用来格式化字符串。
|
|
|
|
|
|
BEGIN...END
语句的执行流程是,awk命令读入数据,然后从BEGIN语句开始,依次读取每一行记录,并打印相应的域,当所有记录都处理后再执行END语句后的程序。也就是说BEGIN...END
语句块中的内容在读取数据过程中会反复执行,直到数据读取完成。
下面的例子表示打印当前目录下,所有以.bat后缀结尾的文件名列表:
|
|
awk有许多内置变量用来设置环境变量信息,这些变量都可以被改变。常用的内置变量和作用如下所示:
|
|
awk中的内置变量都是很有用处的,可以直接使用。比如上面讲过的指定分隔符操作就可以用FS变量来代替:
|
|
下面会有很多实用awk内置变量的例子。
awk可以自定义变量,并参与运算。
比如统计当前目录下列出的文件总大小,以M为单位显示出来:
|
|
注意此统计没有把文件夹下的所有文件算在内。
自定义的变量有时候可以不用作初始化操作,不过正规起见,还是建议作初始化操作为好。
awk中的条件语句跟C语言类似,声明方式如下:
|
|
看下面例子,将第三列为12,第六列为0的行打印输出:
|
|
awk中的循环语句同样与C语言中的类似,支持while、do/while、for、break、continue关键字。
看下面的例子,输出每行的行号和第一列的数据:
|
|
看下面例子,统计第六列每一个值出现的次数:
|
|
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/02/24/linux-awk/
转载请注明作者及出处,谢谢。
]]>原文链接:http://tianweili.github.io/blog/2015/02/15/open-close-principle/
Software entities like classes, modules and functions shoule be open for extension but closed for modifications.(一个软件实体如类,模块和函数应该对扩展开放,对修改关闭。)
开闭原则的定义很短,就是对扩展开放,对修改关闭。但是为什么要遵守这一个原则呢?
做过实际项目的筒子们应该都会深有体会,一个软件在其生命周期内都会发生很多变化,这几乎是不可避免的。无论是需求的变化、业务逻辑的变化、程序代码的变化等等,这些变化都有可能对整个软件的稳定性造成一定的威胁。
而开闭原则就是应对这些变化的,它告诉我们应该通过扩展来实现变化,而不是通过修改已有的代码。
我记得上中学的时候,每次考试出成绩的时候老师都会站在讲台上一遍发卷子一遍念出每位同学的分数,下面的学生们心理都暗自捏了一把汗。对于念出自己分数的同学,如果考得好,走上讲台领会卷子,自然心中倍感自豪。但是如果考得不好,一路上感觉都很惭愧啊,此时就很不希望老师当众把自己分数念出来。
下面我们来看看这种场景下的UML类图:
程序代码如下:
学生类,每个学生都有姓名和成绩:
|
|
老师类,每个老师管理一群的学生:
|
|
场景类:
|
|
输出结果:
最后王同学看到自己成绩不错,裂开嘴笑了。而张同学就惨了,差点不及格,在全班同学的哄笑中领走了试卷。
因为这种当众念出学生成绩的行为可能为伤害到一些成绩不是那么好的同学的自尊心,所以临时决定要对这种念出分数的方式进行改革。把同学们的成绩按照级别来分,分别有优秀,良好,一般,及格,不及格这几种。这样可以照顾成绩不好的同学的自尊心。
那么我们怎么根据这种需求来更改我们的软件呢?
有人说直接修改Student类的getGrade方法不就行了嘛。可能有很多人在实际项目中都是这么做的,但是这就违背了开闭原则,开闭原则要求我们尽量不要修改已有的代码,尽量通过扩展来实现改变。
因为我们举的例子比较简单,但是在实际复杂的项目中,首先理解已有的方法业务逻辑可能就不是一件容易的事情,更何况如果再有其他实体类需要调用你已有的方法,如果你修改了这个方法,所有调用这个方法的代码都得需要找到并修改,这是一件既困难又不安全的做法。
再者说修改了getGrade方法,那么这下只能知道学生成绩处于哪一个层次了,老师也无法知道学生的具体分数了。
那么我们应该怎么做呢?
既然前门我们知道了开闭原则,我们就要用上。我们可以通过扩展已有的代码来实现改变,可以增加一个LevelStudent来继承Student,并扩展修改getGrade方法。
修改后的UML类图如下所示:
增加LevelStudent类代码:
|
|
开闭原则是对扩展开放,对修改关闭。
开闭原则的主旨是为了拥抱变化。
在六大原则中,开闭原则只是一个思想,没有具体实际操作方法。其他五大原则都是为了实现这个开闭思想的一些方法和工具。
想要遵守开闭原则,就需要一个设计合理的系统。可以说在做系统设计的时候就要考虑到未来的扩展和改变。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/02/15/open-close-principle/
转载请注明作者及出处,谢谢。
]]>原文链接:http://tianweili.github.io/blog/2015/02/12/law-of-demeter/
迪米特法则:Law Of Demeter,LoD。
也被称为最少知识原则,Least Knowledge Principle,LKP。
就是说一个对象应该对其他对象保持最少的了解。正如最少知识原则这个定义一样,一个类应该对其耦合的其他类或所调用的类知道得最少。所耦合的类内部无论如何复杂,怎么实现的我都不需要知道,我只调用你public出来的这些方法,其他都不用知道。
另外可以解释一下开头提到的只与直接的朋友通信,什么叫直接的朋友呢?我们继续看。
光看定义可能无法完全理解它所表达的含义,以及在什么场景下才需要使用这个迪米特法则。现在我们就来举个“栗子”。
现在市面上各种人脉书上很多都会提到“六度人脉”这个理论,这个理论说的是你与世界上任何一个人中间只隔了六个人。也就是说你想找任何一个人,无论这个人是政界要人,还是商界巨鳄,抑或是明星名人,你最多只通过六个人就可以联系到他(想想还有点小激动呢-_-#)。
我们暂且不论这个理论是对是错,在现实生活中我们也经常遇到这样的情况。比如你想办一件事情,但是凭借你的能力是做不到的,而你周围的朋友也无法帮你办到。但是恰好你有一个朋友认识有另外一个朋友可以办得成此事,那么你只有拜托你这位朋友中间牵线搭桥,让他的朋友帮你办好此事。
在这个例子中,我们就暂且定义你为A,你的朋友为B,你朋友的朋友为C好了。
我们先来看看表达此种关系的UML类图:
实现代码如下:
1.类A和类B是好朋友,能找到类B来帮忙:
|
|
2.类B和类C是好朋友,能知道类C来帮忙:
|
|
3.类C能够办成此事:
|
|
4.场景类
|
|
运行结果如下:
上面的输出虽然是把事情成功办好了,但是仔细看业务逻辑明显是不对的。A和C又不是好朋友,为什么在类A中会出现类C呢?他们又互相不认识。
看到这里很多人都会明白,这种场景在实际开发中是非常常见的一种情况。对象A需要调用对象B的方法,对象B有需要调用对象C的方法……就是常见的getXXX().getXXX().getXXX()……类似于这种代码。如果你发现你的代码中也有这样的代码,那就考虑下是不是违反迪米特法则,是不是要重构一下了。
为了符合迪米特法则,也为了让业务逻辑能够说得通,我们把上面的例子稍微修改一下。
UML类图如下:
代码如下:
1.类A和类B是好朋友,能找到类B来帮忙:
|
|
2.类B和类C是好朋友,能知道类C来帮忙:
|
|
3.类C能够办成此事:
|
|
4.场景类
|
|
运行结果如下:
上面代码只是修改了下类A和B的work方法,使之符合了迪米特法则:
迪米特法则的目的是让类之间解耦,降低耦合度。只有这样,类的可复用性才能提高。
但是迪米特法则也有弊端,它会产生大量的中转类或跳转类,导致系统的复杂度提高。
所以我们不要太死板的遵守这个迪米特法则,在系统设计的时候,在弱耦合和结构清晰之间反复权衡。尽量保证系统结构清晰,又能做到低耦合。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/02/12/law-of-demeter/
转载请注明作者及出处,谢谢。
]]>原文链接:http://tianweili.github.io/blog/2015/02/10/interface-segregation-principle/
接口隔离原则比较简单,有两种定义:
- Clients should not be forced to depend upon interfaces that they don’t use.(客户端不应该强行依赖它不需要的接口)
- The dependency of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)
其实上述两种定义说的是同一种意思。客户端不应该依赖它不需要的接口,意思就是说客户端只要依赖它需要的接口,它需要什么接口,就提供什么接口,不提供多余的接口。“类间的依赖关系应该建立在最小的接口上”也表达这一层意思。通俗的讲就是:接口中的方法应该尽量少,不要使接口过于臃肿,不要有很多不相关的逻辑方法。
通过简单的代码还原开篇的问题,代码如下:
|
|
运行结果:
从以上代码可以看出,如果接口过于臃肿,不同业务逻辑的抽象方法都放在一个接口内,则会造成它的实现类必须实现自己并不需要的方法,这种设计方式显然是不妥当的。所以我们要修改上述设计方法,把接口I拆分成3个接口,使得实现类只需要实现自己需要的接口即可。只贴出修改后的接口和实现类的代码,修改代码如下:
|
|
到了这里,有些人可能觉得接口隔离原则与单一职责原则很相似,其实不然。
第一,单一职责原则注重的是职责;而接口隔离原则注重对接口依赖的隔离。
第二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口,主要针对抽象,针对程序整体框架的构建。
原则是前人经验的总结,在软件设计中具有一定的指导作用,但是不能完全照搬这些原则。对于接口隔离原则来说,接口尽量小,但是也要有限度。对接口进行细化可以提高程序设计灵活性是不争的事实,但是如果过小,则会造成接口数量过多,使设计复杂化,所以一定要适度。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/02/10/interface-segregation-principle/
转载请注明作者及出处,谢谢。
]]>原文链接:http://tianweili.github.io/blog/2015/02/07/dependence-inversion-principle/
英文缩写DIP(Dependence Inversion Principle)。
原始定义:
High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.
翻译过来就三层含义:
抽象:即抽象类或接口,两者是不能够实例化的。
细节:即具体的实现类,实现接口或者继承抽象类所产生的类,两者可以通过关键字new直接被实例化。
现在我们来通过实例还原开篇问题的场景,以便更好的来理解。下面代码描述了一个简单的场景,Jim作为人有吃的方法,苹果有取得自己名字的方法,然后实现Jim去吃苹果。
代码如下:
|
|
运行结果:Jim eat apple
上面代码看起来比较简单,但其实是一个非常脆弱的设计。现在Jim可以吃苹果了,但是不能只吃苹果而不吃别的水果啊,这样下去肯定会造成营养失衡。现在想让Jim吃香蕉了(好像香蕉里含钾元素比较多,吃点比较有益),突然发现Jim是吃不了香蕉的,那怎么办呢?看来只有修改代码了啊,由于上面代码中Jim类依赖于Apple类,所以导致不得不去改动Jim类里面的代码。那如果下次Jim又要吃别的水果了呢?继续修改代码?这种处理方式显然是不可取的,频繁修改会带来很大的系统风险,改着改着可能就发现Jim不会吃水果了。
上面的代码之所以会出现上述难堪的问题,就是因为Jim类依赖于Apple类,两者是紧耦合的关系,其导致的结果就是系统的可维护性大大降低。要增加香蕉类却要去修改Jim类代码,这是不可忍受的,你改你的代码为什么要动我的啊,显然Jim不乐意了。我们常说要设计一个健壮稳定的系统,而这里只是增加了一个香蕉类,就要去修改Jim类,健壮和稳定还从何谈起。
而根据依赖倒置原则,我们可以对上述代码做些修改,提取抽象的部分。首先我们提取出两个接口:People和Fruit,都提供各自必需的抽象方法,这样以后无论是增加Jim人类,还是增加Apple、Banana等各种水果,都只需要增加自己的实现类就可以了。由于遵循依赖倒置原则,只依赖于抽象,而不依赖于细节,所以增加类无需修改其他类。
代码如下:
|
|
运行结果:
到了这里,我们对依赖倒置原则的“依赖”就很好理解了,但是什么是“倒置”呢。是这样子的,刚开始按照正常人的一般思维方式,我想吃香蕉就是吃香蕉,想吃苹果就吃苹果,编程也是这样,都是按照面向实现的思维方式来设计。而现在要倒置思维,提取公共的抽象,面向接口(抽象类)编程。不再依赖于具体实现了,而是依赖于接口或抽象类,这就是依赖的思维方式“倒置”了。
对象的依赖关系有三种方式来传递:
就是我们上面代码所展示的那样。
在构造函数中的需要传递的参数是抽象类或接口的方式实现。代码如下:
|
|
在我们设置的setXXX方法中的参数为抽象类或接口,来实现传递依赖对象。代码如下:
|
|
从上面的代码修改过程中,我们可以看到由于类之间松耦合的设计,面向接口编程依赖抽象而不依赖细节,所以在修改某个类的代码时,不会牵涉到其他类的修改,显著降低系统风险,提高系统健壮性。
还有一个优点是,在我们实际项目开发中,都是多人团队协作,每人负责某一模块。比如一个人负责开发People模块,一人负责开发Fruit模块,如果未采用依赖倒置原则,没有提取抽象,那么开发People模块的人必须等Fruit模块开发完成后自己才能开发,否则编译都无法通过,这就是单线程的开发。为了能够两人并行开发,设计时遵循依赖倒置原则,提取抽象,就可以大大提高开发进度。
说到底,依赖倒置原则的核心就是面向接口编程的思想,尽量对每个实现类都提取抽象和公共接口形成接口或抽象类,依赖于抽象而不要依赖于具体实现。依赖倒置原则的本质其实就是通过抽象(抽象类或接口)使各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。但是这个原则也是6个设计原则中最难以实现的了,如果没有实现这个原则,那么也就意味着开闭原则(对扩展开放,对修改关闭)也无法实现。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/02/07/dependence-inversion-principle/
转载请注明作者及出处,谢谢。
]]>原文链接:http://tianweili.github.io/blog/2015/02/04/liskov-substitution-principle/
前面说过的单一职责原则,从字面意思就很好理解,但是里氏替换原则就有点让人摸不着头脑。查过资料后发现原来这项原则最早是在1988年,由麻省理工学院一位姓里的女士(Liskov)提出来的。
英文缩写:LSP (Liskov Substitution Principle)。
严格的定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。
通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象。
更通俗的定义:子类可以扩展父类的功能,但不能改变父类原有的功能。
代码示例
|
|
里氏替换原则包含以下4层含义:
在我们做系统设计时,经常会设计接口或抽象类,然后由子类来实现抽象方法,这里使用的其实就是里氏替换原则。子类可以实现父类的抽象方法很好理解,事实上,子类也必须完全实现父类的抽象方法,哪怕写一个空方法,否则会编译报错。
里氏替换原则的关键点在于不能覆盖父类的非抽象方法。父类中凡是已经实现好的方法,实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些规范,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。
在面向对象的设计思想中,继承这一特性为系统的设计带来了极大的便利性,但是由之而来的也潜在着一些风险。就像开篇所提到的那一场景一样,对于那种情况最好遵循里氏替换原则,类C1继承类C时,可以添加新方法完成新增功能,尽量不要重写父类C的方法。否则可能带来难以预料的风险,比如下面一个简单的例子还原开篇的场景:
|
|
运行结果:2+1=1
上面的运行结果明显是错误的。类C1继承C,后来需要增加新功能,类C1并没有新写一个方法,而是直接重写了父类C的func方法,违背里氏替换原则,引用父类的地方并不能透明的使用子类的对象,导致运行结果出错。
在继承父类属性和方法的同时,每个子类也都可以有自己的个性,在父类的基础上扩展自己的功能。前面其实已经提到,当功能扩展时,子类尽量不要重写父类的方法,而是另写一个方法,所以对上面的代码加以更改,使其符合里氏替换原则,代码如下:
|
|
运行结果:2-1=1
代码示例
|
|
运行结果:执行父类…
注意Son类的func方法前面是不能加@Override注解的,因为否则会编译提示报错,因为这并不是重写(Override),而是重载(Overload),因为方法的输入参数不同。重写和重载的区别在Java面向对象详解一文中已作解释,此处不再赘述。
代码示例:
|
|
执行结果:{h=执行子类…}
继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了一些弊端,它增加了对象之间的耦合性。因此在系统设计时,遵循里氏替换原则,尽量避免子类重写父类的方法,可以有效降低代码出错的可能性。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/02/04/liskov-substitution-principle/
转载请注明作者及出处,谢谢。
]]>原文链接:http://tianweili.github.io/blog/2015/02/25/single-responsibility-principle/
单一职责原则的英文名称是Single Responsibility Principle,简称是SRP。SRP原则的解释是:There should never be more than one reason for a class to change。定义很简单,即不能存在多于一个导致类变更的原因。简单的说就是一个类只负责一项职责。
在软件设计中,秉承着“高内聚,低耦合”的思想,让一个类仅负责一项职责,如果一个类有多于一项的职责,那么就代表这个类耦合性变高了,这些职责耦合在了一起,这是比较脆弱的设计。因为一旦某一项职责发生了改变,需要去更改代码,那么有可能会引起其他职责改变。所谓牵一发而动全身,这显然是我们所不愿意看到的,所以我们会把这个类分拆开来,由两个类来分别维护这两个职责,这样当一个职责发生改变,需要修改时,不会影响到另一个职责。
需要说明的是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。
看到上面所述,或许有人会说这么简单谁不知道。的确,很多程序员即使没有学过设计模式,不知道单一职责原则,在编程的时候,在设计软件时也会有意识的遵循这一原则。因为谁都不希望修改一个地方会引发另外一个地方出现问题,而避免这种问题的最好处理方式就是设计时遵循单一职责原则。但是,我认为单一职责原则的难点是在于职责范围的认定。关于职责的认定是一个仁者见仁智者见智的话题,在实际开发中也会引起程序员之间的争论。有的人认为这些功能方法的实现目的很相似,必须要放在一个类中,有的人认为方法差别很大,必须要分拆成多个类,在多个类里面来实现。
还有职责的扩散问题。软件一开发完上线后并不是一成不变的,随着社会的进步,需求的变更,软件的功能可能要做些维护更改,有时候会遇到职责扩散。所谓的职责扩散就是因为某种原因,职责R被分化为粒度更细的R1和R2。
比如类C只负责一个职责R,这是符合单一职责原则的。但是后来需要把职责R拆分为职责R1和职责R2,那么这时候是否需要死守着单一职责原则,把类C也拆开为C1和C2。接着如果R1又需要细化为R11和R12呢……
我们必须要意识到,一味的遵守单一职责原则,不停的分拆类所付出的开销是很大的。这时候就涉及到平衡的问题,平衡单一职责原则与修改造成的开销。我的观点是如果一个方法逻辑不复杂的情况下,可以修改方法实现,否则要拆分为两个方法,遵循方法级别的单一职责原则;如果一个类方法不多的情况下,可以只增加方法,而不用分拆为多个类,否则要拆分为多个类,遵循类级别的单一职责原则。
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/02/25/single-responsibility-principle/
转载请注明作者及出处,谢谢。
]]>原文链接:http://tianweili.github.io/blog/2015/01/29/use-jxl-produce-excel/
jxl是一个韩国人写的java操作excel的工具, 在开源世界中,有两套比较有影响的API可供使用,一个是POI,一个是jExcelAPI。其中jExcelAPI功能相对POI比较弱一点。但jExcelAPI对中文支持非常好,API是纯Java的,并不依赖Windows系统,即使运行在Linux下,它同样能够正确的处理Excel文件。另外需要说明的是,这套API对图形和图表的支持很有限,而且仅仅识别PNG格式。
网上下载jxl.jar包,然后导入工程项目lib中,即可使用。
以下实例是生成一个名为“test.xls”的Excel文件,其中第一个工作表被命名为“第一页”。编译执行后,会产生一个Excel文件。
|
|
Excel中很重要的一部分是对单元格的操作,比如行高、列宽、单元格合并等,所幸jExcelAPI提供了这些支持。这些操作相对比较简单,下面只介绍一下相关的API。
合并既可以是横向的,也可以是纵向的。合并后的单元格不能再次进行合并,否则会触发异常。
|
|
|
|
字符串的格式化涉及到的是字体、粗细、字号等元素,这些功能主要由WritableFont和WritableCellFormat类来负责。
WritableFont有非常丰富的构造子方法,供不同情况下使用,jExcelAPI的java-doc中有详细列表,这里不再列出。
WritableCellFormat类非常重要,通过它可以指定单元格的各种属性,后面的单元格格式化中会有更多描述。
|
|
在WritableCellFormat类中,还有一个很重要的方法是指定数据的对齐方式,比如针对我们上面的实例,可以指定:
|
|
|
|
程序的输出结果是:
|
|
Cell接口的方法还可以获取单元格行、列位置,单元格是否隐藏等属性。具体的参考jxl的API。
修改Excel文件除了打开文件的方式不同之外,其他与创建Excel是一样的。
|
|
附上一个导出文件例子。
|
|
|
|
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/01/29/use-jxl-produce-excel/
]]>原文链接:http://tianweili.github.io/blog/2015/01/27/java-listener/
之前写了一篇关于Filter的文章,现在再来一篇Listener的,Filter和Listener在项目中是经常用到的,巧妙的使用可以达到事半功倍的效果。故把两者的用法总结一下。
监听器Listener就是在application,session,request三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。
Listener是Servlet的监听器,可以监听客户端的请求,服务端的操作等。
主要有以下三类:
ServletContextListener:用于对Servlet整个上下文进行监听(创建、销毁)。
|
|
ServletContextAttributeListener:对Servlet上下文属性的监听(增删改属性)。
|
|
Session属于http协议下的内容,接口位于javax.servlet.http.*包下。
HttpSessionListener接口:对Session的整体状态的监听。
|
|
HttpSessionAttributeListener接口:对session的属性监听。
|
|
session的销毁有两种情况:
1.session超时,web.xml配置:
|
|
2.手工使session失效
|
|
ServletRequestListener:用于对Request请求进行监听(创建、销毁)。
|
|
ServletRequestAttributeListener:对Request属性的监听(增删改属性)。
|
|
Listener配置信息必须在Filter和Servlet配置之前,Listener的初始化(ServletContentListener初始化)比Servlet和Filter都优先,而销毁比Servlet和Filter都慢。
|
|
|
|
ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。
ContextLoaderListener如何查找ApplicationContext.xml的配置位置以及配置多个xml:如果在web.xml中不写任何参数配置信息,默认的路径是”/WEB-INF/applicationContext.xml”,在WEB-INF目录下创建的xml文件的名称必须是applicationContext.xml(在MyEclipse中把xml文件放置在src目录下)。如果是要自定义文件名可以在web.xml里加入contextConfigLocation这个context参数。
|
|
Spring使用Log4jConfigListener的好处:
|
|
这个监听器的作用是在web应用关闭时刷新JDK的JavaBeans的Introspector缓存,以确保Web应用程序的类加载器以及其加载的类正确的释放资源。
如果JavaBeans的Introspector已被用来分析应用程序类,系统级的Introspector缓存将持有这些类的一个硬引用。因此,这些类和Web应用程序的类加载器在Web应用程序关闭时将不会被垃圾收集器回收!而IntrospectorCleanupListener则会对其进行适当的清理,已使其能够被垃圾收集器回收。
唯一能够清理Introspector的方法是刷新整个Introspector缓存,没有其他办法来确切指定应用程序所引用的类。这将删除所有其他应用程序在服务器的缓存的Introspector结果。
在使用Spring内部的bean机制时,不需要使用此监听器,因为Spring自己的introspection results cache将会立即刷新被分析过的JavaBeans Introspector cache,而仅仅会在应用程序自己的ClassLoader里面持有一个cache。虽然Spring本身不产生泄漏,注意,即使在Spring框架的类本身驻留在一个“共同”类加载器(如系统的ClassLoader)的情况下,也仍然应该使用使用IntrospectorCleanupListener。在这种情况下,这个IntrospectorCleanupListener将会妥善清理Spring的introspection cache。
应用程序类,几乎不需要直接使用JavaBeans Introspector,所以,通常都不是Introspector resource造成内存泄露。相反,许多库和框架,不清理Introspector,例如: Struts和Quartz。
需要注意的是一个简单Introspector泄漏将会导致整个Web应用程序的类加载器不会被回收!这样做的结果,将会是在web应用程序关闭时,该应用程序所有的静态类资源(比如:单实例对象)都没有得到释放。而导致内存泄露的根本原因其实并不是这些未被回收的类!
注意:IntrospectorCleanupListener应该注册为web.xml中的第一个Listener,在任何其他Listener之前注册,比如在Spring’s ContextLoaderListener注册之前,才能确保IntrospectorCleanupListener在Web应用的生命周期适当时机生效。
|
|
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/01/27/java-listener/
]]>原文链接:http://tianweili.github.io/blog/2015/01/26/java-filter/
Filter也称之为过滤器,它是Servlet技术中最实用的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
它主要用于对用户请求进行预处理,也可以对HttpServletResponse 进行后处理。使用Filter 的完整流程:Filter 对用户请求进行预处理,接着将请求交给Servlet 进行处理并生成响应,最后Filter 再对服务器响应进行后处理。
Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的:
web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。
web.xml配置各节点介绍:
<filter>
指定一个过滤器。<filter-name>
用于为过滤器指定一个名字,该元素的内容不能为空。<filter-class>
元素用于指定过滤器的完整的限定类名。<init-param>
元素用于为过滤器指定初始化参数,它的子元素<param-name>
指定参数的名字,<param-value>
指定参数的值。FilterConfig
接口对象来访问初始化参数。<filter-mapping>
元素用于设置一个 Filter 所负责拦截的资源。一个Filter拦截的资源可通过两种方式来指定:Servlet 名称和资源访问的请求路径 <filter-name>
子元素用于设置filter的注册名称。该值必须是在<filter>
元素中声明过的过滤器的名字 <url-pattern>
设置 filter 所拦截的请求路径(过滤器关联的URL样式) <servlet-name>
指定过滤器所拦截的Servlet名称。 <dispatcher>
指定过滤器所拦截的资源被 Servlet 容器调用的方式,可以是REQUEST
,INCLUDE
,FORWARD
和ERROR
之一,默认REQUEST
。用户可以设置多个<dispatcher>
子元素用来指定 Filter 对资源的多种调用方式进行拦截。 <dispatcher>
子元素可以设置的值及其意义REQUEST
:当用户直接访问页面时,Web容器将会调用过滤器。如果目标资源是通过RequestDispatcher的include()或forward()方法访问时,那么该过滤器就不会被调用。 INCLUDE
:如果目标资源是通过RequestDispatcher的include()方法访问时,那么该过滤器将被调用。除此之外,该过滤器不会被调用。 FORWARD
:如果目标资源是通过RequestDispatcher的forward()方法访问时,那么该过滤器将被调用,除此之外,该过滤器不会被调用。 ERROR
:如果目标资源是通过声明式异常处理机制调用时,那么该过滤器将被调用。除此之外,过滤器不会被调用。在一个web应用中,可以开发编写多个Filter,这些Filter组合起来称之为一个Filter链。
web服务器根据Filter在web.xml文件中的注册顺序,决定先调用哪个Filter,当第一个Filter的doFilter方法被调用时,web服务器会创建一个代表Filter链的FilterChain对象传递给该方法。在doFilter方法中,开发人员如果调用了FilterChain对象的doFilter方法,则web服务器会检查FilterChain对象中是否还有filter,如果有,则调用第2个filter,如果没有,则调用目标资源。
|
|
和我们编写的Servlet程序一样,Filter的创建和销毁由WEB服务器负责。 web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
|
|
这个方法完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器。
|
|
Filter对象创建后会驻留在内存,当web应用移除或服务器停止时才销毁。在Web容器卸载 Filter 对象之前被调用。该方法在Filter的生命周期中仅执行一次。在这个方法中,可以释放过滤器使用的资源。
用户在配置filter时,可以使用
|
|
前段时间参与维护一个项目,用户退出系统后,再去地址栏访问历史,根据url,仍然能够进入系统响应页面。我去检查一下发现对请求未进行过滤验证用户登录。添加一个filter搞定问题!
先在web.xml配置
|
|
接着编写FilterServlet.java
:
|
|
这样既可完成对用户所有请求,均要经过这个Filter进行验证用户登录。
项目使用spring框架时。当前台JSP页面和JAVA代码中使用了不同的字符集进行编码的时候就会出现表单提交的数据或者上传/下载中文名称文件出现乱码的问题,那就可以使用这个过滤器。
|
|
当hibernate+spring配合使用的时候,如果设置了lazy=true(延迟加载),那么在读取数据的时候,当读取了父数据后,hibernate 会自动关闭session,这样,当要使用与之关联数据、子数据的时候,系统会抛出lazyinit的错误,这时就需要使用spring提供的OpenSessionInViewFilter过滤器。
OpenSessionInViewFilter主要是保持Session状态直到request将全部页面发送到客户端,直到请求结束后才关闭session,这样就可以解决延迟加载带来的问题。
注意:OpenSessionInViewFilter配置要写在struts2的配置前面。因为tomcat容器在加载过滤器的时候是按照顺序加载的,如果配置文件先写的是struts2的过滤器配置,然后才是OpenSessionInViewFilter过滤器配置,所以加载的顺序导致,action在获得数据的时候session并没有被spring管理。
|
|
项目中使用Struts2同样需要在web.xml配置过滤器,用来截取请求,转到Struts2的Action进行处理。
注意:如果在2.1.3以前的Struts2版本,过滤器使用org.apache.struts2.dispatcher.FilterDispatcher。否则使用org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter。从Struts2.1.3开始,将废弃ActionContextCleanUp过滤器,而在StrutsPrepareAndExecuteFilter过滤器中包含相应的功能。
三个初始化参数配置:
|
|
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/01/26/java-filter/
]]>原文链接:http://tianweili.github.com/blog/2015/01/11/setup-octopress-blog/
因为“墙”的关系,所以Octopress建立以后会发现访问速度奇慢无比,竟然超过了40s。
仔细分析后我们发现其中都是一些被墙的请求报了404Error,所以导致访问博客巨慢无比,下面我们就一次阉割掉这些被墙的请求。T_T
Octopress默认使用的是Google的JS公共库地址,加载的过程无比的缓慢。因此我们要把它改为百度的JS公共库,需要把/source/_includes/head.html
文件中的Google公共库地址改为:
|
|
从上图可以看出加载失败的还有twitter,这个也得给去掉。
把在根目录下的_config.yml
文件中Twitter内容给注释掉。
把\source\_includes\after_footer.html
文件中的twitter内容给注释掉:
|
|
把在\source\_includes\custom\head.html
中的Google font样式给删除:
|
|
在博文中,如果点击链接直接在本窗口打开了,那么用户体验就不是很好。而markdown的标准语法是不支持链接在新窗口打开的,虽然可以通过在markdown中直接写html标签来解决这个问题,但是这与markdown的简洁书写特性不符。但是我们可以通过设置Octopress来达到这种效果,即在\source\_includes\custom\head.html
文件中添加如下一段代码:
<!--more-->
,执行rake generate后在首页上会看到只显示<!—more—>
前面的文字,文字后面会显示Read on
链接,点击后进入文字的详细页面;
|
|
Octopress使用的是Pygments来进行代码着色的,使用方式也比较简单如下所示:
|
|
当然你也可以修改Pygments生成的代码css样式。
Pygments默认提供了很多css样式,你可以在python shell中用下面命令列出当前pygments所支持的样式:
|
|
通过-S来选择,需要生成default的样式:
|
|
有时候Octopress会把我们想要展示的Ruby代码解析成HTML,如果只是想展示代码,而不让Octopress来解析,那么可以在代码前后加入raw
和endraw
代码。
1.在plugins
目录下创建category_list_tag.rb
文件,内容如下:
|
|
2.添加source/_includes/asides/category_list.html
文件,内容如下:
3.修改_config.yml
文件,在default_asides
项中添加asides/category_list.html
,值之间以逗号隔开,值的先后顺序代表了侧边栏展现的先后顺序。
在侧边栏还可以添加其他组件,如微博、标签云等,添加方式和上面类似。
Octopress默认自带了DISQUS,但是对于国内不是很好用。所以在经过考虑之后选择了国内比较流行的多说评论系统。
首先要去多说网站注册,获取站点的short_name
。
在_config.yml
中添加
在./source/_layouts/post.html
中的disqus
代码
|
|
下方添加多说评论模块:
如果你希望一些单独的页面下方也放置评论功能,那么在./source/_layouts/page.html
中也做如上修改。
然后创建一个./source/_includes/post/duoshuo.html
文件,内容如下:
|
|
最后再修改_includes/article.html
文件,在
下方添加下面代码:
我把图片资源都放在了七牛云存储上,写博客时候就是用七牛的外链。但是这样有几个问题:
现在我们就使用一种灵活的方式来配置并自动生成图片的URL前缀:
首先修改/plugins/image_tag.rb
文件,在@img['class'].gsub!(/"/, '') if @img['class']
后添加下面一行代码:
|
|
然后再修改根目录下的_config.yml
文件,添加如下配置:
|
|
最后我们在插入图片的时候要记住不能再使用Markdown语法来写了,要使用Ocotpress自定义的IMG标签来插入图片。
本地预览先generate后preview,这样一来插入图片就灵活方便多了。
本博客的访客统计系统使用的是Flag Counter,所以要先去Flag Counter获取代码。
拿到代码后添加.\source\_includes\custom\asides\flag_counter.html
文件:
|
|
将页面添加到侧边栏,在./_config.yml
配置文件中添加下面一行配置:
|
|
最后添加控制开关,在./_config.yml
配置文件中添加下面一行配置:
作者:李天炜
原文链接:http://tianweili.github.com/blog/2015/01/11/setup-octopress-blog/
转载请注明作者和文章出处,谢谢。
]]>原文链接:http://tianweili.github.io/blog/2015/01/11/create-octopress-blog-in-windows/
进入博客源代码所在目录。编辑markdown后
rake new_post['my first blog']
来生成一篇博文;rake generate
生成博客网页;rake preview
后在本地输入rake setup_github_pages
命令后,按照提示输入对应的GitHub的repository地址:git@github.com:TianweiLi/tianweili.github.com.git
;(不执行这一步会可能会报No such file or directory - _deploy
错误)rake deploy
将博客站点发布到GitHubmaster
分支上,这样就可以访问博客了(这一步就是把public目录下文件push到master分支上);source
分支上:依次执行下面命令
如果需要在另一台电脑写博客并提交上去,那么可以采用下面步骤来实现。
先要找到GitHub的repository url,然后clone source分支到本地:
|
|
然后clone master分支到本地:
然后进行一些相关依赖的安装,依次执行下面命令:
|
|
作者:李天炜
原文链接:http://tianweili.github.io/blog/2015/01/11/create-octopress-blog-in-windows/
转载请注明作者和文章出处,谢谢。
]]>