In this article, we'll look at some of the ways these services can be better directed in the confusing situations found in startup Java servers.
** Controller Base Class **
/** Controller Base Classes */
public class BaseController {
/** Injection services related */
/** User Service */
@Autowired
protected UserService userService;
...
/** Static constant correlation */
/** Phone number mode */
protected static final String PHONE_PATTERN = "/^[1]([3-9])[0-9]{9}$/";
...
/** Static function related */
/** Verify phone number */
protected static vaildPhone(String phone) {...}
...
}
The common controller-based class mainly contains injection services, static constants, and static functions, and all controllers inherit these resources from the controller-based class and can use them directly in their functions. I will.
** Service Base Class ** The general Service Base Class is as follows.
/** Service Base Classes */
public class BaseService {
/** Injection DAO related */
/** User DAO */
@Autowired
protected UserDAO userDAO;
...
/** Injection services related */
/** SMS service */
@Autowired
protected SmsService smsService;
...
/** Injection parameters related */
/** system name */
@Value("${example.systemName}")
protected String systemName;
...
/** Injection constant related */
/** super user ID */
protected static final long SUPPER_USER_ID = 0L;
...
/** Service function related */
/** Get user function */
protected UserDO getUser(Long userId) {...}
...
/** Static function related */
/** Get user name */
protected static String getUserName(UserDO user) {...}
...
}
Common service-based classes are mainly injection data access object (DAO), injection It includes services, injection parameters, static constants, service functions, and static functions, and all services inherit these resources from service-based classes and can be used directly in the functions.
First, Liskov Substitution Principle (LSP )Let's look at.
According to the LSP, you must make the objects of that subclass transparently available everywhere you reference the base class (superclass).
Next, let's take a look at the advantages of the base class.
Therefore, we can draw the following conclusions.
The bottom line is that both controller-based and service-based classes fall into miscellaneous classes. These are not really base classes and need to be split.
Since the service base class is more typical than the controller base class, this article will explain how to divide the "base class" using the service base class as an example.
** Put the injection instance in the implementation class ** Inject the DAO, services, parameters, etc. to be used into the implementation class according to the principle of "introduce the class only when it is used and delete it when it is not needed".
/** Udser Service Class */
@Service
public class UserService {
/** User DAO */
@Autowired
private UserDAO userDAO;
/** SMS service */
@Autowired
private SmsService smsService;
/** System name */
@Value("${example.systemName}")
private String systemName;
...
}
** Put static constants in constant class ** Encapsulate static constants in the corresponding constant class and use them directly when needed.
/** example constant class */
public class ExampleConstants {
/** super user ID */
public static final long SUPPER_USER_ID = 0L;
...
}
** Put the service function in the service class ** Encapsulate the service function in the corresponding class of service. If you want to use another class of service, you can inject an instance of this class of service and call service functions through the instance.
/** User service class */
@Service
public class UserService {
/** Ger user function */
public UserDO getUser(Long userId) {...}
...
}
/** Company service class */
@Service
public class CompanyService {
/** User service */
@Autowired
private UserService userService;
/** Get the administrator */
public UserDO getManager(Long companyId) {
CompanyDO company = ...;
return userService.getUser(company.getManagerId());
}
...
}
** Put static functions in tool class ** Encapsulate static functions in the corresponding tool class and use them directly when needed.
/** User Aid Class */
public class UserHelper {
/** Get the user name */
public static String getUserName(UserDO user) {...}
...
}
I often see code like the following in the controller class.
/** User Controller Class */
@Controller
@RequestMapping("/user")
public class UserController {
/** User DAO */
@Autowired
private UserDAO userDAO;
/** Get user function */
@ResponseBody
@RequestMapping(path = "/getUser", method = RequestMethod.GET)
public Result<UserVO> getUser(@RequestParam(name = "userId", required = true) Long userId) {
// Get user information
UserDO userDO = userDAO.getUser(userId);
if (Objects.isNull(userDO)) {
return null;
}
// Copy and return the user
UserVO userVO = new UserVO();
BeanUtils.copyProperties(userDO, userVO);
return Result.success(userVO);
}
...
}
The compiler might explain that you could write it this way because the interface function is simple and you don't need to encapsulate the interface function in the service function, but in reality it encapsulates the interface function in the service function. There is no need to interface.
In this special case, the code looks like this:
/** Test Controller Class */
@Controller
@RequestMapping("/test")
public class TestController {
/** System name */
@Value("${example.systemName}")
private String systemName;
/** Access function */
@RequestMapping(path = "/access", method = RequestMethod.GET)
public String access() {
return String.format("You're accessing System (%s)!", systemName);
}
}
The access results are as follows.
curl http://localhost:8080/test/access
You are accessing System (null)!
You may be asked why the systemName parameter is not injected. By the way, the document of Spring is as follows. There is an explanation.
The actual processing of @Value annotation is [BeanPostProcessor](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html?spm= Note that it is run by a2c65.11461447.0.0.7a631744QsPCA3).
The BeanPostProcessor interface is scoped on a per-container basis. This is only relevant if you are using a container hierarchy. If you define BeanPostProcessor in one container, the work is performed only for the beans in that container. Beans defined in one container are not post-processed by the BeanPostProcessor in another container, even if both containers are part of the same hierarchy.
According to these explanations, @Value is processed via BeanPostProcessor and WebApplicationContex //docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/WebApplicationContext.html?spm=a2c65.11461447.0.0.7a631744QsPCA3) and [ApplicationContext](https: / /docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html?spm=a2c65.11461447.0.0.7a631744QsPCA3) is processed separately. Therefore, WebApplicationContex cannot use the attribute value of the parent container.
The controller does not meet the service requirements. Therefore, it is inappropriate to write business code in the controller class.
SpringMVC Server is presentation layer, business layer, persistence It uses the classic three-layer architecture of layers, using @Controller, @Service, and @Repository for class annotation.
--Presentation layer: Also called the controller layer. This layer is responsible for receiving requests from clients and responding to clients with results from clients. HTTP is often used at this layer. --Business layer: Also called service layer. This layer is in charge of business-related logic processing, and is divided into services and jobs by function. --Persistence layer: Also known as repository layer. This tier is responsible for data persistence and is used by business tiers to access caches and databases.
Therefore, writing business code in the controller class does not comply with the Spring MVC server's three-tier architecture specification.
On the functional side, I think it's okay to write persistence layer code in the service class. That's why many users accept this coding method.
Here, the direct query of the database persistence middleware Hibernate is explained as an example.
** Explanation of the phenomenon **
/** User Service Class */
@Service
public class UserService {
/** Session factory */
@Autowired
private SessionFactory sessionFactory;
/** Get user function based on job number */
public UserVO getUserByEmpId(String empId) {
// Assemble HQL statement
String hql = "from t_user where emp_id = '" + empId + "'";
// Perform database query
Query query = sessionFactory.getCurrentSession().createQuery(hql);
List<UserDO> userList = query.list();
if (CollectionUtils.isEmpty(userList)) {
return null;
}
// Convert and return user
UserVO userVO = new UserVO();
BeanUtils.copyProperties(userList.get(0), userVO);
return userVO;
}
}
** Recommended solution **
/** User DAO CLass */
@Repository
public class UserDAO {
/** Session factory */
@Autowired
private SessionFactory sessionFactory;
/** Get user function based on job number */
public UserDO getUserByEmpId(String empId) {
// Assemble HQLstatement
String hql = "from t_user where emp_id = '" + empId + "'";
// Perform database query
Query query = sessionFactory.getCurrentSession().createQuery(hql);
List<UserDO> userList = query.list();
if (CollectionUtils.isEmpty(userList)) {
return null;
}
// Return user information
return userList.get(0);
}
}
/** User Service Class */
@Service
public class UserService {
/** User DAO */
@Autowired
private UserDAO userDAO;
/** Get user function based on job number */
public UserVO getUserByEmpId(String empId) {
// Query user based on job number
UserDO userDO = userDAO.getUserByEmpId(empId);
if (Objects.isNull(userDO)) {
return null;
}
// Convert and return user
UserVO userVO = new UserVO();
BeanUtils.copyProperties(userDO, userVO);
return userVO;
}
}
** About plugins ** AliGenerator was developed by Alibaba [MyBatis Generator] ](Https://mybatis.org/generator/?spm=a2c65.11461447.0.0.7a631744QsPCA3) -based tool that automatically generates code for the DAO (Data Access Object) layer. In the code generated by AliGenerator, when executing a complex query, it is necessary to construct the query condition in the business code. As a result, the business code becomes particularly bloated.
/** User Service Class */
@Service
public class UserService {
/** User DAO */
@Autowired
private UserDAO userDAO;
/** Get user function */
public UserVO getUser(String companyId, String empId) {
// Query database
UserParam userParam = new UserParam();
userParam.createCriteria().andCompanyIdEqualTo(companyId)
.andEmpIdEqualTo(empId)
.andStatusEqualTo(UserStatus.ENABLE.getValue());
List<UserDO> userList = userDAO.selectByParam(userParam);
if (CollectionUtils.isEmpty(userList)) {
return null;
}
// Convert and return users
UserVO userVO = new UserVO();
BeanUtils.copyProperties(userList.get(0), userVO);
return userVO;
}
}
Personally, I don't like using plugins to generate code for DAO layers. Instead, I prefer to use the original MyBatis XML for the mapping.
--The plugin may import incompatible code into your project. --To execute a simple query, you need to import the complete set of complex code. --For complex queries, the code for constructing conditions is complex and unintuitive. It is better to write the SQL statement directly in XML. --After modifying the table, you need to regenerate and overwrite the code, in the meantime you can accidentally drop a user-defined function (UDF).
If you choose to use a plugin, you should enjoy the benefits it brings while also accepting the disadvantages of the plugin.
Description
/** User Service Class */
@Service
public class UserService {
/** User DAO */
@Autowired
private UserDAO userDAO;
/** Redistemplate */
@Autowired
private RedisTemplate<String, String> redisTemplate;
/** User primary key mode */
private static final String USER_KEY_PATTERN = "hash::user::%s";
/** Save user function */
public void saveUser(UserVO user) {
// Convert user information
UserDO userDO = transUser(user);
// Save Redis user
String userKey = MessageFormat.format(USER_KEY_PATTERN, userDO.getId());
Map<String, String> fieldMap = new HashMap<>(8);
fieldMap.put(UserDO.CONST_NAME, user.getName());
fieldMap.put(UserDO.CONST_SEX, String.valueOf(user.getSex()));
fieldMap.put(UserDO.CONST_AGE, String.valueOf(user.getAge()));
redisTemplate.opsForHash().putAll(userKey, fieldMap);
// Save database user
userDAO.save(userDO);
}
}
** Recommended solution **
/** User Redis Class */
@Repository
public class UserRedis {
/** Redistemplate */
@Autowired
private RedisTemplate<String, String> redisTemplate;
/** Primary key mode */
private static final String KEY_PATTERN = "hash::user::%s";
/** Save user function */
public UserDO save(UserDO user) {
String key = MessageFormat.format(KEY_PATTERN, userDO.getId());
Map<String, String> fieldMap = new HashMap<>(8);
fieldMap.put(UserDO.CONST_NAME, user.getName());
fieldMap.put(UserDO.CONST_SEX, String.valueOf(user.getSex()));
fieldMap.put(UserDO.CONST_AGE, String.valueOf(user.getAge()));
redisTemplate.opsForHash().putAll(key, fieldMap);
}
}
/** User Service Class */
@Service
public class UserService {
/** User DAO */
@Autowired
private UserDAO userDAO;
/** User Redis */
@Autowired
private UserRedis userRedis;
/** Save user function */
public void saveUser(UserVO user) {
//Personification for transformation
UserDO userDO = transUser(user);
// Save Redis user
userRedis.save(userDO);
// Save database user
userDAO.save(userDO);
}
}
Encapsulates Redis object-related operational interfaces in DAO classes. It adheres to the Spring MVC server's object-oriented programming principles and 3-tier architecture specifications, making code easier to manage and maintain.
/** User DAO Class */
@Repository
public class UserDAO {
/** Get user function */
public UserDO getUser(Long userId) {...}
}
/** User Service Class */
@Service
public class UserService {
/** User DAO */
@Autowired
private UserDAO userDAO;
/** Get user function */
public UserDO getUser(Long userId) {
return userDAO.getUser(userId);
}
}
/** User Controller Class */
@Controller
@RequestMapping("/user")
public class UserController {
/** User service */
@Autowired
private UserService userService;
/** Get user function */
@RequestMapping(path = "/getUser", method = RequestMethod.GET)
public Result<UserDO> getUser(@RequestParam(name = "userId", required = true) Long userId) {
UserDO user = userService.getUser(userId);
return Result.success(user);
}
}
The predecessor code seems to conform to the Spring MVC server's three-tier architecture. The only problem is that the database model UserDO is exposed directly to the external interface.
** Existing problems **
solution
The following describes how to build Java projects more scientifically to effectively prevent developers from exposing database model classes to the interface.
** Method 1: Build a project with a shared model ** Place all model classes in one model project (example-model). All other projects (example-repository, example-service, example-website, etc.) rely on example-model. The relationship diagram is as follows.
risk The presentation layer project (example-webapp) can call any service function of the business layer project (example-service), and the DAO function of the persistence layer project (example-repository) can be directly applied across the business layers. You can also call it.
** Method 2: Build a project with a separate model ** Build an API project (example-api) separately to abstract the external interface and its model VO class. The business layer project (example-service) implements these interfaces and provides services to the presentation layer project (example-webapp). The presentation layer project (example-webapp) calls only the service interfaces defined in the API project (example-api).
risk The presentation layer project (example-webapp) can still call the internal service function of the business layer project (example-service) and the DAO function of the persistence layer project (example-repository). To avoid this situation, the management system should allow the presentation layer project (example-webapp) to call only the service interface functions defined by the API project (example-api).
** Method 3: Build a service-oriented project ** Package the business tier project (example-service) and persistence tier project (example-repository) into a service using the Dubbo project (example-dubbo). Provides interface functionality defined in API projects (example-api) for business layer projects (example-webapp) or other business projects (other-service).
Note: The Dubbo project (example-dubbo) releases only the service interfaces defined in the API project (example-api). This ensures that the database model is not exposed. Business layer projects (eg-webapp) and other business projects (other services) depend only on API projects (eg-api) and can only call service interfaces defined in the API project.
Some users may have the following considerations: Considering that the interface model and the persistent layer model are separated, if the interface model defines the VO class of the data query model, the persistent layer model also needs to define the DO class of the data query model. There will be. Also, if the interface model defines the VO class of the data return model, the persistence layer model must also define the DO class of the data return model. However, this is not well suited for rapid iterative development early in the project. In addition, the following questions also arise. Is it possible to let the interface data model be used by the persistence layer without exposing the persistence layer data model through the interface?
This method is unacceptable as it affects the independence of the Spring MVC server's three-tier architecture. However, this method does not expose the database model class and is acceptable for rapid iterative development. Therefore, this is a less recommended proposal.
/** User DAO Class */
@Repository
public class UserDAO {
/** Calculate user function */
public Long countByParameter(QueryUserParameterVO parameter) {...}
/** Query user function */
public List<UserVO> queryByParameter(QueryUserParameterVO parameter) {...}
}
/** User Service Class */
@Service
public class UserService {
/** User DAO */
@Autowired
private UserDAO userDAO;
/** Query user function */
public PageData<UserVO> queryUser(QueryUserParameterVO parameter) {
Long totalCount = userDAO.countByParameter(parameter);
List<UserVO> userList = null;
if (Objects.nonNull(totalCount) && totalCount.compareTo(0L) > 0) {
userList = userDAO.queryByParameter(parameter);
}
return new PageData<>(totalCount, userList);
}
}
/** User Controller Class */
@Controller
@RequestMapping("/user")
public class UserController {
/** User service */
@Autowired
private UserService userService;
/** Query user function (with the page index parameters of startIndex and pageSize) */
@RequestMapping(path = "/queryUser", method = RequestMethod.POST)
public Result<PageData<UserVO>> queryUser(@Valid @RequestBody QueryUserParameterVO parameter) {
PageData<UserVO> pageData = userService.queryUser(parameter);
return Result.success(pageData);
}
}
Everyone has their own opinions on how to take advantage of Java, and of course this article only gives my personal opinion. But for me, I thought it was important to express my thoughts based on my experience with some startups I worked for before. Because, in my understanding, if these chaotic configurations are fixed, the whole system will be better.
Recommended Posts