欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

项目代码重构

程序员文章站 2022-03-10 09:56:55
...

项目代码重构

毕设做完,能跑起来,但是感觉代码写的不好。

一开始,对于controller层方法和service层方法,该返回什么类型的值,没有什么想法,于是代码写的有点混乱。也发现controller层或service层,都没有对异常处理,也没有日志处理。感觉这样子写的代码,过段时间自己可能都看不下去。于是,就想重构下。

问题描述

如职位模块中:
service层的add方法这么写:

public String addJob(Job job) {
    int result = jobDao.save(job);
    if(result == 0)
        return "success";
    return "fail";
}

controller层就可以很简洁:

@ResponseBody
public String addJob(Job job){
    return jobService.addJob(job);
}

前端这么处理:

function add(){
    $('#wu-form').form('submit', {
        url:'/job/add',
        success:function(data){
            if(data=='success'){
                $.messager.alert('信息提示','添加成功!','info');
                $('#wu-datagrid').datagrid('reload');
                $('#wu-dialog').dialog('close');
            }
            else
            {
                $.messager.alert('信息提示','添加失败!','info');
            }
        }
    });
}

又如权限控制模块中:
controller层的add方法这么写:

public String addPermission(Permission permission){
    RedisUtil.refreshRedis();
    int result = permissionService.addPermission(permission);
    if(result ==0)
        return null;
    return "success";
}

service层的方法就可以很简洁:

public int addPermission(Permission permission) {
    return permissionDao.save(permission);
}

前端这么处理:

function add(){
    $('#wu-form').form('submit', {
        url:'/rbac/permission/save',
        success:function(data){
            if(data){//当data为null,则if不成立
                $.messager.alert('信息提示','添加成功!','info');
                $('#wu-datagrid').datagrid('reload');
                $('#wu-dialog').dialog('close');
                //更新树
                $('#wu-category-tree').tree('reload');
            }
            else
            {
                $.messager.alert('信息提示','添加失败!','info');
            }
        }
    });
}

是的,各个模块都没有统一,显得很混乱。后台也没有将可能出错的原因显示到前端界面上。而且,也没有异常处理,更没有对异常进行日志记录。

嗯上面说的问题,就是接下来要解决的问题。

重构思路

我的想法:
对于增删改操作,controller层返回String类型的success或者fail,没什么意义。倒不如在操作失败时,返回一个出错的可能的原因。因此,封装一个ResultDTO,它包含两个属性,boolean类型的的success和String类型的errorMessage。然后对于增删改操作,controller方法就返回一个ResultDTO对象。当操作正常时,返回一个success为true的ResultDTO对象。当操作不正常时,返回一个success为false且带有errorMessage错误信息的ResultDTO对象。
然后,对于异常的处理和日志记录,如果在controller方法中对每个被调用的service方法都通过try catch来处理,那样controller层会变得很臃肿。如果在service方法中捕获异常与记录日志,那么业务逻辑就会和这些无关业务的东西紧耦合。因此这两种方法都不采用。可以使用AOP技术,通过使用AOP技术对service方法进行处理。
因为,使用aop技术对service方法进行异常与日志的处理,因此封装DTO的操作放在controller层。即service方法只负责业务逻辑。封装操作交由controller方法。
通过aop的around方法,对service方法进行异常处理以及出现异常时记录日志,当出现异常则返回null,否则返回正常的对象。因此,service层方法的返回值类型不能是基本数据类型(因为你返回的可能是null),因此service方法返回值类型得是引用类型。(详细的说,就是service层的增删改方法,返回值类型不能是int,得是Integer)。
然后由于进行了异常处理,所以controller层方法中,得对调用的service方法的返回值进行是否为null的判断,当为null,说明service方法出现异常,则封装一个success为false且errorMessage带上错误信息的ResultDTO对象并返回。
最后是前端,具体变动查看后面写的代码吧,其实变动不大,就是增强了功能,可以将ResultDTO对象中的errorMessage反馈给用户。而不是简单的提示说操作失败。

具体重构操作

下面以职位模块的add操作为例子进行重构。

1.ResultDTO类:

package hrm.DTO;

public class ResultDTO {
    private boolean success;
    private String errorMessage;

    //封装的一些信息
    public static final String Being_Referenced = "出错的原因可能是该记录被其他表引用了,如果该记录被引用了,则无法删除。详情联立管理,通过查看日志分析。";
    public static final String Not_Exist = "出错的原因可能是该记录已经不存在了。可以尝试刷新表格看看记录是否已经不存在了。详情联立管理,通过查看日志分析。";
    public static final String Unknown = "未知异常。详情联立管理,通过查看日志分析。";

    //操作成功使用这个构造方法
    public ResultDTO(boolean success) {
        this.success = success;
    }

