Dependency injection learning notes

Ioc overview

Ioc(Inversion Of Control) is translated as control inversion

First, make it clear that the control without inversion is "active control"

  1. Active control: all the codes we wrote before are active control, which means that we can instantiate whatever objects are needed during the running of the program
  2. Inversion of control: the code written with the idea of inversion of control is to save all the objects that will be used by the program to the external container and obtain them from the container when necessary

Create a Spring project

Create a Spring project

Use Idea to create Maven project, and then add Spring dependency

The POM of the current project after modification The XML file is as follows

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SpringTest</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <!-- set up JDK Version 1.8 -->
        <maven.compiler.target>1.8</maven.compiler.target>
        <maven.compiler.source>1.8</maven.compiler.source>
        <!-- Set code to UTF-8 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    </properties>
    <dependencies>
        <!-- Spring Context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>
</project>

Let's start creating a HelloWorld program that controls inversion

The first program to control inversion

Let's first create a class that contains student attributes

public class Stu {
    
    private Integer id;
    private String name;
    
    // get\set\toString omitted
}

Next, we will learn how to save an object in the Spring container to achieve control inversion

Create a new class Config

// This annotation indicates that the current class is the configuration class used to configure Spring
@Configuration
public class Config {

    // In this configuration class, you can save the object to the Spring container
    // This @ Bean annotation means to save the return value of the following method to the Spring container
    // The name of this object in the Spring container is the name of this method
    @Bean
    public Stu stu(){
        Stu stu=new Stu();
        stu.setId(1);
        stu.setName("Sun WuKong");
        return stu;
    }
}

Use the objects in the Spring container in the main method

public class DemoTest {

    public static void main(String[] args) {
        // Initialize Spring container
        AnnotationConfigApplicationContext acac=
          new AnnotationConfigApplicationContext(Config.class);
        // Get the object saved in the Spring container
        //  getBean([object name \ id], [object type. class])
        Stu stu=acac.getBean("stu",Stu.class);
        System.out.println(stu);

    }

}

Using @ Bean annotation to save objects to the Spring container is one of the main methods provided by the Spring framework

JUnit test run Spring

If you use the main method to test the program, you should create a new class to write the main method for each test

There will be many classes, and every time the main method instantiates and closes the acac object, the code is redundant

We can improve the above shortcomings by using JUnit for unit testing

First, add JUnit4 dependencies

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
</dependency>

Create the demo package in the java folder of test

Create TestDemo class in the package, and the test code is as follows

public class TestDemo {

    // Multiple test methods that can be run can be written in a test class

    @Test
    public void run1(){
        System.out.println("run1");
    }

    @Test
    public void run2(){
        System.out.println("run2");
    }

    // Declare the ACAC object as an attribute of the test class, which can be used in all methods
    AnnotationConfigApplicationContext acac;

    // @The method marked by the Before annotation will run automatically Before the current Test class @ Test method runs
    @Before
    public void init(){
        acac=new AnnotationConfigApplicationContext(Config.class);
    }

    // @The method marked by the After annotation will run automatically After the current Test class @ Test method runs
    @After
    public void destroy(){
        acac.close();
    }

    @Test
    public void getStu(){
        Stu stu=acac.getBean("stu",Stu.class);
        System.out.println(stu);
    }
    
}

The component scans and saves the object to the Spring container

Above, we use the @ Bean annotation method to save the object to the Spring container

Let's learn another method to save objects to the Spring container

Create a new package san

Create a new class GuanYu in the package

The code is as follows

// @Component is one of the key annotations that the component scan saves to the Spring container
// Component means component. When the current class is scanned, an object will be instantiated automatically,
// Save to the Spring container. The ID (name) of the object is the lowercase word guanYu of the current class name

@Component
public class GuanYu {
    private String name="Guan Yu";
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void fight(){
        System.out.println(name+"In battle!");
    }

}

Component scanning is also inseparable from Config class

The code for creating ConfigSan class is as follows

@Configuration
// Annotation of componentscan
// This annotation needs to specify a package. When instantiating the ACAC object and parsing the configuration file, configsan Class hour
// All classes in the specified package will be scanned, as long as there are annotation marked @ Component in these classes,
// The instantiated object will be saved to the Spring container
@ComponentScan("san")
public class ConfigSan {
}

The test obtains the object saved to the Spring container in the scanning mode of the component

public class TestSan {

    AnnotationConfigApplicationContext acac;

    @Before
    public void init(){
        acac=new AnnotationConfigApplicationContext(
                                         ConfigSan.class);
    }
    @After
    public void destroy(){
        acac.close();
    }
    @Test
    public void getGuan(){
        GuanYu guanYu=acac.getBean("guanYu",GuanYu.class);
        guanYu.fight();
    }
}

