Layered decoupling–three-tier architecture–IOC&DI

1. Case

In one case: the code for parsing XML data, obtaining the data, the code for processing the logic of the data, and the code for responding to the page are all piled together, and they are all written in the controller method.

There is a problem:

  • When we want to modify the code for operating data, we need to modify the Controller

  • When we want to improve the logic processing part of the code, we need to change the Controller

  • When we need to modify the data response code, we still need to modify the Controller

The reusability of the entire project code is relatively poor, and the code is difficult to maintain. Therefore, we needto split development (layered decoupling).

2. Three-tier architecture

2.1 Introduction

When we design and develop programs, we try to make the responsibilities of each interface, class, and method as simple as possible (single responsibility principle).

Single responsibility principle: A class or a method only does one thing and only takes care of one function.

This can make classes, interfaces, and methods less complex, more readable, more scalable, and more cost-effective for later maintenance.

The program we developed before does not satisfy the single responsibility principle. Let’s analyze the previous program:

In fact, the processing logic of our above case can be divided into three parts in terms of composition:

  • Data access: Responsible for the maintenance operations of business data, including operations such as addition, deletion, modification, and query.
  • Logic processing: Code responsible for business logic processing.
  • Request processing, response data: Responsible for receiving page requests and responding to the page with data.

According to the above three components, in our project development, the code can be divided into three layers:

  • Controller: Control layer. Receive requests sent by the front end, process the requests, and respond with data.
  • Service: Business logic layer. Handle specific business logic.
  • Dao: Data Access Object, also called persistence layer. Responsible for data access operations, including data addition, deletion, modification, and query.

Program execution process based on three-tier architecture:

  • Requests initiated by the front end are received by the Controller layer (Controller responds with data to the front end)
  • The Controller layer calls the Service layer to perform logical processing (after the Service layer completes processing, the processing results are returned to the Controller layer)
  • The Serivce layer calls the Dao layer (some data needed during logical processing must be obtained from the Dao layer)
  • The Dao layer operates the data in the file (the data obtained by Dao will be returned to the Service layer)

In this way, according to the three-layer architecture, changes to the business logic (Service layer) will not affect the Controller layer and Dao layer

2.2 Code Splitting

We use the three-tier architecture idea to transform the previous program:

  • Control layer package name: xxxx.controller
  • Business logic layer package name: xxxx.service
  • Data access layer package name: xxxx.dao

  • Control layer: Receive requests sent by the front end, process the requests, and respond to the data
@RestController
public class EmpController {<!-- -->
    //Business layer object
    private EmpService empService = new EmpServiceA();

    @RequestMapping("/listEmp")
    public Result list(){<!-- -->
        //1. Call the service layer to obtain data
        List<Emp> empList = empService.listEmp();

        //3. Response data
        return Result.success(empList);
    }
}

Business logic layer: handles specific business logic

  • Business interface
//Business logic interface (formulate business standards)
public interface EmpService {<!-- -->
    //Get employee list
    public List<Emp> listEmp();
}
  • Business implementation class
//Business logic implementation class (implemented according to business standards)
public class EmpServiceA implements EmpService {<!-- -->
    //dao layer object
    private EmpDao empDao = new EmpDaoA();

    @Override
    public List<Emp> listEmp() {<!-- -->
        //1. Call dao and get data
        List<Emp> empList = empDao.listEmp();

        //2. Convert data - gender, job
        empList.stream().forEach(emp -> {<!-- -->
            //Handle gender 1: male, 2: female
            String gender = emp.getGender();
            if("1".equals(gender)){<!-- -->
                emp.setGender("male");
            }else if("2".equals(gender)){<!-- -->
                emp.setGender("female");
            }

            //Process job - 1: Lecturer, 2: Class Teacher, 3: Employment Guidance
            String job = emp.getJob();
            if("1".equals(job)){<!-- -->
                emp.setJob("lecturer");
            }else if("2".equals(job)){<!-- -->
                emp.setJob("head teacher");
            }else if("3".equals(job)){<!-- -->
                emp.setJob("Employment Guidance");
            }
        });
        return empList;
    }
}

Data access layer: Responsible for data access operations, including data addition, deletion, modification, and query

  • Data access interface
//Data access layer interface (standard setting)
public interface EmpDao {<!-- -->
    //Get employee list data
    public List<Emp> listEmp();
}
  • Data access implementation class
//Data access implementation class
public class EmpDaoA implements EmpDao {<!-- -->
    @Override
    public List<Emp> listEmp() {<!-- -->
        //1. Load and parse emp.xml
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}


Benefits of three-tier architecture:

  1. Strong reusability
  2. Easy to maintain
  3. Take advantage of extensions

3. Layered decoupling

Decoupling: Decoupling.

3.1 Coupling issues

First, you need to understand two concepts involved in software development: cohesion and coupling.

  • Cohesion: the functional connections within each functional module in the software.