    //操作失败时使用这个构造方法
    public ResultDTO(boolean success, String errorMessage) {
        this.success = success;
        this.errorMessage = errorMessage;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }

}

2.引入AOP
2.1 新增一个切面类,使用around环绕方法。

package hrm.ExceptionHandle;

import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

public class ExceptionAspect {
    private Logger logger = LoggerFactory.getLogger(ExceptionAspect.class);

    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        logger.warn("前置增强");
        Object result;
        //捕获异常,当出现异常时,记录日志并返回null
        try{
            result = proceedingJoinPoint.proceed();//调用实际的service方法
        }catch (Exception e){
            logger.error(new Date(System.currentTimeMillis()) + ":" + e.getMessage());
            return null;//出现异常,返回null。该aop操作,是针对service层处理的,因此!service层的返回值类型必须是引用类型而不能是基本数据类型。否则当出现异常时返回null会出现转换异常
        }
        return result;
    }
}

2.2 配置spring.xml文件

<!-- aop -->
<bean class="hrm.ExceptionHandle.ExceptionAspect" id="exceptionAspect"></bean>
<aop:config>
    <aop:pointcut id="perform" expression="execution(* hrm.service.*.*(..))"/><!--这里表示对service包里的所有方法进行处理-->
    <aop:aspect ref="exceptionAspect">
        <aop:around method="around" pointcut-ref="perform"/><!--around方法-->
    </aop:aspect>
</aop:config>

2.3 引入AOP依赖

<!--aop-->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.8.6</version>
</dependency>

2.4 service方法的变动
原来的service方法:

public String addJob(Job job) {
    int result = jobDao.save(job);
    if(result == 0)
        return "success";
    return "fail";
}

改成:

//必须是返回Integer而不能是int
public Integer addJob(Job job) {
    return jobDao.save(job);
}

2.5 controller方法的变动
原来的controller方法:

@ResponseBody
public String addJob(Job job){
    return jobService.addJob(job);
}

改成:

@ResponseBody
public ResultDTO addJob(Job job){
    Integer result = jobService.addJob(job);//用Integer类型的值接收方法的返回值
    if(result == null || result == 0)//必须先进行null判断,如果为null,则说明出现异常,如果为0,则也是新增失败
        return new ResultDTO(false,ResultDTO.Unknown);
    return new ResultDTO(true);
}

ps:上面因为不知道出错的原因是什么,所以统一都用”未知异常。详情联立管理,通过查看日志分析。”作为errorMessage的内容。

再来看看删除操作的controller方法:

@ResponseBody
public ResultDTO removeJob(@RequestParam("ids") Integer ids){
    Integer result = jobService.removeJob(ids);
    if(result == null)
        return new ResultDTO(false,ResultDTO.Being_Referenced);//这只是可能的原因,实际原因要看日志
    else if(result == 0)//删除一条不存在的记录并不会抛出异常,只会返回0.
        return new ResultDTO(false,ResultDTO.Not_Exist);//这只是可能的原因,实际原因要看日志
    return new ResultDTO(true);
}

当出现异常,那么我所知道的,很可能是因为该记录作为外键被其他表记录引用了,因此无法删除。因此这里,可以将errorMessage设置为”出错的原因可能是该记录被其他表引用了,如果该记录被引用了,则无法删除。详情联立管理,通过查看日志分析。”,但这只是可能的原因而已。实际是不是这个原因还是得查看日志。

又如controller的update方法,假如两个管理员先后对同一条记录进行操作,前者删除该记录,后者修改该记录,那么当执行sql操作的时候,update结果的影响行数为0,那么controller方法中就可以这样响应前端:

@ResponseBody
public ResultDTO modifyJob(Job job){
    Integer result = jobService.modifyJob(job);
    if(result == null)
        return new ResultDTO(false,ResultDTO.Unknown);
    else if(result == 0)//当修改记录为0时,很可能是因为该记录不存在,删除一条不存在记录并不会抛出异常!!
        return new ResultDTO(false,ResultDTO.Not_Exist);//因此这里响应时可以提示说该记录可能已经被删除了,所以你的删除操作失败了。提示用户说,刷新表格,可能该记录确实是不存在了
    return new ResultDTO(true);
}

2.6 前端变动
原本add方法为:

function add(){
    $('#wu-form').form('submit', {
        url:'/job/add',
        success:function(data){
            if(data=='success'){
                $.messager.alert('信息提示','添加成功!','info');
                $('#wu-datagrid').datagrid('reload');
                $('#wu-dialog').dialog('close');
            } else {
                $.messager.alert('信息提示','添加失败!','info');//只提示“添加失败!”
            }
        }
    });
}

现在改为:

