In diesem Artikel werden einige Möglichkeiten untersucht, wie diese Dienste in den verwirrenden Situationen, die beim Starten von Java-Servern auftreten, besser ausgerichtet werden können.
** Controller-Basisklasse **
/** 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) {...}
...
}
Die allgemeine Controller-basierte Klasse umfasst hauptsächlich Injektionsdienste, statische Konstanten und statische Funktionen. Alle Controller erben diese Ressourcen von der Controller-basierten Klasse und können sie direkt in der Funktion verwenden. Ich werde.
** Service Basisklasse ** Die allgemeine Service-Basisklasse lautet wie folgt.
/** 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) {...}
...
}
Übliche service-basierte Klassen sind hauptsächlich Injection Datenzugriffsobjekt (DAO), Injection Es enthält Services, Injection-Parameter, statische Konstanten, Servicefunktionen und statische Funktionen. Alle Services erben diese Ressourcen von service-basierten Klassen und können direkt in den Funktionen verwendet werden.
Zunächst Liskov-Substitutionsprinzip (LSP ) Schauen wir uns das an.
Laut LSP müssen Objekte dieser Unterklasse überall transparent verfügbar sein, wo auf die Basisklasse (Oberklasse) verwiesen wird.
Schauen wir uns als nächstes die Vorteile der Basisklasse an.
Daher können wir die folgenden Schlussfolgerungen ziehen.
Das Fazit ist, dass sowohl Controller-basierte als auch Service-basierte Klassen in verschiedene Klassen fallen. Dies sind keine wirklichen Basisklassen und müssen aufgeteilt werden.
Da die Service-Basisklasse typischer ist als die Controller-Basisklasse, wird in diesem Artikel erläutert, wie die "Basisklasse" am Beispiel der Service-Basisklasse unterteilt wird.
** Fügen Sie die Injection-Instanz in die Implementierungsklasse ein ** Fügen Sie das DAO, die Dienste, Parameter usw., die verwendet werden sollen, in die Implementierungsklasse gemäß dem Prinzip ein: "Führen Sie die Klasse nur ein, wenn sie verwendet wird, und löschen Sie sie, wenn sie nicht benötigt wird."
/** 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;
...
}
** Statische Konstanten in konstante Klasse einordnen ** Kapseln Sie statische Konstanten in die entsprechende Konstantenklasse und verwenden Sie sie bei Bedarf direkt.
/** example constant class */
public class ExampleConstants {
/** super user ID */
public static final long SUPPER_USER_ID = 0L;
...
}
** Setzen Sie die Servicefunktion in die Serviceklasse ** Kapseln Sie die Servicefunktion in die entsprechende Serviceklasse. Wenn Sie eine andere Serviceklasse verwenden möchten, können Sie eine Instanz dieser Serviceklasse einfügen und Servicefunktionen über die Instanz aufrufen.
/** 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());
}
...
}
** Statische Funktionen in Werkzeugklasse einfügen ** Kapseln Sie statische Funktionen in die entsprechenden Werkzeugklassen und verwenden Sie sie bei Bedarf direkt.
/** User Aid Class */
public class UserHelper {
/** Get the user name */
public static String getUserName(UserDO user) {...}
...
}
In der Controller-Klasse wird häufig Code wie der folgende angezeigt.
/** 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);
}
...
}
Der Compiler kann erklären, dass Sie es auf diese Weise schreiben können, da die Schnittstellenfunktion einfach ist und Sie die Schnittstellenfunktion nicht in der Servicefunktion kapseln müssen, aber in Wirklichkeit ist die Schnittstellenfunktion in der Servicefunktion gekapselt. Es besteht keine Notwendigkeit, Änderungen vorzunehmen.
In diesem speziellen Fall sieht der Code folgendermaßen aus:
/** 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);
}
}
Die Zugriffsergebnisse sind wie folgt.
curl http://localhost:8080/test/access
Sie greifen auf System (null) zu!
Möglicherweise werden Sie gefragt, warum der Parameter systemName nicht eingefügt wurde. In der Dokumentation zu Spring heißt es nun: Es gibt eine Erklärung.
Die eigentliche Verarbeitung der Annotation @Value lautet BeanPostProcessor Beachten Sie, dass dies von a2c65.11461447.0.0.7a631744QsPCA3) ausgeführt wird.
Die BeanPostProcessor-Schnittstelle wird pro Container festgelegt. Dies ist nur relevant, wenn Sie eine Containerhierarchie verwenden. Wenn Sie einen BeanPostProcessor in einem Container definieren, wird diese Arbeit nur für die Beans in diesem Container ausgeführt. In einem Container definierte Beans werden vom BeanPostProcessor in einem anderen Container nicht nachbearbeitet, selbst wenn beide Container Teil derselben Hierarchie sind.
Nach diesen Erläuterungen wird @Value über BeanPostProcessor und WebApplicationContex verarbeitet. //docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/WebApplicationContext.html?spm=a2c65.11461447.0.0.7a631744QsPCA3) und [ApplicationContext](https: / /docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html?spm=a2c65.11461447.0.0.7a631744QsPCA3) wird separat verarbeitet. Daher kann WebApplicationContex den Attributwert des übergeordneten Containers nicht verwenden.
Der Controller erfüllt die Serviceanforderungen nicht. Daher ist es unangemessen, Geschäftscode in die Controller-Klasse zu schreiben.
SpringMVC Server sind Präsentationsschicht, Geschäftsschicht und Persistenz Es verwendet eine klassische dreistufige Architektur mit Ebenen, wobei @Controller, @Service und @Repository für Klassenanmerkungen verwendet werden.
--Präsentationsschicht: Wird auch als Controller-Schicht bezeichnet. Diese Schicht ist dafür verantwortlich, Anfragen von Kunden zu empfangen und auf Kunden mit Ergebnissen von Kunden zu antworten. Auf dieser Ebene wird häufig HTTP verwendet. --Business-Schicht: Wird auch als Service-Schicht bezeichnet. Diese Schicht ist für die geschäftsbezogene Logikverarbeitung zuständig und nach Funktionen in Dienste und Jobs unterteilt. --Persistenzschicht: Wird auch als Repository-Schicht bezeichnet. Diese Ebene ist für die Datenpersistenz verantwortlich und wird von Unternehmensebenen für den Zugriff auf Caches und Datenbanken verwendet.
Daher entspricht das Schreiben von Geschäftscode in der Controller-Klasse nicht der dreistufigen Architekturspezifikation des Spring MVC-Servers.
Auf der funktionalen Seite denke ich, dass es in Ordnung ist, Persistenzschichtcode in die Serviceklasse zu schreiben. Aus diesem Grund akzeptieren viele Benutzer diese Codierungsmethode.
Hier wird als Beispiel die direkte Abfrage der Datenbankpersistenz-Middleware Hibernate erläutert.
** Erklärung des Phänomens **
/** 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;
}
}
** Empfohlene Lösung **
/** 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;
}
}
** Über Plug-Ins ** AliGenerator wurde von Alibaba [MyBatis Generator] entwickelt. ](Https://mybatis.org/generator/?spm=a2c65.11461447.0.0.7a631744QsPCA3) -basiertes Tool, das automatisch Code für die DAO-Schicht (Data Access Object) generiert. Für den von AliGenerator generierten Code müssen Sie bei der Ausführung komplexer Abfragen Abfragebedingungen in Ihrem Geschäftscode erstellen. Infolgedessen wird der Geschäftscode besonders aufgebläht.
/** 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;
}
}
Persönlich mag ich es nicht, Plugins zu verwenden, um Code für DAO-Ebenen zu generieren. Stattdessen bevorzuge ich die Verwendung des ursprünglichen MyBatis XML für die Zuordnung.
Wenn Sie sich für ein Plug-In entscheiden, sollten Sie die Vorteile des Plug-Ins nutzen und gleichzeitig die Nachteile des Plug-Ins akzeptieren.
Erläuterung
/** 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);
}
}
** Empfohlene Lösung **
/** 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) {
//Informationen zur Konvertierung
UserDO userDO = transUser(user);
// Save Redis user
userRedis.save(userDO);
// Save database user
userDAO.save(userDO);
}
}
Verkapselt die objektbezogene Operationsschnittstelle von Redis in DAO-Klassen. Es entspricht den objektorientierten Programmierprinzipien des Spring MVC-Servers und den dreistufigen Architekturspezifikationen, wodurch die Verwaltung und Wartung von Code vereinfacht wird.
/** 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);
}
}
Der Vorgängercode scheint der dreistufigen Architektur des Spring MVC-Servers zu entsprechen. Das einzige Problem besteht darin, dass das Datenbankmodell UserDO direkt der externen Schnittstelle ausgesetzt ist.
** Bestehende Probleme **
Lösung
Im Folgenden wird beschrieben, wie Sie Java-Projekte wissenschaftlicher erstellen, um effektiv zu verhindern, dass Entwickler Datenbankmodellklassen für die Schnittstelle verfügbar machen.
** Methode 1: Erstellen Sie ein Projekt mit einem gemeinsam genutzten Modell ** Platzieren Sie alle Modellklassen in einem Modellprojekt (Beispielmodell). Alle anderen Projekte (Beispiel-Repository, Beispiel-Service, Beispiel-Website usw.) basieren auf einem Beispielmodell. Das Beziehungsdiagramm ist wie folgt.
Risiko Das Präsentationsschichtprojekt (Beispiel-Webanwendung) kann jede Servicefunktion des Geschäftsschichtprojekts (Beispieldienst) aufrufen, und die DAO-Funktion des Persistenzschichtprojekts (Beispiel-Repository) kann direkt auf die Geschäftsschichten angewendet werden. Sie können es auch nennen.
** Methode 2: Erstellen Sie ein Projekt mit einem separaten Modell ** Erstellen Sie ein API-Projekt (Beispiel-API) separat, um die externe Schnittstelle und ihre Modell-VO-Klasse zu abstrahieren. Das Business-Tier-Projekt (Beispieldienst) implementiert diese Schnittstellen und stellt Dienste für das Präsentationsschichtprojekt (Beispiel-Webanwendung) bereit. Das Präsentationsschichtprojekt (Beispiel-Webanwendung) ruft nur die im API-Projekt definierten Dienstschnittstellen (Beispiel-API) auf.
Risiko Das Präsentationsschichtprojekt (Beispiel-Webanwendung) kann weiterhin die interne Servicefunktionalität des Geschäftsschichtprojekts (Beispieldienst) und die DAO-Funktionalität des Persistenzschichtprojekts (Beispielrepository) aufrufen. Um diese Situation zu vermeiden, sollte das Managementsystem zulassen, dass das Präsentationsschichtprojekt (Beispiel-Webanwendung) nur die vom API-Projekt definierten Dienstschnittstellenfunktionen (Beispiel-API) aufruft.
** Methode 3: Erstellen Sie ein serviceorientiertes Projekt ** Packen Sie das Business-Tier-Projekt (Beispiel-Service) und das Persistenz-Tier-Projekt (Beispiel-Repository) mithilfe des Dubbo-Projekts (Beispiel-Dubbo) in einen Service. Bietet Schnittstellenfunktionen, die in API-Projekten (example-api) für Business-Layer-Projekte (example-webapp) oder andere Business-Projekte (other-service) definiert sind.
Hinweis: Das Dubbo-Projekt (Beispiel-Dubbo) gibt nur die im API-Projekt definierte Dienstschnittstelle frei (Beispiel-API). Dadurch wird sichergestellt, dass das Datenbankmodell nicht verfügbar gemacht wird. Business-Layer-Projekte (z. B. Webapp) und andere Geschäftsprojekte (z. B. andere Services) hängen nur von API-Projekten (z. B. API) ab und können nur in API-Projekten definierte Service-Schnittstellen aufrufen.
Einige Benutzer haben möglicherweise die folgenden Überlegungen: Unter Berücksichtigung der Tatsache, dass das Schnittstellenmodell und das persistente Schichtmodell getrennt sind, muss die DO-Klasse des Datenabfragemodells auch im persistenten Schichtmodell definiert werden, wenn die VO-Klasse des Datenabfragemodells im Schnittstellenmodell definiert ist. Es wird____geben. Wenn das Schnittstellenmodell die VO-Klasse des Datenrückgabemodells definiert, muss das Persistenzschichtmodell auch die DO-Klasse des Datenrückgabemodells definieren. Dies ist jedoch nicht gut für eine schnelle iterative Entwicklung zu Beginn des Projekts geeignet. Darüber hinaus stellen sich auch folgende Fragen. Ist es möglich, dass die Persistenzschicht das Schnittstellendatenmodell verwendet, ohne das Persistenzschichtdatenmodell über die Schnittstelle verfügbar zu machen?
Diese Methode ist nicht akzeptabel, da sie die Unabhängigkeit der dreistufigen Architektur des Spring MVC-Servers beeinträchtigt. Diese Methode macht die Datenbankmodellklasse jedoch nicht verfügbar und ist für eine schnelle iterative Entwicklung akzeptabel. Daher ist dies ein weniger empfohlener Vorschlag.
/** 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);
}
}
Jeder hat seine eigene Meinung darüber, wie man Java nutzt, und natürlich gibt dieser Artikel nur meine persönliche Meinung wieder. Aber für mich war es wichtig, meine Gedanken aufgrund meiner Erfahrungen mit einigen Startups auszudrücken, für die ich zuvor gearbeitet habe. Denn nach meinem Verständnis ist das gesamte System besser, wenn diese chaotischen Konfigurationen behoben sind.
Recommended Posts