SpringBoot合理使用接口对业务代码和逻辑代码进行分离

6,568 阅读6分钟

折腾无极限,技术永无止境

前言

未标准化进行java开发时,没有进行对业务代码以及公用逻辑进行分离,如果需求遇到改动,需要改公用逻辑时,需要去一处处业务代码中去找,大大的浪费了时间,使用接口后,可以合理的解决这一问题。

接口的概念

  • 使用interface声明的类
  • 只有方法标识符,没有方法体
  • 接口像蓝图一样,指明一个类必须要做什么和不能做什么
  • 接口中所有的方法都是抽象的和public的,所有的属性都是public,static,final的。
  • 一个类如果要实现某个接口时,必须要实现这个接口中的所有方法。

接口的作用

  • 实现多继承:因为Java不能像c++一样支持多继承,Java可以通过实现接口来弥补这个问题。
  • 接口可以解偶
  • 可以将逻辑代码与业务代码进行分

接口的语法

  • 使用interface声明一个类,在类内声明一个注册方法
public interface UserService {
    // 声明一个方法
    public void method();
}

  • 创建接口实现类,使用Override重写接口中声明的方法
// 实现接口中声明的方法
    @Override
    public void method() {
       // +++++++ 具体的实现 +++++++
    }

至此就就完成了一个接口的声明到实现

实际应用

我们先来看看不使用接口时,将功能的实现和业务代码全写在controller层的代码,这里我们以登录为例,如果遇到新的需求,我们就要去找找功能实现部分进行修改,业务代码过多时是一件很头疼的事情

@RestController
@RequestMapping("/user")
public class GreetingController {
    private final Logger logger= LoggerFactory.getLogger(getClass());
    @Resource
    private UserInfoMapper userMapper;
    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();
    @CrossOrigin()
    // 登录请求
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(@RequestBody String request){
        // 获取前端参数
        JSONObject jsReq = new JSONObject(request);
        JSONObject jsReply = new JSONObject();
        String username = jsReq.getString("username");
        String password = jsReq.getString("password");
        UserInfo row = new UserInfo();
        JSONArray queryType = new JSONArray();
        queryType.put("name");
        queryType.put("password");
        queryType.put("career");
        queryType.put("avatarSrc");
        queryType.put("userID");
        row.setQueryType(queryType);
        row.setName(username);
        List<UserInfo> resultList = userMapper.customQuery(row);
        JSONArray result = null;
        if(resultList.size()>0){
            result = new JSONArray(resultList);
            if(password.equals(result.getJSONObject(0).getString("password"))){
                jsReply.put("code",0);
                // 根据当前用户名和密码生成token
                jsReply.put("token", JwtUtil.sign(username,password));
                jsReply.put("msg","验证成功,token有效期为30分钟");
                jsReply.put("avatarSrc",result.getJSONObject(0).getString("avatarSrc"));
                jsReply.put("userID",result.getJSONObject(0).getString("userID"));
            }else{
                jsReply.put("code",-2);
                jsReply.put("msg","密码错误");
            }
        }else{
            jsReply.put("code",-1);
            jsReply.put("msg","当前登录用户不存在");
        }
        return jsReply.toString();
    }

}

  • 使用接口对上述场景的代码进行分离(这里我们以注册功能为例)
// service层
package com.lk.service;

import com.lk.enums.ResultEnum;

public interface UserService {
    // 新用户注册接口
    public ResultEnum AddUser(String userName, String password, String avatarSrc) throws Exception;
}


// serviceimpl层(接口实现)
package com.lk.serviceimpl;

import com.lk.dao.UserMapper;
import com.lk.entity.User;
import com.lk.enums.ResultEnum;
import com.lk.service.UserService;
import com.lk.utils.DateUtil;
import com.lk.utils.HMacMD5;
import com.lk.utils.UUIDUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
// @Slf4j是lombok的注解,使用此注解后可以在当前类直接使用log关键字来打印日志
@Slf4j
@Service("userServiceImpl")
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper userMapper;
    // 从配置文件读取加密key
    @Value("${HMacKey}")
    private String HMacKey;
    // 实现注册接口
    @Override
    public ResultEnum AddUser(String userName, String password, String avatarSrc) throws Exception {
        // 判断参数是否合法
        if(StringUtils.isBlank(userName) ||userName.length()>10){
            log.error("违规操作:用户名为空或用户名长度大于10,"+userName);
            return ResultEnum.USER_ERROR;
        }
        if(StringUtils.isBlank(password)||password.length()>16){
            log.error("违规操作:密码为空或密码长度大于10,"+password);
            return ResultEnum.PASSWORD_ERR;
        }
        if(StringUtils.isBlank(avatarSrc)||avatarSrc.length()>300){
            log.error("违规操作:头像地址为空或头像地址过长,"+avatarSrc);
            return ResultEnum.AVATAR_ERR;
        }
        // 对密码进行加密
        password = HMacMD5.encryptHMAC2String(password,HMacKey);
        User row = new User();
        row.setUserName(userName);
        row.setUserId(UUIDUtil.getUUID());
        row.setPassword(password);
        row.setAvatarSrc(avatarSrc);
        row.setCreateTime(DateUtil.getThisTime());
        log.info(userMapper+"");
        // 增加用户,向数据库插入一条数据
        int result = userMapper.insertUser(row);
        return ResultEnum.SUCCESS;
    }
}

