[project practice] dependency injection is well used, and design patterns are easy to follow

RudeCrab 2021-01-21 09:45:06
project practice dependency injection used


 Head picture .png

Project driven learning , Test true knowledge with practice

Preface

Design patterns are an inextricable part of our programming path , Good use of design patterns can make the code have good maintainability 、 Readability and extensibility , It seems to be “ grace ” The pronoun of , It can also be seen in various frameworks and libraries .

It's because it has all kinds of advantages , So a lot of people always want to use a design pattern in a project during development , However, it is often used in an awkward way . Part of the reason is that the business requirements don't quite match the design patterns used , And part of the reason is that Web In the project, our objects are all handed over to Spring Framework of the Ioc Container to manage , Many design patterns cannot be applied directly . So in real project development , We need to make a flexible adaptation to the design pattern , Make it work with the framework , Play a real advantage in the actual development .

When the project is introduced IoC After container , We usually use objects through dependency injection , This is the key to combining design patterns with frameworks ! This article will explain how to complete the following three design patterns through dependency injection :

  • The singleton pattern
  • The chain of responsibility model
  • The strategy pattern

from the shallower to the deeper , Let you understand several design patterns and master some magical functions of dependency injection at the same time .

All the code in this article is in Github On , Clone it and run it to see the effect .

actual combat

The singleton pattern

Singleton should be the first design pattern that many people come into contact with , Compared with other design patterns, the concept of singleton is very simple , namely In a process , A class has only one instance object from beginning to end . But no matter how simple the concept is , It still needs a little bit of coding ,R Previous post There are four ways to write Huizi , Do you know that there are five ways to write a single example There is a detailed explanation , This design pattern is only introduced here , Let's take a look at how to use this mode in actual development .

Leave it to Spring IoC The object of container management is called Bean, Every Bean They all have their scope (scope), This scope can be understood as Spring control Bean The way of life cycle . Creation and destruction are essential nodes in the life cycle , The focus of the singleton pattern is naturally the creation of objects . and Spring The process of creating objects is imperceptible to us , That is, we just need to configure Bean, Then you can use objects through dependency injection :

@Service // and @Component Function as , Declare the class as Bean Leave it to the container
public class UserServiceImpl implements UserService{
}
@Controller
public class UserController {
@Autowired // Dependency injection
private UserService userService;
}

How can we control the creation of this object ?

Actually ,Bean The default scope is singleton , We don't need to write a single example . To verify Bean Whether it's a singleton is very simple , We get it all over the program Bean Then print it hashCode We can see if it is the same object , For example, two different classes are injected with UserService

@Controller
public class UserController {
@Autowired
private UserService userService;
public void test() {
System.out.println(userService.hashCode());
}
}
@Controller
public class OtherController {
@Autowired
private UserService userService;
public void test() {
System.out.println(userService.hashCode());
}
}

The printout will be two identical hashCode.

Why? Spring By default, it will be instantiated in the form of singleton Bean Well ? This is naturally because a single instance can save resources , There are many classes that don't need to instantiate multiple objects .

If we just want to get every time Bean Create an object every time ? We can declare Bean When it's time to add @Scope Annotation to configure its scope :

@Service
@Scope("prototype")
public class UserServiceImpl implements UserService{
}

So every time you get Bean An instance will be created when it is created .

Bean The scope of is as follows , We can configure it on demand , In most cases, we just use the default singleton :

name explain
singleton Default scope . Every IoC The container creates only one object instance .
prototype Defined as multiple object instances .
request Limit to HTTP In the life cycle of the request . Every HTTP Client requests have their own object instances .
session Limit to HttpSession Within the life cycle of .
application Limit to ServletContext Within the life cycle of .
websocket Limit to WebSocket Within the life cycle of .

One extra thing to note here ,Bean A singleton of is not a singleton in the traditional sense , Because its scope can only be guaranteed in IoC Only one object instance is guaranteed in the container , But there is no guarantee that there is only one object instance in a process . in other words , If you don't pass Spring Provide a way to get Bean, Instead, you create an object of your own , At this point, the program will have multiple objects :

public void test() {
// own new An object
System.out.println(new UserServiceImpl().hashCode());
}

That's where the flexibility is ,Spring It can be said that we cover every corner in our daily development , As long as you don't deliberately bypass Spring, So promise IoC A singleton in a container is basically equivalent to a singleton in the whole program .

The chain of responsibility model

After explaining the simple concept of single example , Let's look at the chain of responsibility model .

Mode explanation

