Java notes: spring IoC

Posted by Pryach on Thu, 23 Sep 2021 13:58:19 +0200

IoC container

Container is a software environment that provides necessary support for the operation of a specific component. IoC refers to Inversion of Control

Components assembled in IoC container need some kind of "injection" mechanism

For example, a component needs a datasource. Instead of creating a datasource, it waits for the external to inject the datasource

In this way, different components can share resources, and another component can also inject datasource

Dependency injection method 1 set function

public class BookService {
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

Method 2 construction method

public class BookService {
    private DataSource dataSource;

    public BookService(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

Method 3 assembles bean s in a configuration file

<bean id="userService" class="com.itranswarp.learnjava.service.UserService">
        <property name="mailService" ref="mailService" />
</bean>

After writing the configuration file, you can load the configuration file and automatically generate bean s

ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

Fetch bean

// Get Bean:
UserService userService = context.getBean(UserService.class);
// Normal call:
User user = userService.login("bob@example.com", "password");

In addition to ApplicationContext, there is also a container called BeanFactory

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MailService mailService = factory.getBean(MailService.class);

Annotation configuration

The method of using configuration files is very cumbersome, and we can use annotations to simplify the process

@Component
public class MailService {
	...
}

Use this annotation to define a Bean with the default name of mailservice

Then add the annotation @ Autowired to another bean

@Component
public class UserService {
    @Autowired
    MailService mailService;

    ...
}

Using @ Autowired simplifies injection

@Configuration is a configuration class
@ComponentScan tells the container to automatically search the package and sub package of the current class, and automatically create the Bean labeled @ component to assemble according to @ Autowired

Using Annotation with automatic scanning can greatly simplify Spring configuration. We only need to ensure that:

  • Each Bean is marked as @ Component and @ Autowired injection is used correctly;
  • The Configuration class is labeled @ Configuration and @ ComponentScan;
  • All beans are in the specified package and sub package.

Inject List

How to inject classes with the same interface but different implementations into the list

First define an interface

public interface Validator {
    void validate(String email, String password, String name);
}

Then the interface is implemented differently

@Component
public class EmailValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}$")) {
            throw new IllegalArgumentException("invalid email: " + email);
        }
    }
}

@Component
public class PasswordValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (!password.matches("^.{6,20}$")) {
            throw new IllegalArgumentException("invalid password");
        }
    }
}

@Component
public class NameValidator implements Validator {
    public void validate(String email, String password, String name) {
        if (name == null || name.isBlank() || name.length() > 20) {
            throw new IllegalArgumentException("invalid name: " + name);
        }
    }
}

We write this when we inject these bean s

@Component
public class Validators {
    @Autowired
    List<Validator> validators;

    public void validate(String email, String password, String name) {
        for (var validator : this.validators) {
            validator.validate(email, password, name);
        }
    }
}

All beans of type Validator will be injected as a List

The order of bean s in the list can be specified using the oder annotation

Use @ Autowired(required=false) to indicate that the injection is optional

Create third-party bean s

Write a java method in the @ Configuration class to create and return it using the @ Bean annotation

@Configuration
@ComponentScan
public class AppConfig {
    // Create a Bean:
    @Bean
    ZoneId createZoneId() {
        return ZoneId.of("Z");
    }
}

Initialization and destruction

@PostConstruct is used to initialize @ PreDestroy for destruction

@Component
public class MailService {
    @Autowired(required = false)
    ZoneId zoneId = ZoneId.systemDefault();

    @PostConstruct
    public void init() {
        System.out.println("Init mail service with zoneId = " + this.zoneId);
    }

    @PreDestroy
    public void shutdown() {
        System.out.println("Shutdown mail service");
    }
}

The execution process of such a Bean is
1. Construction method
2. Inject according to @ Autowired
3. Initialization
4. Destroy container

alias

Bean s of the same type can be distinguished by aliases when creating beans

@Configuration
@ComponentScan
public class AppConfig {
    @Bean("z")
    ZoneId createZoneOfZ() {
        return ZoneId.of("Z");
    }

    @Bean
    @Qualifier("utc8")
    ZoneId createZoneOfUTC8() {
        return ZoneId.of("UTC+08:00");
    }
}

During injection

@Component
public class MailService {
	@Autowired(required = false)
	@Qualifier("z") // Specifies the ZoneId with the injection name "z"
	ZoneId zoneId = ZoneId.systemDefault();
    ...
}

You can specify a bean as @ Primary

@Configuration
@ComponentScan
public class AppConfig {
    @Bean
    @Primary // Specify as primary Bean
    @Qualifier("z")
    ZoneId createZoneOfZ() {
        return ZoneId.of("Z");
    }

    @Bean
    @Qualifier("utc8")
    ZoneId createZoneOfUTC8() {
        return ZoneId.of("UTC+08:00");
    }
}

When no bean name is specified, the primary bean will be injected

FactoryBean

spring provides a factory pattern using the FactoryBean interface

@Component
public class ZoneIdFactoryBean implements FactoryBean<ZoneId> {

    String zone = "Z";

    @Override
    public ZoneId getObject() throws Exception {
        return ZoneId.of(zone);
    }

    @Override
    public Class<?> getObjectType() {
        return ZoneId.class;
    }
}

Injection configuration

Use @ PropertySource to automatically read the configuration file and then use @ Value injection

@Configuration
@ComponentScan
@PropertySource("app.properties") // Represents app.properties for reading classpath
public class AppConfig {
    @Value("${app.zone:Z}")
    String zoneId;

    @Bean
    ZoneId createZoneId() {
        return ZoneId.of(zoneId);
    }
}
  • "${app. Zone}" means reading the value of the key as app.zone. If the key does not exist, an error will be reported during startup;
  • "${app.zone:Z}" means to read the value whose key is app.zone, but if the key does not exist, the default value Z will be used.

Conditional assembly

According to the annotation @ Profile, you can decide whether to create bean s in the current environment

Using the annotation conditional, you can require that a bean be created only when a certain condition is met

Topics: Java Spring Spring Boot