来自维基百科
约定优于配置(convention over configuration),也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。
本质是说,开发人员仅需规定应用中不符约定的部分。例如,如果模型中有个名为Sale的类,那么数据库中对应的表就会默认命名为sales。只有在偏离这一约定时,例如将该表命名为"products_sold",才需写有关这个名字的配置。
如果您所用工具的约定与你的期待相符,便可省去配置;反之,你可以配置来达到你所期待的方式。
动机
设计不好的框架通常需要多个配置文件,每一个都有许多设置。这些配置文件为每一个项目提供信息说明从URL到将类映射到数据库表的各种信息。大量包含太多参数的配置文件通常是过度复杂的应用设计的指标(代码坏味道)。
例如,在知名的Java对象关系映射框架Hibernate的早期版本中,将类及其属性映射到数据库上需要是在XML文件中的描述,其中大部分信息都应能够按照约定得到,如将类映射到同名的数据库表,将属性分别映射到表上的字段。后续的版本抛弃了XML配置文件,而是使用这些恰当的约定,对于不符合这些约定的情形,可以使用Java 标注来说明(参见下面提供的JavaBeans规范)。
使用
许多新的框架使用了约定优于配置的方法,包括:Spring,Ruby on Rails,Kohana PHP,Grails,Grok,Zend Framework,CakePHP,symfony,Maven,ASP.NET MVC,Web2py(MVC),Apache Wicket。
这是一个古老的概念, 甚至在Java类库中也可以找出这一概念的踪迹。例如,JavaBean规范非常多的依赖这一概念。下面摘录JavaBeans 1.1版规范的一段:
按照一般的规则,我们不希望造出一个奇怪的java.beans.everything类,其他类需要从该类派生。而是希望在运行时JavaBeans为一般的对象提供缺省的行为特征,但是允许对象通过继承特定的java.beans.something接口来覆盖缺省的行为特征的一部分。
例如,Maven约定的项目目录结构如下图,只需配置很少的信息就可以自动完成编译,测试和打包等工作。
实战
参考 约定优于配置-实战
约定优于配置是一个简单的概念。 系统,类库,框架应该假定合理的默认值,而非要求提供不必要的配置。 在大部分情况下,你会发现使用框架提供的默认值会让你的项目运行的更快。零配置并不是完全没有配置,而是通过约定来减少配置, 减少 XML。
约定代码结构或命名规范来减少配置数量
如果模型中有个名为Sale的类,那么数据库中对应的表就会默认命名为sale。只有在偏离这一约定时,例如将该表命名为"products_sold",才需写有关这个名字的配置。
例如: EJB3 持久化,将一个特殊的Bean持久化,你所需要做的只是将这个类标注为 @Entity 。 框架将会假定表名和列名是基于类名和属性名。 系统也提供了一些钩子,当有需要的时候你可以重写这些名字。
maven 项目约定,在没有自定义的情况下,源代码假定是在src/main/java
,资源文件假定是在src/main/resources
。测试代码假定是在 src/test
。项目假定会产生一个 JAR 文件。Maven假定你想要把编译好的字节码放到 target/classes
并且在 target
创建一个可分发的 JAR 文件。Maven 对约定优于配置的应用不仅仅是简单的目录位置,Maven 的核心插件使用了一组通用的约定,以用来编译源代码,打包可分发的构件,生成 web 站点,还有许多其他的过程。 Maven 的力量来自它的"武断",它有一个定义好的生命周期和一组知道如何构建和装配软件的通用插件。如果你遵循这些约定,Maven 只需要几乎为零的工作——仅仅是将你的源代码放到正确的目录,Maven 将会帮你处理剩下的事情。
HTML5 Boilerplate ,为App的默认模板以及文件路径规范,无论是网站或者富UI的App,都可以采用这个模板作为起步。HTML5 Boilerplate的模板核心部分不过30行,但是每一行都可谓千锤百炼,可以用最小的消耗解决一些前端的顽固问题:
采用更简洁的配置方式来替代XML
例如:hibernate + proxool配置
jdbc.driver=oracle.jdbc.OracleDriver jdbc.url=jdbc:oracle:thin:@localhost:1521:egdb jdbc.username=example jdbc.password=P@ssw0rd hibernate.dialect=org.hibernate.dialect.Oracle10gDialect hibernate.show_sql=true hibernate.format_sql=true hibernate.session_context_class=thread hibernate.cache.use_query_cache=true hibernate.cache.use_second_level_cache=true hibernate.cache.provider_class=org.hibernate.cache.EhCacheProvider proxool.alias=MAIN_POOL proxool.prototypeCount=10 proxool.houseKeepingSleepTime=90000 proxool.maximumActiveTime=300000 proxool.maximumConnectionLifetime=14400000 proxool.maximumConnectionCount=100 proxool.minimumConnectionCount=10 proxool.simultaneousBuildThrottle=100 proxool.houseKeepingTestSql=select CURRENT_DATE
hibernate + druid配置
druid.name=MAIN_POOL druid.initialSize=1 druid.minIdle=10 druid.maxActive=20 druid.maxWait=60000 druid.timeBetweenEvictionRunsMillis=60000 druid.minEvictableIdleTimeMillis=300000 druid.validationQuery=select 'X' from dual druid.testWhileIdle=true druid.testOnBorrow=false druid.testOnReturn=false druid.maxOpenPreparedStatements=20 druid.removeAbandoned=true druid.removeAbandonedTimeout=1800 druid.logAbandoned=true druid.filters=stat
通过注解约定其含义来减少配置数量
Spring注解
Spring会自动搜索指定包路径下的Java类,并将这些java类注册为Bean实例,这样就省去了将所有 bean 都在 XML 配置。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd" default-lazy-init="true"> <description>Spring公共配置</description> <!-- 使用annotation自动注册bean, 并检查@Required, @Autowired的属性已被注入 --> <!-- 通过exclude-filter把所有@Controller注解的表现层控制器组件排除, 如果不排除@Controller会影响事务管理, Controller是在spring-mvc.xml中配置 --> <context:component-scan base-package="org.stones.rbac"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" /> </context:component-scan> </beans>
注意:如果配置了<context:component-scan>,那么<context:annotation-config/>标签就可以不用再xml中配置了,因为前者包含了后者。另外<context:component-scan>还提供了两个子标签 <context:include-filter>
、<context:exclude-filter>
用来控制扫描文件的颗粒度。 Spring通过使用特殊的Annotation来标注Bean类。
- @Component :标注一个普通的Spring Bean类。
- @Controller : 标注一个控制器组件类。
- @Service: 标注一个业务逻辑组件类。
- @Repository : 标注一个DAO组件类。
配置代码实现(可参考使用Java配置进行Spring bean管理)。
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("org.stones.rbac"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); }
SpringMVC注解
import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresRoles; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller @RequestMapping(value = "/user") public class UserController { @Autowired private UserService userService; // 特别设定多个ReuireRoles之间为Or关系, 而不是默认的And. @RequiresRoles(value = { "Admin", "User" }, logical = Logical.OR) @RequestMapping(value = "") public String list(Model model, ServletRequest request) { // do something return "xxx/user/index"; } @RequestMapping(value = "{var}", method = RequestMethod.GET) public String doSomething(@PathVariable("var") String var, Model model) { // do something model.addAttribute("action", Constants.ACTION_TYPE_VIEW); return "xxx/user/form"; } ... }