The pattern is not complicated : A request can be processed by multiple objects , These objects are connected into a chain and requests are passed along the chain , Until an object processes it . The advantage of this pattern is to decouple the requester from the receiver , You can add and delete processing logic dynamically , It gives a very high degree of flexibility to deal with the responsibilities of the object . The filters we use in our development Filter And interceptors Interceptor It's using the chain of responsibility model .

Just looking at the introduction will only make people confused , Let's look directly at how the model works .

Take leave approval at work for example , When we initiate a leave application , There are usually multiple reviewers , Each of the examiners represents a responsibility node , They all have their own approval logic . Let's assume that there are the following examiners :

group leader Leader: You can only apply for leave of no more than three days ;

The manager Manger: You can only apply for leave of no more than seven days ;

Boss Boss: Can approve any number of days .

Let's define a leave approval object first :

public class Request {
/**
* The name of the requester
*/
private String name;
/**
* Days off . For the sake of demonstration, it's just a whole day , No more hours
*/
private Integer day;
public Request(String name, Integer day) {
this.name = name;
this.day = day;
}
// Omit get、set Method
}

According to the traditional way of writing, the receiver receives the object and processes it by judging the condition :

public class Handler {
public void process(Request request) {
System.out.println("---");
// Leader The examination and approval
if (request.getDay() <= 3) {
System.out.println(String.format("Leader Approved 【%s】 Of 【%d】 I'd like to ask for a day off ", request.getName(), request.getDay()));
return;
}
System.out.println(String.format("Leader Can't approve 【%s】 Of 【%d】 I'd like to ask for a day off ", request.getName(), request.getDay()));
// Manger The examination and approval
if (request.getDay() <= 7) {
System.out.println(String.format("Manger Approved 【%s】 Of 【%d】 I'd like to ask for a day off ", request.getName(), request.getDay()));
return;
}
System.out.println(String.format("Manger Can't approve 【%s】 Of 【%d】 I'd like to ask for a day off ", request.getName(), request.getDay()));
// Boss The examination and approval
System.out.println(String.format("Boss Approved 【%s】 Of 【%d】 I'd like to ask for a day off ", request.getName(), request.getDay()));
System.out.println("---");
}
}

Simulate the approval process on the client side :

public class App {
public static void main( String[] args ) {
Handler handler = new Handler();
handler.process(new Request(" Zhang San ", 2));
handler.process(new Request(" Li Si ", 5));
handler.process(new Request(" Wang Wu ", 14));
}
}

The results are as follows :

---
Leader Approved 【 Zhang San 】 Of 【2】 I'd like to ask for a day off
---
Leader Can't approve 【 Li Si 】 Of 【5】 I'd like to ask for a day off
Manger Approved 【 Li Si 】 Of 【5】 I'd like to ask for a day off
---
Leader Can't approve 【 Wang Wu 】 Of 【14】 I'd like to ask for a day off
Manger Can't approve 【 Wang Wu 】 Of 【14】 I'd like to ask for a day off
Boss Approved 【 Wang Wu 】 Of 【14】 I'd like to ask for a day off
---

It's not hard to see. Handler The code in the class is full of bad smell ! The coupling degree between each responsible node is very high , If you want to add or delete a node , It's going to change this big piece of code , Is not very flexible . Moreover, the approval logic demonstrated here is just a printed sentence , In real business, processing logic is much more complicated than that , If you want to change it, it's a disaster .

At this time, our responsibility chain model will come into use ! We encapsulate each responsible node as a separate object , And then combine these objects into a chain , And through the unified entrance one by one processing .

First , We want to abstract the interface of the responsible node , All nodes implement the interface :

public interface Handler {
/**
* The return value is true, It means release , Leave it to the next node
* The return value is false, It means no release
*/
boolean process(Request request);
}

With Leader Node as an example , Implement the interface :

public class LeaderHandler implements Handler{
@Override
public boolean process(Request request) {
if (request.getDay() <= 3) {
System.out.println(String.format("Leader Approved 【%s】 Of 【%d】 I'd like to ask for a day off ", request.getName(), request.getDay()));
// Finished processing , No release
return false;
}
System.out.println(String.format("Leader Can't approve 【%s】 Of 【%d】 I'd like to ask for a day off ", request.getName(), request.getDay()));
// release
return true;
}
}

And then define a special one to deal with these Handler The chain class of :