// controller层(业务代码)
package com.lk.controller;

import com.lk.VO.ResultVO;
import com.lk.dao.UserMapper;
import com.lk.entity.User;
import com.lk.enums.ResultEnum;
import com.lk.serviceimpl.UserServiceImpl;
import com.lk.utils.ResultVOUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@Api(value = "/api/user", tags = "用户信息接口模块")
@RestController
@RequestMapping("/api/user")
@Slf4j
public class UserController {
    // 注入用户接口
    @Resource(name = "userServiceImpl")
    private UserService userService;
    @ApiOperation(value = "注册接口", notes = "传用户名、密码、头像地址来注册用户")
    @RequestMapping(value = "/registered", method = RequestMethod.POST)
    public ResultVO registered(@RequestBody User user) throws Exception {
        // 调用注册接口
        ResultEnum result = userService.AddUser(user.getUserName(),user.getPassword(),user.getAvatarSrc());
        // 获取返回枚举的code判断是否成功
        if(result.getCode()==0){
            return ResultVOUtil.success(result.getMessage());
        }
        return ResultVOUtil.error(result.getCode(),result.getMessage());
    }
}

特别感谢

感谢评论区开发者给我提出的建议,让我意识到自己有的Java有多菜,之前写项目一直处于松散开发,好多场景都没考虑到,写的代码凌乱不堪。我没有真实参加过公司的Java项目开发,因为我全职前端,文中讲的不到位的地方,还望大家多多包涵。 针对评论区开发者的建议,我做了如下修改:

  • controller不要写任何逻辑只做控制,将所有的逻辑代码放在接口的impl里实现
  • 使用枚举类声明一组命名的常数,便于判断接口相关操作是否成功,解决了魔法数字问题
  • 修改接口返回类型为枚举
  • 修改之前的json接收前端参数方法,采用dto来接收前端参数
  • 修改之前返回使用json构造返回数据的方法,采用VO结果视图给前端返回参数
  • 集成swagger,用于生成接口文档
  • 至于评论区@muruxing 开发者说的我为什么不使用@autowired注解,因为使用这个注解时idea会有黄线警告,所以我使用了@Resource,而且我考虑到同一个接口可能有很多的实现类,所以我采用给每一个实现类注入@Service("userServiceImpl"),然后在Controller中使用时通过@Resource(name="userServiceImpl")方法实例化对应的实现类
  • 使用@Slf4j注解,省去了之前在实例化logger的工厂对象

踩坑记录

在controller层使用接口实现类里的方法时,spring注解的内容一直为null,无法拿到@Resource注解的mybatis里的mapper对象和@Value注解的获取yml文件中的属性,接口实现类报空指针异常。 原因是:我在controller层实例化接口实现类时,对象是自己new的,没有走spring,所以一直为null。找了很多解决方案,走了很多弯路,最终使用@Service注解和@Resource注解完美解决了这个问题。 我的问题可以描述为:接口的实现类在controller层如何注入

  • 在controller层自己new接口实现类对象
// 错误的解决方案,实现类不走spring容器,报空指针异常
UserServiceImpl userService = new UserServiceImpl();
  • 在接口实现类中使用@service注解,在controller层使用@Resource注入
// 这里括号中的名字为当前类名首字母小写
@Service("userServiceImpl")
public class UserServiceImpl implements UserService {
     /////++++++++++++++++++++++++/////
}
// controller层
public class UserController {
    // 括号里的name值对应接口实现类@Service注解括号里的名字
    @Resource(name = "userServiceImpl")
    private UserService userService;
}

至此,关于接口实现类在controller层如何注入的问题就完美解决了,带大家看下上述注册接口实现的效果

写在最后

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
  • 本文首发于掘金,如需转载请评论区留言💌