• 基于纯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

  • 相关阅读:
    tensorflow RNN和简单例子
    推荐系统理论及实战(转)附个人的理解和实战
    mysql 中 case when then .... else end 的简单使用
    动态规划最常见的习题 (最长公共子串、最长公共子序列、最短编辑距离)
    神经网络中 梯度消失、梯度爆炸的问题分析
    深度学习基础(转)
    用户兴趣的模型 浅析
    20190317 A
    pkuwc2019游记
    练习题16
  • 原文地址:https://www.cnblogs.com/softidea/p/5698599.html
Copyright © 2020-2023  润新知