Dans cet article, nous examinerons certaines des façons dont ces services peuvent être mieux orientés dans les situations déroutantes rencontrées dans les serveurs Java de démarrage.
** Classe de base du contrôleur **
/** 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) {...}
...
}
La classe commune basée sur le contrôleur comprend principalement les services d'injection, les constantes statiques et les fonctions statiques, et tous les contrôleurs héritent de ces ressources de la classe basée sur le contrôleur et peuvent les utiliser directement dans la fonction. Je vais.
** Classe de base de service ** La classe de base du service général est la suivante.
/** 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) {...}
...
}
Les classes courantes basées sur les services sont principalement l'injection objet d'accès aux données (DAO), injection Il comprend des services, des paramètres d'injection, des constantes statiques, des fonctions de service et des fonctions statiques, et tous les services héritent de ces ressources de classes basées sur les services et peuvent être utilisés directement dans les fonctions.
Premièrement, Principe de substitution de Liskov (LSP )Regardons.
Selon le LSP, les objets de cette sous-classe doivent être disponibles de manière transparente partout qui fait référence à la classe de base (superclasse).
Ensuite, jetons un œil aux avantages de la classe de base.
Par conséquent, nous pouvons tirer les conclusions suivantes.
L'essentiel est que les classes basées sur les contrôleurs et les services appartiennent à des classes diverses. Ce ne sont pas vraiment des classes de base et doivent être divisées.
Étant donné que la classe de base de service est plus typique que la classe de base du contrôleur, cet article explique comment diviser la «classe de base» à l'aide de la classe de base de service comme exemple.
** Mettez l'instance d'injection dans la classe d'implémentation ** Injectez le DAO, les services, les paramètres, etc. à utiliser dans la classe d'implémentation selon le principe «d'introduire la classe uniquement lorsqu'elle est utilisée et de la supprimer lorsqu'elle n'est pas nécessaire».
/** 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;
...
}
** Mettez les constantes statiques dans la classe constante ** Encapsulez les constantes statiques dans la classe de constante correspondante et utilisez-les directement si nécessaire.
/** example constant class */
public class ExampleConstants {
/** super user ID */
public static final long SUPPER_USER_ID = 0L;
...
}
** Mettez la fonction de service dans la classe de service ** Encapsulez la fonction de service dans la classe de service correspondante. Si vous souhaitez utiliser une autre classe de service, vous pouvez injecter une instance de cette classe de service et appeler des fonctions de service via l'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());
}
...
}
** Mettre les fonctions statiques dans la classe d'outils ** Encapsulez les fonctions statiques dans les classes d'outils correspondantes et utilisez-les directement si nécessaire.
/** User Aid Class */
public class UserHelper {
/** Get the user name */
public static String getUserName(UserDO user) {...}
...
}
Je vois souvent du code comme celui-ci dans la classe de contrôleur.
/** 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);
}
...
}
Le compilateur peut expliquer que vous pouvez l'écrire de cette façon car la fonction d'interface est simple et vous n'avez pas besoin d'encapsuler la fonction d'interface dans la fonction de service, mais en réalité, la fonction d'interface est encapsulée dans la fonction de service. Il n'y a pas besoin de changer.
Dans ce cas particulier, le code ressemble à ceci:
/** 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);
}
}
Les résultats d'accès sont les suivants.
curl http://localhost:8080/test/access
Vous accédez à System (null)!
On peut vous demander pourquoi le paramètre systemName n'a pas été injecté. À propos, le document de Spring est le suivant. Il y a une explication.
Le traitement réel de l'annotation @Value est [BeanPostProcessor](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html?spm= Notez qu'il est effectué par a2c65.11461447.0.0.7a631744QsPCA3).
L'interface BeanPostProcessor est étendue sur une base par conteneur. Cela n'est pertinent que si vous utilisez une hiérarchie de conteneurs. Si vous définissez un BeanPostProcessor dans un conteneur, ce travail est effectué uniquement pour les beans de ce conteneur. Les beans définis dans un conteneur ne sont pas post-traités par le BeanPostProcessor dans un autre conteneur, même si les deux conteneurs font partie de la même hiérarchie.
Selon ces explications, @Value est traité via BeanPostProcessor et WebApplicationContex. //docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/WebApplicationContext.html?spm=a2c65.11461447.0.0.7a631744QsPCA3) et [ApplicationContext](https: / /docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html?spm=a2c65.11461447.0.0.7a631744QsPCA3) est traité séparément. Par conséquent, WebApplicationContex ne peut pas utiliser la valeur d'attribut du conteneur parent.
Le contrôleur ne répond pas aux exigences du service. Par conséquent, il est inapproprié d'écrire du code métier dans la classe de contrôleur.
SpringMVC Les serveurs sont des niveaux présentation, métier, persistance Il utilise une architecture classique à trois niveaux avec des couches, utilisant @Controller, @Service et @Repository pour les annotations de classe.
--Couche de présentation: également appelée couche de contrôleur. Cette couche est chargée de recevoir les demandes des clients et de répondre aux clients avec les résultats des clients. HTTP est souvent utilisé à cette couche. --Couche commerciale: également appelée couche de service. Cette couche est responsable du traitement de la logique métier et est divisée en services et emplois par fonction.
Par conséquent, l'écriture de code métier dans la classe de contrôleur n'est pas conforme à la spécification d'architecture à trois niveaux du serveur Spring MVC.
Sur le plan fonctionnel, je pense qu'il est normal d'écrire le code de la couche de persistance dans la classe de service. C'est pourquoi de nombreux utilisateurs acceptent cette méthode de codage.
Ici, la requête directe du middleware de persistance de la base de données Hibernate est expliquée à titre d'exemple.
** Explication du phénomène **
/** 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;
}
}
** Solution recommandée **
/** 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;
}
}
** À propos des plug-ins ** AliGenerator a été développé par Alibaba [MyBatis Generator] ](Https://mybatis.org/generator/?spm=a2c65.11461447.0.0.7a631744QsPCA3) qui génère automatiquement du code pour la couche DAO (Data Access Object). Le code généré par AliGenerator vous oblige à créer des conditions de requête dans votre code métier lors de l'exécution de requêtes complexes. En conséquence, le code métier devient particulièrement gonflé.
/** 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;
}
}
Personnellement, je n'aime pas utiliser des plugins pour générer du code pour les couches DAO. Au lieu de cela, je préfère utiliser le MyBatis [XML] original (https://mybatis.org/mybatis-3/sqlmap-xml.html?spm=a2c65.11461447.0.0.7a631744QsPCA3) pour le mappage.
Si vous choisissez d'utiliser un plug-in, vous devriez profiter des avantages qu'il apporte tout en acceptant les inconvénients du plug-in.
La 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);
}
}
** Solution recommandée **
/** 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) {
//Informations pour la conversion
UserDO userDO = transUser(user);
// Save Redis user
userRedis.save(userDO);
// Save database user
userDAO.save(userDO);
}
}
Encapsule l'interface des opérations liées aux objets de Redis dans les classes DAO. Il adhère aux principes de programmation orientée objet du serveur Spring MVC et aux spécifications d'architecture à 3 niveaux, ce qui facilite la gestion et la maintenance du code.
/** 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);
}
}
Le code prédécesseur semble conforme à l'architecture à trois niveaux du serveur Spring MVC. Le seul problème est que le modèle de base de données UserDO est directement exposé à l'interface externe.
** Problèmes existants **
Solution
Ce qui suit décrit comment créer des projets Java de manière plus scientifique pour empêcher efficacement les développeurs d'exposer des classes de modèle de base de données à l'interface.
** Méthode 1: Construisez un projet avec un modèle partagé ** Placez toutes les classes de modèle dans un projet de modèle (exemple-modèle). Tous les autres projets (exemple-référentiel, service-exemple, site-exemple, etc.) reposent sur le modèle-exemple. Le diagramme des relations est le suivant.
risque Le projet de couche de présentation (exemple-webapp) peut appeler n'importe quelle fonction de service du projet de couche de gestion (exemple-service), et la fonction DAO du projet de couche de persistance (exemple-référentiel) peut être directement appliquée à travers les couches de gestion. Vous pouvez également l'appeler.
** Méthode 2: Construisez un projet avec un modèle séparé ** Créez un projet d'API (example-api) séparément pour faire abstraction de l'interface externe et de sa classe VO modèle. Le projet de niveau métier (exemple-service) implémente ces interfaces et fournit des services au projet de niveau présentation (exemple-webapp). Le projet de couche de présentation (example-webapp) appelle uniquement les interfaces de service définies dans le projet API (example-api).
risque Le projet de niveau présentation (exemple-webapp) peut toujours appeler la fonctionnalité de service interne du projet de niveau métier (exemple-service) et la fonctionnalité DAO du projet de niveau de persistance (exemple-référentiel). Pour éviter cette situation, le système de gestion doit permettre au projet de couche présentation (example-webapp) d'appeler uniquement les fonctions d'interface de service définies par le projet API (example-api).
** Méthode 3: Construire un projet orienté services ** Empaquetez le projet de niveau métier (exemple-service) et le projet de niveau de persistance (exemple-référentiel) dans un service à l'aide du projet Dubbo (exemple-dubbo). Fournit une fonctionnalité d'interface définie dans le projet d'API (example-api) pour les projets de couche de gestion (exemple-webapp) ou d'autres projets d'entreprise (autre service).
Remarque: Le projet Dubbo (exemple-dubbo) ne libère que l'interface de service définie dans le projet API (exemple-api). Cela garantit que le modèle de base de données n'est pas exposé. Les projets de couche métier (par exemple, webapp) et d'autres projets d'entreprise (par exemple, d'autres services) dépendent uniquement des projets API (par exemple, api) et ne peuvent appeler que les interfaces de service définies dans les projets API.
Certains utilisateurs peuvent avoir les considérations suivantes: Étant donné que le modèle d'interface et le modèle de couche persistante sont séparés, si le modèle d'interface définit la classe VO du modèle de requête de données, le modèle de couche persistante doit également définir la classe DO du modèle de requête de données. Il y aura. De plus, si le modèle d'interface définit la classe VO du modèle de retour de données, le modèle de couche de persistance doit également définir la classe DO du modèle de retour de données. Cependant, cela n'est pas bien adapté pour un développement itératif rapide au début du projet. En outre, les questions suivantes se posent également. Est-il possible de laisser la couche de persistance utiliser le modèle de données d'interface sans exposer le modèle de données de couche de persistance via l'interface?
Cette méthode est inacceptable car elle affecte l'indépendance de l'architecture à trois niveaux du serveur Spring MVC. Cependant, cette méthode n'expose pas la classe de modèle de base de données et est acceptable pour un développement itératif rapide. C'est donc une proposition moins recommandée.
/** 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);
}
}
Chacun a ses propres opinions sur la façon de tirer parti de Java, et bien sûr cet article ne donne que mon opinion personnelle. Mais pour moi, j'ai pensé qu'il était important d'exprimer mes pensées sur la base de mon expérience avec certaines des startups pour lesquelles j'ai travaillé auparavant. Parce que, à ma connaissance, si ces configurations chaotiques sont corrigées, tout le système sera meilleur.
Recommended Posts