public class HandlerChain {
// Store all Handler
private List<Handler> handlers = new LinkedList<>();
// Provide an increase to the outside Handler Entrance
public void addHandler(Handler handler) {
this.handlers.add(handler);
}
public void process(Request request) {
// In turn, calls Handler
for (Handler handler : handlers) {
// If returned to false, Abort call
if (!handler.process(request)) {
break;
}
}
}
}

Now let's take a look at how to implement the approval process by using the chain of responsibility :

public class App {
public static void main( String[] args ) {
// Build a chain of responsibility
HandlerChain chain = new HandlerChain();
chain.addHandler(new LeaderHandler());
chain.addHandler(new ManagerHandler());
chain.addHandler(new BossHandler());
// Perform multiple processes
chain.process(new Request(" Zhang San ", 2));
chain.process(new Request(" Li Si ", 5));
chain.process(new Request(" Wang Wu ", 14));
}
}

The print is the same as before .

The benefits are obvious , We can easily add and delete responsibility nodes , Modifying the logic of a responsible node will not affect other nodes , Each node only needs to focus on its own logic . And the responsibility chain executes nodes in a fixed order , Add each object in the order you want, and you can easily arrange the order .

In addition, there are many variants of the chain of responsibility , Such as Servlet Of Filter You need to hold the chain reference when you execute the next node :

public class MyFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
if (...) {
// Release by chain reference
chain.doFilter(req, resp);
} else {
// If not called chain The method of stop is to stop passing down
...
}
}
}

In addition to the way of transmission, each chain of responsibility is different , The overall link logic can also be different .

What we just demonstrated is that the request is processed by a certain node , As long as one is dealt with , You don't have to deal with it later . Some responsibility chains are not designed to find a node to handle , Instead, each node does something , It's like an assembly line .

Like the approval process just now , We can change the logic to a leave application, which requires the approval of all the examiners ,Leader I'll transfer it to Manger The examination and approval ,Manger I'll transfer it to Boss The examination and approval , Only Boss Finally agreed to take effect .

There are many forms , The core concept is to chain request objects , Without breaking away from this point, it can be regarded as the mode of responsibility chain , There's no need to stick to the definition .

Matching frame

In the responsibility chain mode , We all create our own responsibility node objects , Then add it to the chain of responsibility . In actual development, there will be a problem , If we inject other dependencies into our responsible node Bean, If you create an object manually, it means that the object is not handed over to Spring management , Those properties will not be injected by dependency :

public class LeaderHandler implements Handler{
@Autowired // Manually create LeaderHandler The property is not injected
private UserService userService;
}

At this point, we have to give each node object to Spring To manage , And then through Spring To get these object instances , Then put these object instances into the chain of responsibility . In fact, most people have been exposed to this way ,Spring MVC Interceptor Interceptor That's how it's used :

@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// obtain Bean, Add to the chain of responsibility ( Watch out! , Here is the method called to get the object , instead of new Object out )
registry.addInterceptor(loginInterceptor());
registry.addInterceptor(authInterceptor());
}
// adopt @Bean The annotation gives the custom interceptor to Spring management
@Bean
public LoginInterceptor loginInterceptor() {return new LoginInterceptor();}
@Bean
public AuthInterceptor authInterceptor() {return new AuthInterceptor();}
}

InterceptorRegistry It's like a chain , The object is created by Spring MVC Pass it on to us , So let's add interceptors , follow-up Spring MVC Will call the chain of responsibility , We don't need to worry about .

The chain of responsibility defined by someone else's framework is called by the framework , How can we call our custom responsibility chain ? Here's a simpler way , That's it take Bean Dependency injection into the collection

In our daily development, we use dependency injection to get a single Bean, This is because the interface or parent class we declare usually needs only one implementation class to meet the business requirements . And what we just customized Handler There will be multiple implementation classes under the interface , At this point, we can inject more than one at a time Bean! Let's transform the previous code now .

First , Each one Handler Implementation class plus @Service annotation , Declare it as Bean:

@Service
public class LeaderHandler implements Handler{
...
}
@Service
public class ManagerHandler implements Handler{
...
}
@Service
public class BossHandler implements Handler{
...
}

Then let's transform our chain class , Declare it as a Bean, And then add... Directly to the member variable @Autowired annotation . Now that it's all implemented through dependency injection , Then there is no need to add a new responsibility node manually , So we will remove the previous method of adding nodes :

@Service
public class HandlerChain {
@Autowired
private List<Handler> handlers;
public void process(Request request) {
// In turn, calls Handler
for (Handler handler : handlers) {
// If returned to false, Abort call
if (!handler.process(request)) {
break;
}
}
}
}

