zoukankan      html  css  js  c++  java
  • 基于纯Java代码的Spring容器和Web容器零配置的思考和实现(1)

    自Spring3.0开始,Spring正式将JavaConfig引入了Spring框架,我们可以基于纯Java代码来配置Spring容器Web容器,不再需要任何XML文件。摒弃XML文件而采用全Java配置的模式正逐渐变成主流。当然我们也不否认现阶段的一些配置还依然需要依托XML,Java应用彻底抛弃XML配置文件还有很长的一段路要走。在本文中,我们将基于纯Java代码来配置一个Spring Web项目。

    回忆一下,我们通常需要在applicationContext.xml和xxx-servlet.xml中配置哪些东西?
    一般来讲,我们会配置组件扫描数据源事务管理器、自定义的一些工具类切面消息转换器静态资源处理以及视图等。
    为了有条理的组织我们的配置类,我们将这些东西分为几个部分来配置。

    我们首先来配置数据源和事务管理器。既然我们是基于纯Java代码来配置,我们就需要考虑配置的可扩展性,我们希望提供的配置能够最大可能的适用于不同的项目,而不是只能用于一个项目。
    所以,对于数据源和事务管理,我们首先提供一个抽象类DBConfig,这个类提供了事务管理器和一些自定义的数据库访问工具,然后提供一个实现数据源抽象方法
    源码如下:

    package com.kiiwow.framework.config.context.spring.rest.dbconfig;
    
    import javax.sql.DataSource;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    
    import com.kiiwow.framework.database.jdbc.JDBCAccess;
    import com.kiiwow.framework.database.jdbc.JDBCAccessContext;
    
    /**
     * 基本的数据库配置
     * 统一配置了数据库访问工具和事务管理器
     * 具体的数据库配置类需要实现数据源创建的抽象方法
     * 这个类为实现不同的数据源提供了接口
     *
     * @author leon.gan
     *
     */
    public abstract class DBConfig {
    
        /**
         * 数据源,由子类实现
         */
        public abstract DataSource dataSource();
        
        /**
         * 事务管理器
         */
        @Bean
        public DataSourceTransactionManager dataSourceTransactionManager() {
            DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
            dataSourceTransactionManager.setDataSource(dataSource());
            return dataSourceTransactionManager;
        }
        
        /**
         * SpringJDBC Template
         */
        @Bean
        public JdbcTemplate jdbcTemplate() {
            JdbcTemplate jdbcTemplate = new JdbcTemplate();
            jdbcTemplate.setDataSource(dataSource());
            return jdbcTemplate;
        }
        
        /**
         * JDBC访问接口
         */
        @Bean
        public JDBCAccess jdbcAccess() {
            JDBCAccess jdbcAccess = new JDBCAccess();
            jdbcAccess.setJdbcTemplate(jdbcTemplate());
            return jdbcAccess;
        }
        
        /**
         * 实际用于执行SQL的工具
         */
        @Bean
        public JDBCAccessContext jdbcAccessContext() {
            JDBCAccessContext jdbcAccessContext = new JDBCAccessContext();
            jdbcAccessContext.setJdbcAccess(jdbcAccess());
            return jdbcAccessContext;
        }
        
    }

    我们需要将DBConfig中的事务管理器和数据库访问工具注册成为被Spring管理的Bean,在方法上添加@Bean注解即可,默认是单例
    这个类并没有指定具体的数据源,而是由子类去实现dataSource()方法来指定,这样就做到了可扩展。(对于以上代码中Jdbc相关的工具类,请参考我的上一篇博文《对JdbcTemplate进行简易封装以使其更加易用》)接下来,我们提供一个BasicDataSourceDBConfig,这个类继承自DBConfig,并实现dataSource()方法提供了一个BasicDataSource数据源。源码如下:

    package com.kiiwow.framework.config.context.spring.rest.dbconfig;
    
    import javax.inject.Inject;
    import javax.sql.DataSource;
    
    import org.apache.commons.dbcp.BasicDataSource;
    import org.springframework.context.annotation.Bean;
    
    import com.kiiwow.framework.config.context.spring.KiiwowEnvironment;
    
    /**
     * Apache BasicDataSource 数据源配置
     *
     * @author leon.gan
     *
     */
    public class BasicDataSourceDBConfig extends DBConfig {
    
        @Inject
        KiiwowEnvironment environment;
        
        @Bean
        public DataSource dataSource() {
            BasicDataSource ds = new BasicDataSource();
            ds.setDriverClassName(environment.getProperty("jdbc.driver"));
            ds.setUrl(environment.getProperty("kiiwow_jdbc.url"));
            ds.setUsername(environment.getProperty("kiiwow_jdbc.username"));
            ds.setPassword(environment.getProperty("kiiwow_jdbc.password"));
            ds.setTestOnBorrow(true);
            ds.setValidationQuery("select 1 from dual");
            return ds;
        }
        
    }

    我们在实际使用时,只要使用BasicDataSourceDBConfig这个类即可。同理,我们也可以提供一个C3p0DataSourceDBConfig类来实现C3P0数据源,源码如下:

    package com.kiiwow.framework.config.context.spring.rest.dbconfig;
    
    import javax.inject.Inject;
    import javax.sql.DataSource;
    
    import org.springframework.context.annotation.Bean;
    
    import com.kiiwow.framework.config.context.spring.KiiwowEnvironment;
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    
    /**
     * C3P0数据源配置
     *
     * @author leon.gan
     *
     */
    public class C3p0DataSourceDBConfig extends DBConfig {
    
        @Inject
        KiiwowEnvironment environment;
        
        @Bean
        public DataSource dataSource() {
            try {
                ComboPooledDataSource dataSource = new ComboPooledDataSource();
                dataSource.setDriverClass(environment.getProperty("db.c3p0.driverClassName"));
                dataSource.setJdbcUrl(environment.getProperty("kiiwow_db.c3p0.url"));
                dataSource.setUser(environment.getProperty("kiiwow_db.c3p0.username"));
                dataSource.setPassword(environment.getProperty("kiiwow_db.c3p0.password"));
                //当连接池在没有可用空闲连接时每次可以新增的连接数
                dataSource.setAcquireIncrement(environment.getRequiredProperty("c3p0.acquireIncrement", int.class, 5));
                //连接池初始连接数
                dataSource.setInitialPoolSize(environment.getRequiredProperty("c3p0.initialPoolSize", int.class, 5));
                //连接池可持有的最大连接数
                dataSource.setMaxPoolSize(environment.getRequiredProperty("c3p0.maxPoolSize", int.class, 200));
                //连接池可持有的最小连接数
                dataSource.setMinPoolSize(environment.getRequiredProperty("c3p0.minPoolSize", int.class, 5));
                //连接池中的连接失效的阀值(即最大未被使用时长)
                dataSource.setMaxIdleTime(environment.getRequiredProperty("c3p0.maxIdleSize", int.class, 1800));
                //与MaxIdleTime配合使用,必须小于MaxIdleTime的值,用于减少连接池中的连接
                dataSource.setMaxIdleTimeExcessConnections(environment.getRequiredProperty("c3p0.maxIdleTimeExcessConnections", int.class, 1200));
                //连接最大存活时间,超过这个时间将被断开,正在使用的连接在使用完毕后被断开
                dataSource.setMaxConnectionAge(environment.getRequiredProperty("c3p0.maxConnectionAge", int.class, 1000));
                //进行空闲连接测试的SQL
                dataSource.setPreferredTestQuery(environment.getProperty("c3p0.preferredTestQuery", "select 1 from dual"));
                //进行空闲连接测试的时间间隔
                dataSource.setIdleConnectionTestPeriod(environment.getRequiredProperty("c3p0.idleConnectionTestPeriod", int.class, 120));
                return dataSource;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        
    }

    在上面的代码中,出现了一个KiiwowEnvironment类,这个类是对org.springframework.core.env.Environment的封装,以实现编码解码功能。为了说明这个类的作用,我们首先来看看Environment这个接口的作用。这个接口是与@PropertySources注解配合工作的,
    我们在@PropertySources注解中指定的配置文件会自动被加载到Environment的实现中以key-value形式存储,
    我们可以通过Environment提供的getProperty(String key)等方法通过属性名获取属性值

    在我们的设计中有一个特殊的需求,我们希望配置在properties文件中的数据库连接以及账号密码都是经过加密的,这样可以一定程度上防止数据库信息泄露。
    所以我们定义在properties文件中所有以"kiiwow_"开头的属性,其对应的值都是经过Base64编码的结果,所以在我们通过Environment取出这些值的时候也要进行解码。不幸的是,Environment并不具备编码解码功能,我们需要自己实现,所以有了KiiwowEnvironment这个类。源码如下:

    package com.kiiwow.framework.config.context.spring;
    
    import javax.inject.Inject;
    
    import org.springframework.core.env.Environment;
    import org.springframework.stereotype.Component;
    
    import com.kiiwow.framework.util.DigestUtils;
    
    /**
     * 自定义环境类
     * 基于JavaConfig配置Spring的情况下,使用@PropertySource注解将配置属性注入Environment中后
     * Environment不能支持解码已编码的属性值。所以提供这个类对数据获取进行解码操作。
     *
     * @author leon.gan
     *
     */
    @Component
    public class KiiwowEnvironment {
    
        @Inject
        Environment env;
        
        /**
         * 所有以kiiwow_开头的属性,其值都需要经过Base64解码后使用
         */
        public String getProperty(String propertyName) {
            if (propertyName.startsWith("kiiwow_")) {
                String originalValue = env.getProperty(propertyName);
                return DigestUtils.decodeBase64(originalValue);
            } else {
                return env.getProperty(propertyName);
            }
        }
        
        /**
         * 没有配置属性时采用默认值
         */
        public String getProperty(String propertyName, String defaultValue) {
            if (propertyName.startsWith("kiiwow_")) {
                String originalValue = env.getProperty(propertyName);
                return originalValue == null ? null : DigestUtils.decodeBase64(originalValue);
            } else {
                return env.getProperty(propertyName) == null ? defaultValue : env.getProperty(propertyName);
            }
        }
        
        /**
         * 将值转换为指定类型
         */
        public <T> T getRequiredProperty(String propertyName, Class<T> targetType) {
            return env.getRequiredProperty(propertyName, targetType);
        }
        
        /**
         * 属性不存在则返回默认值
         */
        public <T> T getRequiredProperty(String propertyName, Class<T> targetType, T defaultValue) {
            try {
                return env.getRequiredProperty(propertyName, targetType);
            } catch (Exception e) {
                return defaultValue;
            }
        }
        
    }

    从上面的代码可以看到,我们只是将org.springframework.core.env.Environment注入,然后在调用他的getProperty(String key)等方法前增加了判断属性名是否以"kiiwow_"开头的逻辑。同时提供了处理属性名不存在于配置文件则返回默认值的方法。特别注意,我们需要将这个类注册成为Spring容器的一个组件,即在类上使用@Component注解,否则org.springframework.core.env.Environment是无法注入的。(这个解决方法特别感谢@YouKnowNothing 的帮助)。

    至此,我们对于数据源和事务管理的配置就结束了。我们可以自由继承DBConfig类来扩展我们需要的数据源,也能对配置文件中的敏感信息进行编码防止信息泄露。不过需要指出的是,这种处理敏感信息的方法依然不是最优的。由于我们要进行解码,所以我们只能使用双向加密策略,如果别人知道了你的编码策略,也能够轻松破解。我们将在下一篇文章中讲解对于静态资源、视图解析器、消息转换器的配置以及web.xml的Java代码化。

    感谢您的阅览,劳烦点个赞。

    http://my.oschina.net/devleon/blog/530803

  • 相关阅读:
    ASE19团队项目 beta阶段 model组 scrum report list
    ASE19团队项目 beta阶段 model组 scrum7 记录
    ASE19团队项目 beta阶段 model组 scrum6 记录
    ASE19团队项目 beta阶段 model组 scrum5 记录
    ASE19团队项目 beta阶段 model组 scrum4 记录
    ASE19团队项目 beta阶段 model组 scrum3 记录
    ASE19团队项目 beta阶段 model组 scrum2 记录
    ASE19团队项目 beta阶段 model组 scrum1 记录
    【ASE模型组】Hint::neural 模型与case study
    【ASE高级软件工程】第二次结对作业
  • 原文地址:https://www.cnblogs.com/softidea/p/5698599.html
Copyright © 2011-2022 走看看