【3月28日】手撕架构(二)人工编写工具类以及业务开发初次迭代

本次项目主要分为以下几层:controller,model,service以及view。在标准的MVC架构中,是没有服务层的,然而实际中很少按照标准的来,可以将service层作为衔接controller与数据库的桥梁,可以使用接口和实现类来表达。在一些简单的应用中,无须使用接口,直接用类就可以了。下面就通过服务层的优化过程来体会代码重构的”艺术”。

一:创建CustomerService和单元测试用例

CustomerService主要提供对customer表的操作,也就是基本的“增,删,改,查“的功能。

/**
 * Created by Tree on 2017/3/27.
 *采用MVC架构搭建本Web应用项目,此为服务层
 */
public class CustomerService {
    /**
     * 获取客户
     */
    public Customer getCustomer(long id){
        return null;
    }

    /**
     * 获取客户列表
     */
    public List<Customer> getCustomerList(){
       return null;
    }

    /**
     * 创建客户
     */
    public boolean createCustomer(Map<String, Object> fieldMap){
        return false;
    }

    /**
     * 更新客户
     */
    public boolean updateCustomer(long id, Map<String, Object> fieldMap){
        return false;
    }

    /**
     * 删除客户
     */
    public boolean deleteCustomer(long id){
        return false;
    }
}

该service主要说明了提供的服务,为了在开发过程中便于测试,正好这几天撸了JUnit,于是顺手编写测试用例。如下就是对getCustomerList的测试方法,使用断言=2(初始表的记录数人工加了2个)来测试该方法。

  @Test
    public void getCustomerListTest() throws Exception{
        List<Customer> customerList = customerService.getCustomerList();
        Assert.assertEquals(2, customerList.size());
    }

二:编写工具类

便于开发,本实验添加了SLF4J的依赖用于提供日志API,使用Log4J作为实现。
实验中使用的数据库为MySQL,为了实现JDBC,在pom中需要添加以下依赖:

        <!--MySQL-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.33</version>
            <scope>runtime</scope>
        </dependency>
        <!--Apache Commons Lang-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!--Apache Commons Collections-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>

Apache的依赖主要是用于提供常用的工具类。当然,既然说了是要手撕结构,就不能直接用轮子。后续中会尝试工具编写工具类。
现在考虑实现CustomerService中的方法,以getCustomerList方法为例。要获取客户列表(暂时不考虑分页),需要首先执行一条select语句(Statement),获这里写代码片得相应的结果集(Result),然后再作为一个List返回。不过在此之前,首先必须或者数据库的连接(Connection)。
在resources目录下创建config.properties属性文件:

jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/demo
jdbc.username = root
jdbc.password = ****

接下来就要考虑该如何来读取属性文件的内容。可以通过编写一个PropsUtil工具类来完成这件事。

package org.smart4j.chapter2.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * Created by Tree on 2017/3/27.
 * util层存放工具类
 * PropsUtil属性文件工具类
 */
public class PropsUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(PropsUtil.class);

    /**
     * 加载属性文件
     */
    public static Properties loadProps(String fileName){
        Properties props = null;
        InputStream is = null;
        try {
            is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
            if (is==null){
                throw new FileNotFoundException(fileName+" file is not found");
            }
            props = new Properties();
            props.load(is);
        }catch (IOException e){
            LOGGER.error("load properties file failure",e);
        }finally {
            if(is!=null){
                try{
                    is.close();
                }catch (IOException e){
                    LOGGER.error("close input stream failure",e);
                }
            }
        }
        return props;
    }

    /**
     * 获取字符型属性(默认值为空字符串)
     */
    public static String getString(Properties props,String key){
        return getString(props,key,"");
    }

    /**
     * 获取自字符型属性(可指定默认值)
     */
    public static String getString(Properties props,String key, String defaultValue){
        String value = defaultValue;
        if(props.containsKey(key)){
            value = props.getProperty(key);
        }
        return value;
    }

    /**
     * 获取数值型属性(默认值为0)
     */
    public  static int getInt(Properties props,String key){
        return getInt(props, key, 0);
    }

    /**
     * 获取数值型属性(可指定默认值)
     */
    public  static int getInt(Properties props,String key, int defaultValue){
        int value = defaultValue;
        if(props.containsKey(key)){
            value = CastUtil.castInt(props.getProperty(key));
        }
        return value;
    }

    /**
     * 获取布尔型属性(默认为false)
     */
    public  static boolean getBoolean(Properties props,String key){
        return getBoolean(props, key, false);
    }

    /**
     * 获取布尔属性(可指定默认值)
     */
    public  static boolean getBoolean(Properties props,String key, boolean defaultValue){
        boolean value = defaultValue;
        if(props.containsKey(key)){
            value = CastUtil.castBoolean(props.getProperty(key));
        }
        return value;
    }
}