Difference between component scanning and @ Bean

1. Simpler component scanning configuration

2.@Bean can assign values to object attributes and then save them to the Spring container

3.@Bean can configure multiple different objects to be saved to the Spring container (for example, w1 and W2 of Worker)

Component scanning considerations

1. The special class name causes the special name to be saved to the Spring container

Under normal circumstances

​ Stu -> stu

​ Worker -> worker

​ GuanYu -> guanYu

This rule is different when the class name is special

Class name that starts with two consecutive uppercase letters

​ VIPStu -> VIPStu

The object name saved to the Spring container is the class name

2. The spring framework supports multiple annotations to represent the meaning of components

Spring provides the following annotation, which has the same function as @ Component

  • @Controller is used to mark control layer components
  • @Service is used to mark business logic layer components
  • @Repository is used to tag data access layer components

The above three annotations can also indicate that the current class is instantiated and saved to the Spring container

Just because the meanings of words are different, it is convenient to know its role and increase the readability of the code

3. Custom component ID (name)

@Component("erye")

public class GuanYu {
    //.....   Code slightly
}

If the program needs to scan the current component and save the custom name of the object

After the annotation from the current @ Spring object () or the function from the @ Spring container () is saved, it needs to use the same name

Scope of bean (object)

The Bean here refers to the object in the Spring container

Scope English: scope

Scope is used to set the management policy of objects in the current Spring container

There are two main management strategies

1. Singleton (single case)

2. Prototype

Singleton strategy

The singleton policy is the default scope policy of the Spring framework

The Stu\Work\GuanYu class saved to the Spring container in the above chapter is a singleton

Let's test in the existing test class to verify the characteristics of "Singleton"

First, since it is a "Singleton", it means that the object obtained from the test class is the same object no matter how many times it is obtained

@Test
public void single(){
    // Code for testing singletons
    Stu s1=acac.getBean("stu",Stu.class);
    Stu s2=acac.getBean("stu",Stu.class);

    System.out.println(s1==s2);

    s1.setName("Qi Tian Da Sheng");
    System.out.println(s2);

}

In the code, their judgment and other codes are written first, and the output result is true

Indicates that they are the same reference

The following s1 object modifies the value of the name attribute

Output S2. The name attribute of S2 object changes with the modification of s1, which proves that they are the same object again

Advantages of single case

1. Save memory

2. It is convenient to manage, modify the current object and affect the properties of all objects

3. When multiple objects need to be obtained, single instance cannot be used, otherwise the purpose cannot be achieved

Prototype strategy

Let's introduce the use of prototype strategy

We can write it under @ Bean annotation or @ Component annotation

@Scope("prototype")

Finish the above configuration and run the following test code

@Test
public void prototype(){
    // Test prototype scope
    // Every time you get an object from the Spring container, you will get a newly instantiated object
    Stu s3=acac.getBean("stu",Stu.class);
    Stu s4=acac.getBean("stu",Stu.class);

    System.out.println(s3==s4);

    s3.setName("Monkey King");
    System.out.println(s3);
    System.out.println(s4);
}

You will find that s3==s4 is false

The property of s3 is modified, which only affects the s3 object itself, but not s4. These phenomena are completely different from the above single example

In other words, under the scope of the prototype, each time you get an object from the Spring container, you will get a newly instantiated object

Prototype strategy advantage

1. When the program needs to use a template to generate multiple objects, the prototype mode is used

2. However, the prototype strategy will be a waste of memory space

It is recommended to use singletons when it is not necessary, and prototypes when multiple objects of one type are really needed

lazy initialization

Lazy initialization is also called "lazy loading"

What is lazy initialization

The so-called lazy initialization means that when the program needs to use this object, the Spring container instantiates it

If the program does not need it during running, Spring will not instantiate it

Why do I need lazy initialization

In the process of program running, some objects may or may not be used

For this kind of object, we set it to lazy initialization, which may reduce the memory occupation

The default setting in the Spring container is not lazy, that is, by default, all singleton scope objects will be instantiated before the program runs, unless lazy initialization is set, that is, lazy initialization is for singleton scope objects

Lazy initialization is not without disadvantages. If all objects in the program are set to lazy initialization, that is, all objects need to be instantiated and used in the running process, it will significantly slow down the running speed

When to instantiate an object by default

The configuration class Config of Stu is restored to the original singleton scope

A parameterless constructor is added to the Stu class

public Stu(){
    System.out.println("Stu instantiation ");
}

Added output to the @ Before method of the TestDemo test class

@Before
public void init(){
    acac=new AnnotationConfigApplicationContext(Config.class);
    System.out.println("init Method execution completed");
}

Then run an empty test method

@Test
public void lazy(){

}

Console output is