function add(){
    $('#wu-form').form('submit', {
        url:'/job/add',
        success:function(result){
            result = JSON.parse(result)//重定义result,将result从json字符串转成Object
            if(result.success){//判断ResultDTO中的success属性是为true还是false
                $.messager.alert('信息提示','添加成功!','info');
                $('#wu-datagrid').datagrid('reload');
                $('#wu-dialog').dialog('close');
            } else {
                $.messager.alert('信息提示','添加失败!'+result.errorMessage,'info');//现在还带上了可能出错的原因
            }
        }
    });
}

验证操作

验证操作如下:
1.新增一条记录:(经理,总部门,xx),添加成功:
项目代码重构
2.出现的结果:
项目代码重构
3.在数据库中删除该记录:
项目代码重构
4.选中这条记录,点击删除,确认:
项目代码重构
5.出现的结果:
项目代码重构
6.验证成功,前台也给出了相应提示。

7.验证删除被引用的记录。选中第一条记录(这条记录被引用了),点击删除,确认:
项目代码重构
8.出现的结果:
项目代码重构
9.验证成功。友好的给出了提示。
10.异常也被记录到了日志中:

13:27:38.056 [http-nio-8080-exec-16] ERROR hrm.ExceptionHandle.ExceptionAspect - Thu Apr 12 13:27:38 CST 2018:
### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`hrm_db`.`employee_inf`, CONSTRAINT `FK_EMP_JOB` FOREIGN KEY (`JOB_ID`) REFERENCES `job_inf` (`ID`))
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: DELETE FROM job_inf WHERE id = ?
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`hrm_db`.`employee_inf`, CONSTRAINT `FK_EMP_JOB` FOREIGN KEY (`JOB_ID`) REFERENCES `job_inf` (`ID`))
; SQL []; Cannot delete or update a parent row: a foreign key constraint fails (`hrm_db`.`employee_inf`, CONSTRAINT `FK_EMP_JOB` FOREIGN KEY (`JOB_ID`) REFERENCES `job_inf` (`ID`)); nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`hrm_db`.`employee_inf`, CONSTRAINT `FK_EMP_JOB` FOREIGN KEY (`JOB_ID`) REFERENCES `job_inf` (`ID`))

查询方法重构

查询方法原本是在service层就转换成jsonString:

public String findJob(Job job,int pageNumber,int pageSize) {
    int count = jobDao.queryCount(job);
    int offset = (pageNumber-1)*pageSize;
    List<Job> list = jobDao.query(job,offset,pageSize);
    Map<String,Object> map = new HashMap<String, Object>();
    map.put("rows",list);
    map.put("total",count);
    return JSON.toJSONString(map);
}

然后controller层方法直接返回结果即可:

@ResponseBody
public String selectJob(@RequestParam("rows")int rows, @RequestParam("page") int page, Job job){
    //前台如果没传这些参数,则默认是空字符串,而不是null。因此当没传该参数时要设置为null,这样dao层才不会以该参数作为查询条件
    if("".equals(job.getName()))
        job.setName(null);
    if("".equals(job.getRemark()))
        job.setRemark(null);
    if("".equals(job.getDeptId()))
        job.setDeptId(null);
    return jobService.findJob(job,rows,page);
}

现在,service层返回map,而不返回jsonString,因为service方法可能被重用,controller层需要的不一定是jsonString,于是返回map对象,让controller自己按需处理:

public Map findJob(Job job,int pageNumber,int pageSize) {
    int count = jobDao.queryCount(job);
    int offset = (pageNumber-1)*pageSize;
    List<Job> list = jobDao.query(job,offset,pageSize);
    Map<String,Object> map = new HashMap<String, Object>();
    map.put("rows",list);
    map.put("total",count);
    return map;
}

这里,我controller方法也直接返回map,由于加了@ResponseBody,返回到前端时会根据需要转化成jsonString:

@ResponseBody
public Map selectJob(@RequestParam("rows")int rows, @RequestParam("page") int page, Job job){
    //前台如果没传这些参数,则默认是空字符串,而不是null。因此当没传该参数时要设置为null,这样dao层才不会以该参数作为查询条件
    if("".equals(job.getName()))
        job.setName(null);
    if("".equals(job.getRemark()))
        job.setRemark(null);
    if("".equals(job.getDeptId()))
        job.setDeptId(null);
    return jobService.findJob(job,rows,page);
}

哦对,对于增删改以外的操作,即查询操作,不把结果封装到ResultDTO中,直接返回该List对象或者Map对象或转json字符串既可以。

总结

这里只是对Job模块进行处理,其他模块也是类似的。日志及异常通过AOP统一处理了。

重构后,代码就清晰很多了。前后端交互也友好了。