改类最关键的是loadProps方法,我们只需要一个属性文件的名称,即可返回一个Properties对象,然后再根据getInt,getString等方法根据key获取指定类型的value,同时也提供默认值保证系统的健壮性。
在PropsUtil类中,使用到了CastUtil类,改类是为处理一些数据转型操作而准备的工具类。

package org.smart4j.chapter2.util;

/**
 * Created by Tree on 2017/3/27.
 * 转型操作工具类
 */
public class CastUtil {
    /**
     *转为String
     */
    public static String castString(Object obj){
        return castString(obj,"");
    }

    /**
     *转为String(提供默认值)
     */
    public static String castString(Object obj, String defaultValue){
        return obj != null?String.valueOf(obj):defaultValue;
    }

    /**
     *转为Double
     */
    public static Double castDouble(Object obj){
        return castDouble(obj, 0);
    }

    /**
     *转为Double(提供默认值:0)
     */
    public static Double castDouble(Object obj, double defaultValue){
        double doubleValue = defaultValue;
        if(obj!=null){
            String strValue = castString(obj);
            if(StringUtil.isNotEmpty(strValue)){
                try {
                    doubleValue = Double.parseDouble(strValue);
                }catch (NumberFormatException e){
                    doubleValue = defaultValue;
                }
            }
        }
        return doubleValue;
    }

    /**
     *转为long
     */
    public static long castLong(Object obj){
        return castLong(obj, 0);
    }

    /**
     *转为long(提供默认值:0)
     */
    public static long castLong(Object obj, long defaultValue){
        long longValue = defaultValue;
        if(obj!=null){
            String strValue = castString(obj);
            if(StringUtil.isNotEmpty(strValue)){
                try {
                   longValue = Long.parseLong(strValue);
                }catch (NumberFormatException e){
                    longValue = defaultValue;
                }
            }
        }
        return longValue;
    }

    /**
     *转为int
     */
    public static int castInt(Object obj){
        return castInt(obj, 0);
    }

    /**
     *转为int(提供默认值:0)
     */
    public static int castInt(Object obj, int defaultValue){
        int intValue = defaultValue;
        if(obj!=null){
            String strValue = castString(obj);
            if(StringUtil.isNotEmpty(strValue)){
                try {
                    intValue = Integer.parseInt(strValue);
                }catch (NumberFormatException e){
                    intValue = defaultValue;
                }
            }
        }
        return intValue;
    }

    /**
     *转为boolean
     */
    public static boolean castBoolean(Object obj){
        return castBoolean(obj, false);
    }

    /**
     *转为int(提供默认值:0)
     */
    public static boolean castBoolean(Object obj, boolean defaultValue){
        boolean booleanValue = defaultValue;
        if(obj!=null){
            String strValue = castString(obj);
            booleanValue = Boolean.parseBoolean(strValue);
        }
        return booleanValue;
    }

}

俄罗斯套娃般的,在CastUtil中,用到了StringUtil类,提供对字符串的操作。