you 're right , Dependency injection is very powerful , More than just being able to inject a single object , You can also inject multiple ! So it's very convenient , We just need to achieve Handler Interface , Declare the implementation class as Bean, It's automatically injected into the chain of responsibility , We don't even have to add it manually . It's also very easy to implement the chain of responsibility , Just get HandlerChain And then call it :

@Controller
public class UserController {
@Autowired
private HandlerChain chain;
public void process() {
chain.process(new Request(" Zhang San ", 2));
chain.process(new Request(" Li Si ", 5));
chain.process(new Request(" Wang Wu ", 14));
}
}

The implementation effect is as follows :

---
Boss Approved 【 Zhang San 】 Of 【2】 I'd like to ask for a day off
---
Boss Approved 【 Li Si 】 Of 【5】 I'd like to ask for a day off
---
Boss Approved 【 Wang Wu 】 Of 【14】 I'd like to ask for a day off

Why , All of them are Boss Approved , Why didn't the first two nodes work ? Because we haven't configured yet Bean The order of injection into the collection , We need to add @Order Note to control Bean Assembly sequence of , The smaller the number, the higher it goes :

@Order(1)
@Service
public class LeaderHandler implements Handler{
...
}
@Order(2)
@Service
public class ManagerHandler implements Handler{
...
}
@Order(3)
@Service
public class BossHandler implements Handler{
...
}

In this way, our custom responsibility chain model is perfectly integrated into Spring It's in !

The strategy pattern

strike while the iron is hot , Now let's talk about a new model !

Mode explanation

We often meet such requirements in development : Different operations need to be performed according to different situations . For example, the most common postage for our shopping , Different regions 、 The postage will be different for different products . Suppose the demand is like this :

The parcel area : Not more than 10KG Our goods are free of post ,10KG above 8 element ;

The neighborhood : Not more than 10KG Of goods 8 element ,10KG above 16 element ;

Remote area : Not more than 10KG Of goods 16 element ,10KG above 15KG following 24 element , 15KG above 32 element .

The way we calculate the postage is like this :

// For the convenience of demonstration , The weight and amount are simply set as integers
public long calPostage(String zone, int weight) {
// The parcel area
if ("freeZone".equals(zone)) {
if (weight <= 10) {
return 0;
} else {
return 8;
}
}
// Close quarters
if ("nearZone".equals(zone)) {
if (weight <= 10) {
return 8;
} else {
return 16;
}
}
// Remote area
if ("farZone".equals(zone)) {
if (weight <= 10) {
return 16;
} else if (weight <= 15) {
return 24;
} else {
return 32;
}
}
return 0;
}

Such a little bit of postage rules write such a long code , If the rules were a little more complicated, it would be much longer . And if the rules change , We need to mend this big piece of code , Over time, the code becomes very difficult to maintain .

The first way we think of optimization is to encapsulate each piece of computation into a method :

public long calPostage(String zone, int weight) {
// The parcel area
if ("freeZone".equals(zone)) {
return calFreeZonePostage(weight);
}
// Close quarters
if ("nearZone".equals(zone)) {
return calNearZonePostage(weight);
}
// Remote area
if ("farZone".equals(zone)) {
return calFarZonePostage(weight);
}
return 0;
}

It's really good , Most of the time, it can meet the demand , But it's still not flexible enough .

Because these rules are written in our methods , If the caller wants to use his own rules , Or change the rules a lot ? We can't always modify the code we have written . You know, postage calculation is only a small part of order price calculation , Of course, we can write several rules to provide services , But you also have to allow people to customize the rules . here , We should highly abstract the operation of postage calculation as an interface , Different classes are implemented with different calculation rules . Different rules represent different strategies , This is our strategy model ! Let's take a look at the specific writing :

First , Package a postage calculation interface :

public interface PostageStrategy {
long calPostage(int weight);
}

then , We encapsulate those regional rules into different implementation classes , Take the example of the parcel post area :

public class FreeZonePostageStrategy implements PostageStrategy{
@Override
public long calPostage(int weight) {
if (weight <= 10) {
return 0;
} else {
return 8;
}
}
}

Last , To apply the policy, we also need a special class :

public class PostageContext {
// Hold a strategy
private PostageStrategy postageStrategy = new FreeZonePostageStrategy();
// Allow callers to set new policies
public void setPostageStrategy(PostageStrategy postageStrategy) {
this.postageStrategy = postageStrategy;
}
// For the caller to execute the policy
public long calPostage(int weight) {
return postageStrategy.calPostage(weight);
}
}

