再见!三层框架开发
说明:三层框架开发 是常见的 JavaWeb 开发模式,依次分为
-
Controller:接收参数,返回响应结果;
-
Service:处理业务,实现功能;
-
DAO:操作数据库;
有的项目不是严格的三层架构,可能会多一层,但这种分层开发模式还是一样的。
本文使用 commons-chain
介绍一种链式开发模式,不同于常见的三层架构开发。
三层架构写法
(1)创建项目
创建一个 Maven 项目,pom 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version><relativePath/></parent><groupId>com.hezy</groupId><artifactId>ChainDemo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>commons-chain</groupId><artifactId>commons-chain</artifactId><version>1.2</version></dependency></dependencies>
</project>
其中,最后是后面要用到的链式写法所需要的依赖
<dependency><groupId>commons-chain</groupId><artifactId>commons-chain</artifactId><version>1.2</version>
</dependency>
(2)创建接口
一个简单的 Demo,有以下两个接口,一个存入用户对象,一个读取用户对象,
import com.hezy.pojo.User;
import com.hezy.service.general.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** 通常操作*/
@RestController
@RequestMapping("/demo")
public class DemoController {@Autowiredprivate UserService userService;@PostMapping("/add")public List<String> addUser(@RequestBody User user) {return userService.addUser(user);}@GetMapping("/get")public User getUser(String username) {return userService.getUser(username);}
}
User 对象如下:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;/*** 用户*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {/*** 用户名*/private String username;/*** 年龄*/private Integer age;
}
(3)实现
实现接口
(UserService)
import com.hezy.pojo.User;import java.util.List;public interface UserService {List<String> addUser(User user);User getUser(String username);
}
(UserServiceImpl,实现类,用一个 Map 模拟存入 User 对象数据)
import com.hezy.pojo.User;
import lombok.Setter;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;import static com.hezy.common.RegexConstant.USERNAME_PATTERN;@Service
@Setter
public class UserServiceImpl implements UserService {private final static Map<String, User> USER_MAP = new HashMap<>();@Overridepublic List<String> addUser(User user) {List<String> errorList = new ArrayList<>();// 检查 User 对象是否为空if (user == null) {errorList.add("用户对象为空");return errorList;}// 检查 usernameif (user.getUsername() == null || user.getUsername().trim().isEmpty()) {errorList.add("用户名为空");return errorList;}if (!USERNAME_PATTERN.matcher(user.getUsername()).matches()) {errorList.add("用户名只允许英文大小写字母");return errorList;}// 检查 ageif (user.getAge() == null) {errorList.add("用户名年龄不能为空");return errorList;}if (user.getAge() <= 0 || user.getAge() > 100) {errorList.add("用户名年龄只能在0~100以内");return errorList;}// 存入对象USER_MAP.put(user.getUsername(), user);return null;}@Overridepublic User getUser(String username) {return USER_MAP.get(username);}
}
其中对用户名进行了校验,正则表达式如下,用户名只允许英文大小写字母
import java.util.regex.Pattern;public interface RegexConstant {/*** 正则表达式:只允许英文大小写字母*/Pattern USERNAME_PATTERN = Pattern.compile("^[a-zA-Z]+$");
}
(4)测试
启动项目,测试 Demo
(存入)
(读取)
以上是使用三层架构实现的开发模式
链式写法
下面使用 commons-chain
链式写法来实现
(1)创建处理链
创建处理链,将创建用户的步骤在这里组装完成,分为参数校验、存入用户两个步骤,对应两个 Command
import org.apache.commons.chain.impl.ChainBase;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;import javax.annotation.Resource;/*** 用户创建处理链*/
@Component
public class UserCreationChain extends ChainBase implements InitializingBean {@Resourceprivate ValidateUserCommand validateUserCommand;@Resourceprivate SaveUserCommand saveUserCommand;@Overridepublic void afterPropertiesSet() {// 参数校验addCommand(validateUserCommand);// 存入用户addCommand(saveUserCommand);// 结束addCommand(e -> PROCESSING_COMPLETE);}
}
(2)创建上下文
创建对应处理链的上下文,这个上下文与处理链没有代码上的绑定关系,可以类比三层架构的Controller 和 Service
import com.hezy.pojo.User;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.chain.impl.ContextBase;import java.util.*;/*** 用户上下文*/
@Getter
@Setter
public class UserContext extends ContextBase {/*** 模拟数据库,存储用户数据*/public static final Map<String, User> DATABASE = new Hashtable<>();/*** 参数*/private User user;/*** 错误信息*/private List<String> errorList;
}
点开继承的 ContextBase
类,可以看到这是一个 Map
(3)创建 Command
创建处理链中的两个 Command,如下:
(校验用户,实现 Command,返回 true 表示处理链终止,false 表示继续往后执行)
import com.hezy.pojo.User;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.springframework.stereotype.Component;import java.util.ArrayList;
import java.util.List;import static com.hezy.common.RegexConstant.USERNAME_PATTERN;/*** 校验用户*/
@Component
public class ValidateUserCommand implements Command {@Overridepublic boolean execute(Context context) {UserContext userContext = (UserContext)context;User user = userContext.getUser();List<String> errorList = new ArrayList<>();// 检查 User 对象是否为空if (user == null) {errorList.add("用户对象为空");userContext.setErrorList(errorList);return true;}// 检查 usernameif (user.getUsername() == null || user.getUsername().trim().isEmpty()) {errorList.add("用户名为空");userContext.setErrorList(errorList);return true;}if (!USERNAME_PATTERN.matcher(user.getUsername()).matches()) {errorList.add("用户名只允许英文大小写字母");userContext.setErrorList(errorList);return true;}// 检查 ageif (user.getAge() == null) {errorList.add("用户名年龄不能为空");userContext.setErrorList(errorList);return true;}if (user.getAge() <= 0 || user.getAge() > 100) {errorList.add("用户名年龄只能在0~100以内");userContext.setErrorList(errorList);return true;}userContext.put(user.getUsername(), user);userContext.setErrorList(null);return false;}
}
(存入对象)
import com.hezy.pojo.User;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.springframework.stereotype.Component;import static com.hezy.service.chain.UserContext.DATABASE;/*** 保存用户命令*/
@Component
public class SaveUserCommand implements Command {@Overridepublic boolean execute(Context context) {UserContext userContext = (UserContext)context;User user = userContext.getUser();// 模拟保存DATABASE.put(user.getUsername(), user);return false;}
}
(4)接口
接口如下,定义上下文,将接收的参数存入到上下文中,接着执行处理链,最后返回上下文中的错误信息
import com.hezy.pojo.User;
import com.hezy.service.chain.UserContext;
import com.hezy.service.chain.UserCreationChain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;import static com.hezy.service.chain.UserContext.DATABASE;/*** 用户接口*/
@RestController
@RequestMapping("/user")
public class UserController {final UserContext userContext = new UserContext();@Autowiredprivate UserCreationChain userCreationChain;@PostMapping("/add")public List<String> addUser(@RequestBody User user) throws Exception {userContext.setUser(user);userCreationChain.execute(userContext);return userContext.getErrorList() != null ? userContext.getErrorList() : null;}@GetMapping("/get")public User getUser(String username) {return DATABASE.get(username);}
}
(4)测试
启动,测试
(年龄为负数,未通过校验)
(没通过校验,没有走到后面的存入,所以没查出来)
(添加没问题)
(也能查出来)
以上就是使用 commons-chain
链式写法实现 JavaWeb 接口开发的 Demo,相比较三层架构,如果接口的实现逻辑复杂,与其在 Service 层里调来调去,或许可以考虑如何使用这种链式写法。
另外
厘清两个概念,Context、Command
Context
Context,翻译为上下文或语境,是一个抽象概念。举个例子,下面这张宣传海报
海报中劳动职业者有公务员、外卖员、工人、农民、老师、医生和上班族,分析这张海报工人、外卖员都是男性,医生、老师都是女性,我们能否得出结论:所有的工人、外卖员都是男性,所有的医生、老师都是女性?
不可以,因为这是一张劳动者职业健康的宣传海报,起宣传作用,这里的“宣传海报”就是上下文,是 Context。
就是说,将对象框起来,放入一些额外的信息,这些框起来的,额外的信息就是上下文。
Command
在本文介绍的链式开发,Command 翻译为命令我觉得是不合适的,命令有点抽象过头,没有体现出链式开发的具体场景。我们可以想象一个场景,领导让你去他办公室帮他找一个笔记本:
-
“桌子上有吗?你看看”
-
“我看看,唔……没看到”
-
“右手抽屉里,翻翻看”
-
“没有”
-
“那应该在背后柜子里了”
-
“……,有,是黑色的吗?”
-
“是的,你送到会议室给我”
-
“好的”
这里领导说的话,就是链式开发中的 Commond,是带有上下文的,有前后逻辑的指令,翻译为指令或许更贴切。
总结
本文使用 commons-chain
介绍了一种链式开发模式,不同于常见的三层架构开发。