Stu instantiation 
init Method execution completed

It is proved that the Stu object is instantiated when the acac object is instantiated

If you want to set Stu as an inert initialization object, add it in the @ Bean or @ Component annotation

@Lazy

annotation

Then run the lazy method in the empty test class, and the console output is as follows

init Method execution completed

Prove that the Stu object was not instantiated when instantiating acac this time

The Stu object is instantiated only when it is actually used in the test class

@Test
public void lazy(){

    Stu ss=acac.getBean("stu", Stu.class);
    System.out.println(ss);
    //Stu sss=acac.getBean("stu",Stu.class);
}

Run the above code and enter as follows

init Method execution completed
Stu instantiation 
Stu{id=1, name='Sun WuKong'}

Even if you uncomment Stu sss, the Stu object will not be instantiated again!

DI dependency injection

What is dependency injection

Dependency injection

To clarify dependency injection, we first understand what dependency is

Dependency in programs means that methods in class A need to use type B objects to run. We say that class A depends on class B

They are dependent

Guan Yu needs to rely on Qinglong Yanyue Dao to fight

How should such dependencies be represented in the program?

The code for creating DragonBlade class in san package is as follows

@Component
public class DragonBlade {

    private String name="falchion";

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

Modify the GuanYu class of san package

@Component
public class GuanYu {

    private String name="Guan Yu";

    private DragonBlade dragonBlade;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public DragonBlade getDragonBlade() {
        return dragonBlade;
    }

    public void setDragonBlade(DragonBlade dragonBlade) {
        this.dragonBlade = dragonBlade;
    }

    public void fight(){
        System.out.println(name+"use"+dragonBlade+"battle!");
    }

}

Test below

If you want Guan Yu to fight with Qinglong Yanyue Dao, you must obtain Guan Yu and Qinglong Yanyue Dao from the Spring container during the test

Then set their dependencies to run normally. The code is as follows

@Test
public void getGuan(){
    DragonBlade blade=acac.getBean(
            "dragonBlade",DragonBlade.class);
    GuanYu guanYu=acac.getBean("guanYu",GuanYu.class);
    guanYu.setDragonBlade(blade);
    guanYu.fight();
}

The problem with the above code

We get Qinglong Yanyue Dao, Guan Yu, and set their dependencies

This is complicated, especially when there are more and more dependent objects

We hope to set up these complex dependencies within Spring

We can directly obtain Guan Yu who has set the dependency object and use it directly

This is dependency injection

Dependency injection: the process of successfully setting the dependencies of objects within the Spring container

Dependency injection (DI)

In the last lesson, we explained the concept of dependency and the definition of dependency injection

Dependency injection is the operation of setting the dependencies of objects with dependencies inside the Spring container

Implementation of dependency injection

It's the same as saving to the Spring container

Dependency injection can also be implemented by @ Bean and component scanning

@Bean implements dependency injection

First, create a bean package for writing code

Create a SkyLance class in the package

public class SkyLance {

    private String name="Fang Tianhua Halberd";
	
    // get\set omitted
    @Override
    public String toString() {
        return name;
    }
}

Create another LvBu class

public class LvBu {

    private String name="Lv Bu";

    private SkyLance skyLance;

    // get\set omitted

    public void fight(){
        System.out.println(name+"use"+skyLance+"battle");
    }
}

Finally, create a ConfigBean class in which @ Bean is used to implement dependency injection

@Configuration
public class ConfigBean {

    @Bean
    public SkyLance skyLance(){
        return new SkyLance();
    }

    // In the parameter list of the following method, we use parameters to represent the type required by the current method
    // Spring will automatically select the appropriate object from the spring container and assign the parameter value
    // That is, when this method runs, the parameter l is the SkyLance object saved to the Spring container above
    @Bean
    public LvBu lvBu(SkyLance l){
        LvBu lb=new LvBu();
        lb.setSkyLance(l);
        return lb;
    }
}

Create a bean package in the test folder for testing

The code is as follows

@Test
public void bean(){
    //Get Lv Bu
    LvBu lb=acac.getBean("lvBu",LvBu.class);
    lb.fight();
}

Now the test is successful because there is only one square halberd in the current Spring container

If there is no halberd object in the Spring container, an error will be reported

In addition, if there are more than two Fang Tianhua halberds in the Spring container, the parameter name of the method we save the Lubu object must match the id of the starting Fang Tianhua halberd, otherwise the operation will also report an error

The configuration code is as follows

@Configuration
public class ConfigBean {

    @Bean
    public SkyLance skyLance(){
        return new SkyLance();
    }
    @Bean
    public SkyLance lance(){
        return new SkyLance();
    }

