preface
With the rise of Spring and the improvement of its functions, it is now possible that most projects are developed using Spring (Family bucket). Spring is indeed the name of the developer’s Spring, Spring has freed the hands of programmers. After SpringBoot comes out, configuration files are greatly reduced, which further liberates the hands of programmers. However, it is precisely because of the power of Spring family products that we are used to developing for Spring. So if there is no Spring one day, will we feel empty? You may not be able to write even the most basic interfaces, especially if you are not familiar with Servlet programming. Without frameworks like Spring, we need to use native servlets to map interface paths and manage objects ourselves.
What can Spring do for us
Spring is a framework designed to address the complexity of enterprise application development. Spring is designed to simplify development.
In the Spring framework, all objects are beans, so through bean-oriented programming (BOP), combined with its core ideas of dependency injection (DI) and section-oriented (AOP) programming, Spring implements its great design philosophy of simplified development.
Inversion of Control (IOC)
IOC: Inversion of Control. The basic concept of inversion of control is that you don’t need to create an object, but you need to describe how the object is created.
To put it simply, we used to create an object in code with the new keyword. With Spring, we no longer need to create an object ourselves. Instead, we need to fetch it directly from the container and automatically inject it into the object we need: dependency injection.
In other words, the control of object creation is no longer in the hands of the programmers, and all the control is in the hands of Spring.
Dependency Injection (DI)
Dependency Injection (DI) is one of Spring’s ways of implementing inversion of control, so inversion of control is sometimes called Dependency Injection.
Aspect Oriented Programming (AOP)
AOP full name: Aspect Oriented Programming. AOP is a programming idea whose core construct is aspects (facets), which encapsulate common behavior that affects multiple classes into reusable modules, leaving the original module to focus only on its own personalized behavior.
Common scenarios for AOP programming are: Authentication, Auto Caching, Error Handling, Debugging, Logging, and Transactions.
Use Spring to complete Hello World
The most native Spring requires more configuration files, and SpringBoot omitted a lot of configuration, compared with the original Spring and simplified a lot, here we take SpringBoot as an example to complete a simple interface development.
- 1. Create a new one
maven
The project,pom
Dependencies introduced in the file (omitting a few attributes) :
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Copy the code
- Create a new one
HelloController
Class:
package com.lonely.wolf.note.springboot.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/demo")
public String helloWorld(String name){
return "Hello:"+ name; }}Copy the code
- 3. Finally create a new one
SpringBoot
Start the class:
package com.lonely.wolf.note.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.lonely.wolf.note.springboot")
class MySpringBootApplication {
public static void main(String[] args) { SpringApplication.run(MySpringBootApplication.class, args); }}Copy the code
- 4. Now enter the test path:
http://localhost:8080/hello/demo?name= Gemini lone Wolf
Test, normal output:Hello, Gemini lone Wolf
.
As you can see, it is very easy to develop a simple application using SpringBoot. You can complete a simple application without any configuration. This is because SpringBoot already has conventions (conventions are better than configuration ideas), including container Tomcat, which is integrated by default. So we don’t need any configuration files to complete a simple demo application.
Suppose there were no Spring
As we can see from the example above, it is very easy to implement a Hello World using Spring, but how would we implement such a Hello World interface without Spring?
Servlet-based development
Before there was a framework, programming was developed based on raw servlets. Let’s make a simple interface call based on native servlets.
- 1.
pom
Files introduce dependencies, and it’s important to note thatpackage
Property to be set towar
Packages, not listed here to save spacepom
Full information:
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
</dependencies>
Copy the code
- 2, in
src/main
Let’s go ahead and create a new folderwebapp/WEB-INF
And then inWEB-INF
Let’s create a new oneweb.xml
File:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Lonely Wolf Web Application</display-name>
<servlet>
<servlet-name>helloServlet</servlet-name>
<servlet-class>com.lonely.wolf.mini.spring.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>helloServlet</servlet-name>
<url-pattern>/hello/*</url-pattern>
</servlet-mapping>
</web-app>
Copy the code
Selvlet and servlet-mapping tags are defined in this file. These two tags must correspond to each other. The top tag defines the location of the servlet, while the bottom servlet-mapping file defines the path mapping. These two labels correspond via the servlet-name tag.
- Create a new one
HelloServlet
Class inheritanceHttpServlet
:
package com.lonely.wolf.mini.spring.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/** * The original Servlet interface is written, generally need to implement GET and POST methods, other methods can optionally inherit */ depending on the situation
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html; charset=utf-8");
response.getWriter().write("Hello:" + request.getParameter("name")); }}Copy the code
- 4, implement
maven
Package command to confirm successful packagingwar
Package:
- 5,
RUN-->Edit Configurations
And then click on the top left+
Create a new oneTomcat Server
If it is the first configuration, it is not configured by defaultTomcat Server
Options, you need to click on the bottomxx more items...
:
- 6. Click on the right
Deployment
, and then click in turn according to the picture below, and finally find the package above in the boxwar
Package files:
- 7. After the selection, note the following
Application Context
By defaultwar
Package name, which we need to delete for convenience, i.e. no context path, just a root path/
(Of course, the context can also be preserved, but this part should be included on each request), and then selectApply
, click on theOK
To complete the deployment:
- 8. Finally we enter the request path in the browser
http://localhost:8080/hello?name= Gemini lone Wolf
, you can get the return:Hello, Gemini lone Wolf
.
We have completed a simple Servlet based interface development, as you can see, the configuration is very troublesome, each additional Servlet needs to increase the corresponding configuration, so there are many frameworks to help simplify development, such as the original popular Struts2 framework, Of course, except for some older projects, we rarely use the Spring framework for development.
Imitation of the Spring
Spring’s source code is so large that most people stay away from it. It’s true that Spring, after all, has been iterated over the years, is feature-rich, and projects are huge. It’s not easy to understand. While Spring is difficult to understand, its core ideas are still the same as those we covered above, and it’s time to emulate the core parts of Spring and implement a super mini version of it yourself (this version doesn’t include AOP functionality).
- 1.
pom
The dependency and the top remain the same, and thenweb.xml
With the following change, all interfaces are blocked here/ *
, and then configure a parameter, this parameter is also for more image simulationSpring
:
<servlet>
<servlet-name>myDispatcherServlet</servlet-name>
<servlet-class>com.lonely.wolf.mini.spring.v1.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>defaultConfig</param-name>
<param-value>application.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>myDispatcherServlet</servlet-name>
<url-pattern>/ *</url-pattern>
</servlet-mapping>
Copy the code
- 2, in
respurces
Let’s create a new configuration fileapplication.properties
Is used to define the basic path of scanning:
basePackages=com.lonely.wolf.mini.spring
Copy the code
- 3. Create some related annotation classes:
package com.lonely.wolf.mini.spring.annotation;
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfAutowired {
String value(a) default "";
}
Copy the code
package com.lonely.wolf.mini.spring.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfController {
String value(a) default "";
}
Copy the code
package com.lonely.wolf.mini.spring.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfGetMapping {
String value(a) default "";
}
Copy the code
package com.lonely.wolf.mini.spring.annotation;
import java.lang.annotation.*;
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfRequestParam {
String value(a) default "";
}
Copy the code
package com.lonely.wolf.mini.spring.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WolfService {
String value(a) default "";
}
Copy the code
- 4. The core logic at this time is
MyDispatcherServlet
Class a:
package com.lonely.wolf.mini.spring.v1;
import com.lonely.wolf.mini.spring.annotation.*;
import com.lonely.wolf.mini.spring.v1.config.MyConfig;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
public class MyDispatcherServlet extends HttpServlet {
private MyConfig myConfig = new MyConfig();
private List<String> classNameList = new ArrayList<String>();
private Map<String,Object> iocContainerMap = new HashMap<>();
private Map<String,HandlerMapping> handlerMappingMap = new HashMap<>();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
this.doDispatch(request, response);
} catch(Exception e) { e.printStackTrace(); }}private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception{
String requestUrl = this.formatUrl(request.getRequestURI());
HandlerMapping handlerMapping = handlerMappingMap.get(requestUrl);
if (null == handlerMapping){
response.getWriter().write("404 Not Found");
return;
}
// Get the parameter type in the methodClass<? >[] paramTypeArr = handlerMapping.getMethod().getParameterTypes(); Object[] paramArr =new Object[paramTypeArr.length];
for (int i=0; i<paramTypeArr.length; i++){ Class<? > clazz = paramTypeArr[i];// Only three types of parameters are considered
if (clazz == HttpServletRequest.class){
paramArr[i] = request;
}else if (clazz == HttpServletResponse.class){
paramArr[i] = response;
} else if (clazz == String.class){
Map<Integer,String> methodParam = handlerMapping.getMethodParams();
paramArr[i] = request.getParameter(methodParam.get(i));
}else{
System.out.println("Currently unsupported parameter types"); }}// reflection calls the controller method
handlerMapping.getMethod().invoke(handlerMapping.getTarget(), paramArr);
}
private String formatUrl(String requestUrl) {
requestUrl = requestUrl.replaceAll("/ +"."/");
if (requestUrl.lastIndexOf("/") == requestUrl.length() -1){
requestUrl = requestUrl.substring(0,requestUrl.length() -1);
}
return requestUrl;
}
@Override
public void init(ServletConfig config) throws ServletException {
//1. Load the configuration file
try {
doLoadConfig(config.getInitParameter("defaultConfig"));
} catch (Exception e) {
System.out.println("Failed to load configuration file");
return;
}
//2. Scan based on the obtained scan path
doScanPacakge(myConfig.getBasePackages());
//3. Initialize the scanned classes and put them in the IOC container
doInitializedClass();
//4. Dependency injection
doDependencyInjection();
System.out.println("DispatchServlet Init End..." );
}
private void doDependencyInjection(a) {
if (iocContainerMap.size() == 0) {return;
}
// Loop classes in IOC containers
Iterator<Map.Entry<String,Object>> iterator = iocContainerMap.entrySet().iterator();
while(iterator.hasNext()){ Map.Entry<String,Object> entry = iterator.next(); Class<? > clazz = entry.getValue().getClass(); Field[] fields = clazz.getDeclaredFields();// Attribute injection
for (Field field : fields){
// If the attribute has a WolfAutowired annotation, inject the value (leaving other annotations aside for now)
if (field.isAnnotationPresent(WolfAutowired.class)){
String value = toLowerFirstLetterCase(field.getType().getSimpleName());// The value of the default bean is lowercase
if (field.getType().isAnnotationPresent(WolfService.class)){
WolfService wolfService = field.getType().getAnnotation(WolfService.class);
value = wolfService.value();
}
field.setAccessible(true);
try {
Object target = iocContainerMap.get(beanName);
if (null == target){
System.out.println(clazz.getName() + "required bean:" + beanName + ",but we not found it");
}
field.set(entry.getValue(),iocContainerMap.get(beanName));// Initialize the object and inject it later
} catch(IllegalAccessException e) { e.printStackTrace(); }}}// Initialize HanderMapping
String requestUrl = "";
// Get the request path on the Controller class
if (clazz.isAnnotationPresent(WolfController.class)){
requestUrl = clazz.getAnnotation(WolfController.class).value();
}
// Loop through the method in the class to get the path on the method
Method[] methods = clazz.getMethods();
for (Method method : methods){
// Assume WolfGetMapping is the only annotation
if(! method.isAnnotationPresent(WolfGetMapping.class)){continue;
}
WolfGetMapping wolfGetMapping = method.getDeclaredAnnotation(WolfGetMapping.class);
requestUrl = requestUrl + "/" + wolfGetMapping.value();// Compose the completed request path
// Not consider the regular match path /xx/* case, only consider the full match case
if (handlerMappingMap.containsKey(requestUrl)){
System.out.println("Repeated path");
continue;
}
Annotation[][] annotationArr = method.getParameterAnnotations();// Get an annotation of the parameters in the method
Map<Integer,String> methodParam = new HashMap<>();// Store the order and names of parameters
retryParam:
for (int i=0; i<annotationArr.length; i++){for (Annotation annotation : annotationArr[i]){
if (annotation instanceof WolfRequestParam){
WolfRequestParam wolfRequestParam = (WolfRequestParam) annotation;
methodParam.put(i,wolfRequestParam.value());// Store the location of the parameter and the parameter name defined in the annotation
continue retryParam;
}
}
}
requestUrl = this.formatUrl(requestUrl);// To prevent path mismatch caused by too many paths
HandlerMapping handlerMapping = new HandlerMapping();
handlerMapping.setRequestUrl(requestUrl);// Request path
handlerMapping.setMethod(method);// Request method
handlerMapping.setTarget(entry.getValue());// Request the controller object of the method
handlerMapping.setMethodParams(methodParam);// Request method parameter information
handlerMappingMap.put(requestUrl,handlerMapping);/ / in the hashmap}}}/** * Initializes the class and places it in the container iocContainerMap */
private void doInitializedClass(a) {
if (classNameList.isEmpty()){
return;
}
for (String className : classNameList){
if (StringUtils.isEmpty(className)){
continue;
}
Class clazz;
try {
clazz = Class.forName(className);// reflection gets objects
if (clazz.isAnnotationPresent(WolfController.class)){
String value = ((WolfController)clazz.getAnnotation(WolfController.class)).value();
// If value is specified directly, value is used; otherwise, lowercase class name is used as the key value to store the instance object of the class
iocContainerMap.put(StringUtils.isBlank(value) ? toLowerFirstLetterCase(clazz.getSimpleName()) : value,clazz.newInstance());
}else if(clazz.isAnnotationPresent(WolfService.class)){
String value = ((WolfService)clazz.getAnnotation(WolfService.class)).value();
iocContainerMap.put(StringUtils.isBlank(value) ? toLowerFirstLetterCase(clazz.getSimpleName()) : value,clazz.newInstance());
}else{
System.out.println("Regardless of other notes."); }}catch (Exception e) {
e.printStackTrace();
System.out.println("Failed to initialize class, className is"+ className); }}}/** * converts the first letter to lowercase *@param className
* @return* /
private String toLowerFirstLetterCase(String className) {
if (StringUtils.isBlank(className)){
return "";
}
String firstLetter = className.substring(0.1);
return firstLetter.toLowerCase() + className.substring(1);
}
/** * Scan all files under the package to obtain the fully qualified class name *@param basePackages
*/
private void doScanPacakge(String basePackages) {
if (StringUtils.isBlank(basePackages)){
return;
}
// Replace the package name with. /
String scanPath = "/" + basePackages.replaceAll("\ \."."/");
URL url = this.getClass().getClassLoader().getResource(scanPath);// Obtain the full path of the disk where the current package resides
File files = new File(url.getFile());// Get all files in the current path
for (File file : files.listFiles()){// Start scanning for all files in the path
if (file.isDirectory()){// If it is a folder, recurse
doScanPacakge(basePackages + "." + file.getName());
}else{// If it is a file, add it to the collection. Because this is the file path obtained through the class loader, it is actually the path where the class file is located
classNameList.add(basePackages + "." + file.getName().replace(".class"."")); }}}/** * Load the configuration file *@paramConfigPath - The path of the configuration file */
private void doLoadConfig(String configPath) {
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configPath);
Properties properties = new Properties();
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
System.out.println("Failed to load configuration file");
}
properties.forEach((k, v) -> {
try {
Field field = myConfig.getClass().getDeclaredField((String)k);
field.setAccessible(true);
field.set(myConfig,v);
} catch (Exception e) {
e.printStackTrace();
System.out.println("Failed to initialize configuration class");
return; }}); }}Copy the code
- 5, the
Servlet
Compared to the one aboveHelloServlet
One moreinit
Method, this method mainly do the following things:
(1) Initialize the configuration file and obtain the parameter information in the configuration file (corresponding method: doLoadConfig).
(2) Get the configuration file loaded in step 1, obtain the package path to be scanned, then convert the package path to the actual disk path, and start traversing all the class files under the disk path, and finally obtain the fully qualified types of all classes under the scanned path after conversion. Store it in the global variable classNameList (doScanPacakge).
(3) According to the global variable obtained in step 2, the classes in classNameList are initialized by reflection (note that only classes with specified annotations are initialized) and the corresponding relationship is stored in the iocContainerMap (the IOC container). The key value is the value attribute in the annotation. If the value attribute is empty, the class name is saved in lower case by default (doInitializedClass).
(4) This step is more critical. It is necessary to assign values to the attributes of all classes in the IOC container and map and store the request paths in the Controller. In order to ensure that the methods in the Controller can be called successfully, the parameters of the method need to be stored. Attributes are mapped only to annotated attributes, and the instance object initialized in step 3 is assigned from the IOC container. Finally, the mapping between the request path and the method in the Controller is stored in the variable handlerMappingMap. The key value is the request path, and the value is the related information of the method (corresponding method: doDependencyInjection).
- 6. Store the mapping between request paths and methods
HandlerMapping
Class to store:
package com.lonely.wolf.mini.spring.v1;
import java.lang.reflect.Method;
import java.util.Map;
// Omits getter/setter methods
public class HandlerMapping {
private String requestUrl;
private Object target;// Save the corresponding instance of the method
private Method method;// The method to save the mapping
private Map<Integer,String> methodParams;// Record method parameters
}
Copy the code
- 7, after initialization is complete, because interception
/ *
, so calling any interface will enterMyDispatcherServlet
And will eventually execute methodsdoDispatch
This method takes the path of the request and then the global variablehandlerMappingMap
Matches, and returns if no match is found404
, the necessary parameters are taken out and assigned, and finally called by reflectionController
Related methods in. - 8. Create a new one
HelloController
和HelloService
To test:
package com.lonely.wolf.mini.spring.controller;
import com.lonely.wolf.mini.spring.annotation.WolfAutowired;
import com.lonely.wolf.mini.spring.annotation.WolfController;
import com.lonely.wolf.mini.spring.annotation.WolfGetMapping;
import com.lonely.wolf.mini.spring.annotation.WolfRequestParam;
import com.lonely.wolf.mini.spring.service.HelloService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WolfController
public class HelloController {
@WolfAutowired
private HelloService helloService;
@WolfGetMapping("/hello")
public void query(HttpServletRequest request,HttpServletResponse response, @WolfRequestParam("name") String name) throws IOException {
response.setContentType("text/html; charset=utf-8");
response.getWriter().write("Hello:"+ name); }}Copy the code
package com.lonely.wolf.mini.spring.service;
import com.lonely.wolf.mini.spring.annotation.WolfService;
@WolfService(value = "hello_service")// To demonstrate whether the value attribute can be correctly fetched
public class HelloService {}Copy the code
- 9. Enter the test path:
http://localhost:8080////hello?name= Gemini lone Wolf
, the test found that the output can be normal:Hello, Gemini lone Wolf
.
The above example is just a simple demonstration of how to do a simple application development without any framework. Many of the details in this example are not addressed, just to experience the core ideas of Spring and understand what Spring actually helps us to do. In fact, Spring can help us do much more than this example. Spring architecture is large, elegant, and has been iterated and optimized for many years. Is a framework worth studying.
conclusion
This article starts with the introduction of the core functions of Spring, from how to use Spring to complete an application development, to how to develop based on servlets without Spring, and finally through a simple example to experience the core ideas of Spring.