package org.smart4j.chapter2.util;

/**
 * Created by Tree on 2017/3/27.
 * 字符串工具类:判断字符串是否为空
 */
public final class StringUtil {
    public static boolean isEmpty(String str) {
        if(str!=null){
            str = str.trim();
        }
        return StringUtil.isEmpty(str);
    }
    public static boolean isNotEmpty(String str){
        return !isEmpty(str);
    }
}

再顺手创建一个CollectionUtil,提供一些集合的操作。

package org.smart4j.chapter2.util;

import org.apache.commons.collections4.MapUtils;

import java.util.Collection;
import java.util.Map;

/**
 * Created by Tree on 2017/3/27.
 * 集合工具类:判断是否为空
 */
public final class CollectionUtil {
    public static boolean isEmpty(Collection<?> collection){
        return CollectionUtil.isEmpty(collection);
    }
    public static boolean isNotEmpty(Collection<?> collection){
        return !isEmpty(collection);
    }

    public static boolean isEmpty(Map<?, ?> map){
        return MapUtils.isEmpty(map);
    }
    public static boolean isNotEmpty(Map<?, ?> map){
        return !isEmpty(map);
    }
}

至此,一口气写了四个工具类,每个工具类提供不同的功能,服务于整个开发过程。以前基本上都是使用现成的轮子,开发中只要关注业务的开发而不需要关心这些基础的工作,现在一步步手写,为那些造轮子的大神鞠躬!

三:业务开发–以getCustomerList为例

现在回到CustomerService,需要在该类中执行数据库的操作,也就是一些JDBC的代码,首先需要调用PropsUtil工具读取配置文件config.properties,获取JDBC相关的配置项。
不妨在CustomerService中卫这些配置项定义一些常量,使用一个”静态代码块“来初始化这些常量:

static {
        Properties conf = PropsUtil.loadProps("config.properties");
        DRIVER = conf.getProperty("jdbc.driver");
        URL = conf.getProperty("jdbc.url");
        USERNAME = conf.getProperty("jdbc.username");
        PASSWORD = conf.getProperty("jdbc.password");

        try{
            Class.forName(DRIVER);
        }catch(ClassNotFoundException e){
            LOGGER.error("can not load jdbc drive", e);
        }
    }

加载这些配置项之后,就可以进行业务功能的描述了,以getCustomerList为例,可以这样写JDBC的代码:

   public List<Customer> getCustomerList(){
        Connection conn = null;
        try{
            List<Customer> customerList = new ArrayList<Customer>();
            String sql = "SELECT * FROM customer";
            conn = DriveManager.getConnection(URL, USERNAME, PASSWORD);
            PreparedStatement stmt = conn.prepareStatement(sql);
            ResultSet rs = stmt.executeQuery();
            while (rs.next()){
                Customer customer = new Customer();
                customer.setId(rs.getLong("id"));
                customer.setName(rs.getString("name"));
                customer.setContract(rs.getString("contact"));
                customer.setTelephone(rs.getString("telephone"));
                customer.setEmail(rs.getString("email"));
                customer.setRemark(rs.getString("remark"));
                customerList.add(customer);
            }
            return customerList;
        }catch (SQLException e){
            LOGGER.error("execute sql failure", e);
        }finally {
            if(conn!=null){
                try{
                    conn.close();
                }catch (SQLException e){
                    LOGGER.error("close connection failure", e);
                }
            }

        }
    }

通过单元测试可以检测改功能是否完成。在测试之后,确实通过了测试,表示基本的功能是可以实现的,但是问题还是有很多,具体包括:
1.在CustomerService类中读取config.properties文件,这是不合理的。一个项目中不可能只有一个service,如果每个service都需要读取config,一来造成不必要的冗余,也存在安全风险。
2.执行一条select语句真的是好累啊,需要编写一大推代码,还必须使用try……catch……finally结构,开发效率明显不高。
接下来就要尝试去解决以上的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值