项目代码重构
项目代码重构
毕设做完,能跑起来,但是感觉代码写的不好。
一开始,对于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统一处理了。
重构后,代码就清晰很多了。前后端交互也友好了。
推荐阅读
-
完成OSS.Http底层HttpClient重构封装 支持标准库
-
Laravel 中使用 swoole 项目实战开发案例一 (建立 swoole 和前端通信)
-
使用nginx代理gogs遇到推送代码错误的问题(RPC failed; HTTP 413 curl 22 The requested URL returned error: 413)
-
如何写出让同事无法维护的代码?
-
在家上班赚钱的项目有哪些,这五个职业在家也能月收入3000+
-
在Ubuntu上搭建一个基于webrtc的多人视频聊天服务实例代码详解
-
SQL2005CLR函数扩展-繁简转换的实现代码
-
vue2.0实战之使用vue-cli搭建项目(2)
-
Python编程二分法实现冒泡算法+快速排序代码示例
-
Python获取当前公网ip并自动断开宽带连接实例代码