项目代码重构

项目代码重构

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

一开始,对于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统一处理了。

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

猜你喜欢

转载自blog.csdn.net/weixin_30531261/article/details/79912690