    // The parameter name of the following method must be consistent with one of the IDS saved to the Spring container object above
    @Bean
    public LvBu lvBu(SkyLance lance){
        LvBu lb=new LvBu();
        lb.setSkyLance(lance);
        return lb;
    }
}

Component scanning to implement dependency injection

We can use the examples of Zhang Fei and Zhangba snake spear to realize the dependency injection of component scanning

First create the comp package, which creates the Zhangba snake spear class

@Component
public class SnakeLance {
    private String name="Zhangba snake spear";

   	//  .....
    @Override
    public String toString() {
        return name;
    }
}

Note the writing of @ Component annotation

Then create Zhang Fei class

@Component
public class ZhangFei {

    private String name="Zhang Yide";

    // Add @ Autowired annotation on the attribute (member variable) of the class
    // Indicates that the current property will automatically get the corresponding object from the Spring container
    @Autowired
    private SnakeLance snakeLance;

    //...

    public void fight(){
        System.out.println(name+"use"+snakeLance+"battle");
    }


}

Configuration class ConfigComp

@Configuration
@ComponentScan("comp")
public class ConfigComp {
}

Now you can test the class in this state

Create test class TestComp

@Test
public void comp(){
    // Get Zhang Fei
    ZhangFei zhangFei=acac.getBean("zhangFei",
                                    ZhangFei.class);
    zhangFei.fight();
}

If there are multiple objects of type Zhangba snake spear in the Spring container

For example, @ Bean has been added to the configuration class, which saves an object to the Spring container

@Configuration
@ComponentScan("comp")
public class ConfigComp {
    @Bean
    public SnakeLance lance(){
        return new SnakeLance();
    }

}

At this time, the attribute name of the member variable marked by @ Autowired annotation will be used as the basis for selecting the object by default, and the object with the same id and member variable name will be injected

However, if the id and member variable name are not consistent, the injection fails and an error is reported

At this time, modifying the member variable name will cause some column maintenance, which is inconvenient

It is recommended that you use @ Qualifier annotation to solve the problem

The code is as follows

// Add @ Autowired annotation on the attribute (member variable) of the class
// Indicates that the current property will automatically get the corresponding object from the Spring container
// If there are more than two objects of this type in the spring container,
// It automatically matches objects with the same id and attribute name
@Autowired
// @The Qualifier annotation can specify a name that matches the id in the Spring container
// Assign the matching object to the following properties,
// It is generally used when there are multiple objects in a Spring container of one type
@Qualifier("lance")
private SnakeLance snakeLance;

IOC\DI decoupling

What is decoupling

Decoupling means uncoupling

To understand decoupling, you need to know what coupling is

The so-called coupling refers to the difficulty of replacing components, objects and parts in the current program

If the replacement object brings less modification, that is, it is easy to replace, we call it loose coupling

If the change object brings many modifications, that is, it is difficult and easy to change, we call it tight coupling

In programs, loosely coupled programs are easier to cope with changes and better expand and maintain, so our programs pursue loose coupling

Back to the topic, the so-called decoupling is the process of reducing the coupling of programs and changing tightly coupled programs into loosely coupled programs

The programs we wrote above are tightly coupled. For example, Guan Yu can only rely on Qinglong Yanyue Dao to fight. If you want to decouple now, you must modify the dependency between Guan Yu and Qinglong Yanyue Dao,

The decoupled operation modifies the specific type of dependency to an interface type

The interface \ abstract class in java program does not belong to "concrete type", and its attribute is "abstract type"

Create a new package ou

Create a new interface in the package

The code is as follows

// For weapon interface, Guan Yu class relies on this interface type to realize decoupling
public interface Weapon {
    
    String getName();
}

Then create Qinglong Yanyue Dao and Zhangba snake spear

@Component
public class DragonBlade implements Weapon {

    private String name="falchion";

  	//...

    @Override
    public String toString() {
        return name;
    }
}
@Component
public class SnakeLance implements Weapon{

    private String name="Zhangba snake spear";

   //...

    @Override
    public String toString() {
        return name;
    }
}

Create Guan Yu class

@Component
public class GuanYu {

    private String name="Guan Yunchang";

    @Autowired
    // Previously, we relied on specific types, but now we rely on interfaces of this type to realize decoupling
    @Qualifier("snakeLance")
    private Weapon weapon;

    public void fight(){
        System.out.println(name+"use"+weapon+"battle");
    }
	//...
}

Configuration class

@Configuration
@ComponentScan("ou")
public class ConfigOu {
}

Finally, in the java test package

@Test
public void run(){
    // Get Guan Yu
    GuanYu guanYu=acac.getBean("guanYu",
                                        GuanYu.class);
    guanYu.fight();

}

Keywords: Java Spring intellij-idea

Added by Jack McSlay on Thu, 24 Feb 2022 11:50:27 +0200