such , Callers can use our existing policies , You can also easily modify or customize policies :

public long calPrice(User user, int weight) {
PostageContext postageContext = new PostageContext();
// Custom policy
if ("RudeCrab".equals(user.getName())) {
// VIP Customer ,20KG All the following items are included in the package ,20KG Only 5 element
postageContext.setPostageStrategy(w -> w <= 20 ? 0 : 5);
return postageContext.calPostage(weight);
}
// Parcel regional strategy
if ("freeZone".equals(user.getZone())) {
postageContext.setPostageStrategy(new FreeZonePostageStrategy());
return postageContext.calPostage(weight);
}
// Neighborhood strategy
if ("nearZone".equals(user.getZone())) {
postageContext.setPostageStrategy(new NearZonePostageStrategy());
return postageContext.calPostage(weight);
}
...
return 0;
}

You can see , Simple logic is used directly Lambda The expression completes the custom policy , If the logic is complex, you can also create a new implementation class directly .

That's the beauty of the strategic model , Allows callers to use different policies to get different results , For maximum flexibility !

Despite the benefits , But the disadvantages of the strategy model are also obvious :

  • There may be too many policy classes , There are as many classes as there are rules
  • The policy pattern just distributes the logic to different implementation classes , Caller's if、else None of them has been reduced .
  • The caller needs to know all the policy classes to use the existing logic .

Most of the disadvantages can be solved with factory mode or reflection , But that adds to the complexity of the system . Is there a solution that can make up for the shortcomings without being complicated , Of course there are , That's what I'm going to talk about next . In the strategic mode Spring Frame at the same time , It can also make up for the shortcomings of the model itself !

Matching frame

Through the chain of responsibility model, we can find that , In fact, the so-called cooperation framework is to give our objects to Spring To manage , And then through Spring call Bean that will do . In strategic mode , Each of our policy classes is instantiated manually , The first step we need to take is to declare these policy classes as Bean:

@Service("freeZone") // The value in the annotation represents Bean The name of , Why do we do this here , I'll explain later
public class FreeZonePostageStrategy implements PostageStrategy{
...
}
@Service("nearZone")
public class NearZonePostageStrategy implements PostageStrategy{
...
}
@Service("farZone")
public class FarZonePostageStrategy implements PostageStrategy{
...
}

And then we're going to go through Spring Get these Bean. One might naturally think of , Let's inject all of these implementation classes into a collection , And then use . It really can , But it's too much trouble . Dependency injection is very powerful , Not only can you Bean Inject into the collection , It can also be injected into Map in

Let's see the specific usage :

@Controller
public class OrderController {
@Autowired
private Map<String, PostageStrategy> map;
public void calPrice(User user, int weight) {
map.get(user.getZone()).calPostage(weight);
}
}

Tell me loud , Clear or not ! Jane is not concise ! Excellent and not elegant !

Dependency injection can make Bean Injection into Map in , among Key by Bean The name of ,Value by Bean object , That's what I'm going to do in front of @Service The reason for setting the value on the annotation , Only in this way can the caller pass through Map Of get Method to get Bean, Then use the Bean object .

Our previous PostageContext Class can be dispensed with , When do you want to call a policy , Inject... Directly at the call Map that will do .

In this way , We don't just integrate the strategic model into Spring In the frame , It's a perfect solution if、else Too much and so on ! We want to add strategies , Just create a new implementation class and declare it as Bean That's it , The original caller does not need to change a line of code to take effect .

Tips: : If an interface or parent class has more than one implementation class , But I just want to rely on injecting a single object , have access to @Qualifier("Bean The name of ") Annotation to get the specified Bean.

summary

This paper introduces three design patterns , And the design patterns in Spring How to use it under the framework ! The dependency injection methods corresponding to these three design patterns are as follows :

  • The singleton pattern : Dependency injection into a single object
  • The chain of responsibility model : Dependency injection collection
  • The strategy pattern : Dependency injection Map

Combine design patterns with Spring The key point of frame coordination is , How to hand over objects in a pattern to Spring management . This is the core of this article , This point is clear , Each design pattern can be used flexibly .

That's the end of the explanation , All the code in this article is in Github, Clone it and run it . If it helps you , You can like to pay attention to , I will continue to update more original 【 Project practice 】 Of !

版权声明
本文为[RudeCrab]所创,转载请带上原文链接,感谢
https://javamana.com/2021/01/20210121094228355O.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云