  • Coupling: Measures the degree of dependence and association between various layers/modules in the software.

Software design principles: high cohesion and low coupling.

High cohesion refers to: the closeness of the connection between the various elements in a module. The higher the connection between the various elements (statements, program segments), the higher the cohesion, that is, “high cohesion” .

Low coupling refers to: the lower the dependencies between various layers and modules in the software, the better.

The embodiment of high cohesion in the program:

  • Only the logic processing code related to employees is written in the EmpServiceA class.


  • The embodiment of coupling code in the program:

  • When changing the business class to EmpServiceB, you need to modify the code in the controller layer

    The purpose of high cohesion and low coupling is to greatly enhance the reusability and portability of program modules.

3.2 Decoupling ideas

When we were writing code before, if we needed any object, we could just create a new one. With this approach, the code between layers is coupled. When the implementation of the service layer changes, we also need to modify the code of the controller layer.

So how should we decouple it?

  • First of all, new objects cannot be used in EmpController. code show as below:
  • At this time, there is another problem. If you cannot use new, it means that there is no business layer object (an error will be reported when the program is run). What should I do?
    • Our solution is:
      • Provide a container that stores some objects (for example: EmpService object)
      • The controller program obtains an object of type EmpService from the container

If we want to achieve the above decoupling operation, we involve two core concepts in Spring:

  • Inversion of Control: Inversion Of Control, referred to as IOC. The idea of transferring object creation control from the program itself to the outside (container) is called inversion of control.

    The right to create objects is transferred from the programmer’s initiative to the container (the container creates and manages objects). This container is called: IOC container or Spring container

  • Dependency Injection: Dependency Injection, referred to as DI. The container provides the resources that the application depends on during runtime, which is called dependency injection.

    When the program needs a certain resource when it is running, the container provides it with this resource.

    Example: The EmpController program requires an EmpService object when running, and the Spring container provides and injects the EmpService object.

The objects created and managed in the IOC container are called: bean objects

4.IOC & DI

4.1 Getting Started with IOC & DI

Task: Complete the code decoupling of the Controller layer, Service layer, and Dao layer

  • Idea:
    1. Delete the code of the new object in the Controller layer and Service layer
    2. The implementation classes of the Service layer and Dao layer are handed over to the IOC container for management.
    3. Inject runtime dependent objects into Controller and Service
      • Service layer objects that inject dependencies into the Controller program
      • Dao layer objects that inject dependencies in the Service program

Step 1: Delete the code of the new object in the Controller layer and Service layer

Step 2: The implementation classes of the Service layer and Dao layer are handed over to the IOC container management

  • Using the annotation provided by Spring: @Component, you can implement the class and hand it over to the IOC container for management.

Step 3: Inject runtime dependent objects into Controller and Service

  • Using the annotation provided by Spring: @Autowired, you can realize the IOC container to automatically inject the required dependency objects when the program is running.

Complete three-layer code:

  • Controller layer:
@RestController
public class EmpController {<!-- -->

    @Autowired //At runtime, obtain this type of object from the IOC container and assign it to the variable
    private EmpService empService;

    @RequestMapping("/listEmp")
    public Result list(){<!-- -->
        //1. Call service and get data
        List<Emp> empList = empService.listEmp();

        //3. Response data
        return Result.success(empList);
    }
}
  • Service layer:
@Component //Leave the current object to the IOC container for management and become a bean of the IOC container
public class EmpServiceA implements EmpService {<!-- -->

    @Autowired //At runtime, obtain this type of object from the IOC container and assign it to the variable
    private EmpDao empDao;

    @Override
    public List<Emp> listEmp() {<!-- -->
        //1. Call dao and get data
        List<Emp> empList = empDao.listEmp();

        //2. Convert data - gender, job
        empList.stream().forEach(emp -> {<!-- -->
            //Handle gender 1: male, 2: female
            String gender = emp.getGender();
            if("1".equals(gender)){<!-- -->
                emp.setGender("male");
            }else if("2".equals(gender)){<!-- -->
                emp.setGender("female");
            }

            //Process job - 1: Lecturer, 2: Class Teacher, 3: Employment Guidance
            String job = emp.getJob();
            if("1".equals(job)){<!-- -->
                emp.setJob("lecturer");
            }else if("2".equals(job)){<!-- -->
                emp.setJob("head teacher");
            }else if("3".equals(job)){<!-- -->
                emp.setJob("Employment Guidance");
            }
        });
        return empList;
    }
}

Dao layer:

@Component //Leave the current object to the IOC container for management and become a bean of the IOC container
public class EmpDaoA implements EmpDao {<!-- -->
    @Override
    public List<Emp> listEmp() {<!-- -->
        //1. Load and parse emp.xml
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}

4.2IOC detailed explanation

4.2.1bean declaration

Earlier we mentioned IOC inversion of control, which is to transfer control of the object to Spring’s IOC container, and the IOC container creates and manages the object. The objects created by the IOC container are called bean objects.

In the previous introductory case, if you want to hand over an object to the IOC container for management, you need to add an annotation to the class: @Component

In order to better identify which layer the bean object belongs to during web application development, the Spring framework provides @Component derived annotations:

  • @Controller (marked on the control layer class)
  • @Service (marked on the business layer class)
  • @Repository (marked on the data access layer class)

Modify the entry case code:

  • Controller layer:
@RestController //@RestController = @Controller + @ResponseBody
public class EmpController {<!-- -->

    @Autowired //At runtime, obtain this type of object from the IOC container and assign it to the variable
    private EmpService empService;

    @RequestMapping("/listEmp")
    public Result list(){<!-- -->
        //1. Call service and get data
        List<Emp> empList = empService.listEmp();

        //3. Response data
        return Result.success(empList);
    }
}
  • Service layer:
@Service
public class EmpServiceA implements EmpService {<!-- -->

    @Autowired //At runtime, obtain this type of object from the IOC container and assign it to the variable
    private EmpDao empDao;

    @Override
    public List<Emp> listEmp() {<!-- -->
        //1. Call dao and get data
        List<Emp> empList = empDao.listEmp();

        //2. Convert data - gender, job
        empList.stream().forEach(emp -> {<!-- -->
            //Handle gender 1: male, 2: female
            String gender = emp.getGender();
            if("1".equals(gender)){<!-- -->
                emp.setGender("male");
            }else if("2".equals(gender)){<!-- -->
                emp.setGender("female");
            }

            //Process job - 1: Lecturer, 2: Class Teacher, 3: Employment Guidance
            String job = emp.getJob();
            if("1".equals(job)){<!-- -->
                emp.setJob("lecturer");
            }else if("2".equals(job)){<!-- -->
                emp.setJob("head teacher");
            }else if("3".equals(job)){<!-- -->
                emp.setJob("Employment Guidance");
            }
        });
        return empList;
    }
}

Dao layer:

@Repository
public class EmpDaoA implements EmpDao {<!-- -->
    @Override
    public List<Emp> listEmp() {<!-- -->
        //1. Load and parse emp.xml
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}

To hand over an object to the IOC container for management, you need to add one of the following annotations to the corresponding class:

td>

Annotation Description Position
@Controller The derived annotation of @Component is marked on the controller class
@Service The derived annotation of @Component is marked on the business class
@Repository @Component’s derived annotation is marked on the data access class (less used due to integration with mybatis)
@Component Declare the basic annotation of the bean Use this annotation when it does not fall into the above three categories


In the IOC container, each Bean has its own name, and the name of the bean can be specified through the value attribute of the annotation. If not specified, the default is to lowercase the first letter of the class name.

Precautions:

  • When declaring a bean, you can specify the bean name through the value attribute. If not specified, the first letter of the class name will be lowercase by default.
  • Beans can be declared using the above four annotations, but in springboot integrated web development, only @Controller can be used to declare controller beans.
4.2.2 Component Scan

Question: Will the beans declared using the four annotations learned previously be effective?

Answer: Not necessarily. (Reason: In order for the bean to take effect, it needs to be scanned by the component)

Next, we test whether the bean object is effective by modifying the directory structure of the project project:

After running the program, an error message appears:

Why is the bean object not found?

  • For beans declared using the four major annotations to take effect, they need to be scanned by the component scanning annotation @ComponentScan.

Although the @ComponentScan annotation is not explicitly configured, it is actually included in the boot class declaration annotation @SpringBootApplication. The default scan scope is the package where the SpringBoot startup class is located and its sub-packages.

  • Solution: Manually add the @ComponentScan annotation and specify the package to be scanned (Only for understanding, not recommended)

Recommended practices (as shown below):

  • Put the controller, service, and dao packages we defined under the sub-package of the package com.itheima where the boot class is located, so that the beans we defined will be automatically scanned.

4.2.3 Detailed explanation of DI

Dependency injection means that the IOC container provides the resources that the application depends on during runtime, and resources refer to objects.

In the starter program case, we used the @Autowired annotation to complete the dependency injection operation, and this Autowired is translated as: automatic assembly.

@Autowired annotation, the default is to automatically assemble according to type (go to the IOC container to find an object of a certain type, and then complete the injection operation)

Example of an entry-level program: When EmpController is running, it is necessary to search for an object of type EmpService in the IOC container. There happens to be an object of type EmpService in our IOC container, so we found an object of this type to complete the injection operation. .

So what happens if there are multiple bean objects of the same type in the IOC container?

  • An error will be reported when running the program

    How to solve the above problems? Spring provides the following solutions:

  • @Primary

  • @Qualifier

  • @Resource

Use @Primary annotation: When there are multiple Bean injections of the same type, add @Primary annotation to determine the default implementation.

Use the @Qualifier annotation: specify the bean object currently to be injected. In the value attribute of @Qualifier, specify the name of the injected bean.

  • The @Qualifier annotation cannot be used alone and must be used in conjunction with @Autowired

    Use the @Resource annotation: inject according to the name of the bean. Specify the name of the bean to be injected through the name attribute (the default is the class name (starting with a lowercase letter)).

Interview question: The difference between @Autowird and @Resource

  • @Autowired is an annotation provided by the spring framework, and @Resource is an annotation provided by JDK
  • @Autowired defaults to injecting by type, while @Resource injects by name