Mastering Spring 5: From Core IoC to Reactive Web with WebFlux

Inversion of Control Container

Underlying Concepts

IoC shifts object creation and wiring responsibilities from application code to the container. The container relies on three main techniques: XML parsing, the factory pattern, and Java reflection. At startup, metadata (XML or annotations) is read, bean definitions are parsed, and the container instantiates objects through reflection. This decouples components and promotes testability.

Container Interfaces: BeanFactory and ApplicationContext

Spring offers two container entry points. BeanFactory is the low‑level API used internally; it creates beans lazily – only when getBean() is called. ApplicationContext extends BeanFactory with enterprise features (message resolution, event publication, etc.) and eagerly instantiates singletons at startup. In practice developers always use ApplicationContext implementations such as ClassPathXmlApplicationContext or AnnotationConfigApplicationContext.

Bean Management via XML

A bean is defined with the <bean> element. The id uniquely identifies the instance; class specifies the fully‑qualified type. By default a no‑arg constructor is used.

<bean id="customerService" class="com.example.service.CustomerService"/>

Setter‑based Dependency Injection

Properties are injected via <property>. The example below shows a Product class receiving two string values:

public class Product {
    private String name;
    private String manufacturer;
    public void setName(String name) { this.name = name; }
    public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; }
}

<bean id="product" class="com.example.model.Product">
    <property name="name" value="Smartphone"/>
    <property name="manufacturer" value="Acme Corp"/>
</bean>

Constructor‑based Injection

Use <constructor‑arg> when the class provides a parameterised constructor:

public class PurchaseOrder {
    private String orderId;
    private String region;
    public PurchaseOrder(String orderId, String region) {
        this.orderId = orderId;
        this.region = region;
    }
}

<bean id="order" class="com.example.model.PurchaseOrder">
    <constructor-arg name="orderId" value="ORD-101"/>
    <constructor-arg name="region" value="EMEA"/>
</bean>

p‑namespace (shortcut)

After declaring the p‑namespace, properties can be set as attributes:

<bean id="product" class="com.example.model.Product" p:name="Tablet" p:manufacturer="GlobalTech"/>

Literal Values: null and Special Characters

<property name="secondaryAddress"><null/></property>
<property name="notes">
    <value><![CDATA[Special <<chars>>]]></value>
</property></chars>

Injecting a Collaborator (external bean)

Use ref to reference another bean by its id.

public class CustomerService {
    private CustomerRepository repository;
    public void setRepository(CustomerRepository repository) { this.repository = repository; }
    public void process() { repository.save(); }
}

<bean id="customerService" class="com.example.service.CustomerService">
    <property name="repository" ref="customerRepository"/>
</bean>
<bean id="customerRepository" class="com.example.dao.CustomerRepository"/>

Inner Beans

For strong one‑to‑one relationships a bean can be nested directly inside a <property>:

public class Employee {
    private Department department;
    public void setDepartment(Department department) { this.department = department; }
}
public class Department { private String name; … }

<bean id="employee" class="com.example.model.Employee">
    <property name="department">
        <bean class="com.example.model.Department">
            <property name="name" value="Engineering"/>
        </bean>
    </property>
</bean>

Cascading Assignment

Properties of nested objects can be set directly using dot notation, provided a getter exists:

<property name="department.name" value="Research"/>

Collection Injection

Arrays, Lists, Sets, and Maps are supported. The following bean holds multiple collection properties:

public class Student {
    private String[] courses;
    private List<String> hobbies;
    private Map<String,String> scores;
    private Set<String> certifications;
    // setter methods omitted for brevity
}

<bean id="student" class="com.example.model.Student">
    <property name="courses">
        <array>
            <value>Algorithms</value>
            <value>Databases</value>
        </array>
    </property>
    <property name="hobbies">
        <list>
            <value>Cycling</value>
            <value>Photography</value>
        </list>
    </property>
    <property name="scores">
        <map>
            <entry key="Physics" value="A"/>
            <entry key="Chemistry" value="B+"/>
        </map>
    </property>
    <property name="certifications">
        <set>
            <value>AWS</value>
            <value>OCPJP</value>
        </set>
    </property>
</bean>

To inject a list of object references:

<property name="enrolledCourses">
    <list>
        <ref bean="math101"/>
        <ref bean="cs201"/>
    </list>
</property>

Extracting Shared Collections with util Namespace

Declare the util namespace and define reusable collection beans:

<util:list id="defaultGenres">
    <value>Comedy</value>
    <value>Drama</value>
</util:list>

<bean id="movie" class="com.example.model.Movie">
    <property name="genres" ref="defaultGenres"/>
</bean>

FactoryBean

When a bean’s creation logic is complex, implement FactoryBean. The container will call getObject() to obtain the actual instance, which may differ from the bean class declared in XML.

public class ReportFactory implements FactoryBean<Report> {
    @Override
    public Report getObject() throws Exception {
        Report r = new Report();
        r.setTitle("Quarterly Summary");
        return r;
    }
    @Override
    public Class<?> getObjectType() { return Report.class; }
    @Override
    public boolean isSingleton() { return true; }
}

<bean id="report" class="com.example.factory.ReportFactory"/>

// Usage:
Report rep = context.getBean("report", Report.class);

Bean Scopes

The scope attribute controls instance creation. singleton (default) creates one instance per container; prototype creates a new instance each time getBean() is called. Singleton beans are eager‑loaded; prototype beans are lazy.

Bean Lifecycle

Lifecycle phases (without post‑processor): construct → set properties → optional init method → ready for use → optional destroy method on container close.

public class ConnectionPool implements AutoCloseable {
    public ConnectionPool() { System.out.println("Step 1: instantiation"); }
    private String url;
    public void setUrl(String url) { this.url = url; System.out.println("Step 2: properties set"); }
    public void init() { System.out.println("Step 3: custom init"); }
    public void cleanup() { System.out.println("Step 5: custom destroy"); }
}

<bean id="pool" class="com.example.util.ConnectionPool"
      init-method="init" destroy-method="cleanup">
    <property name="url" value="jdbc:h2:mem:test"/>
</bean>

With a BeanPostProcessor two extra steps are inserted: postProcessBeforeInitialization after property setting, and postProcessAfterInitialization after the custom init method.

Autowiring

Set autowire to byName (matches property name with bean id) or byType (matches property type with a single compatible bean).

<bean id="employee" class="com.example.model.Employee" autowire="byType"/>

External Property Files

Load .properties files using <context:property-placeholder> and reference values with ${…}. This is commonly used for datasource configuration.

<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${db.driver}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.user}"/>
    <property name="password" value="${db.pass}"/>
</bean>

Annotation‑Based Container Configuration

Stereotype annotations – @Component, @Service, @Repository, @Controller – all cause a class to become a Spring bean. Enable component scanning with <context:component-scan>.

@Service("billingService")
public class BillingService { … }

<context:component-scan base-package="com.example"/>

Dependency Injection Annotations:

  • @Autowired wires by type. Combined with @Qualifier it narrows down by bean name.
  • @Resource (JSR‑250) can inject by name (@Resource(name="...")) or by type.
  • @Value injects simple values or property placeholders.

Full Java‑Based Configuration

Replace XML entirely with @Configuration and @ComponentScan. The container is bootstrapped via AnnotationConfigApplicationContext.

@Configuration
@ComponentScan("com.example")
public class AppConfig { }

ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
BillingService service = ctx.getBean(BillingService.class);

Aspect‑Oriented Programming (AOP)

AOP separates cross‑cutting concerns (logging, security, transactions) from business logic. Spring uses dynamic proxies: JDK proxies for interfaces, CGLIB for classes without interfaces.

Key Terminology

  • Join point: any point during execution (e.g., method call).
  • Pointcut: expression that selects join points.
  • Advice: action taken at a join point (before, after, etc.).
  • Aspect: module encapsulating pointcuts and advices.

Pointcut Expressions

Using AspectJ expression syntax: execution(modifiers‑pattern? ret‑type‑pattern declaring‑type‑pattern? name‑pattern(param‑pattern) throws‑pattern?)

execution(* com.example.service.*.*(..))

Annotation‑Based Aspects with AspectJ

Enable @AspectJ support with <aop:aspectj-autoproxy/> (or @EnableAspectJAutoProxy). Define an aspect bean and mark advice methods.

@Component
@Aspect
public class LoggingAspect {
    @Before("execution(* com.example.service.OrderService.placeOrder(..))")
    public void logBefore() { System.out.println("Order placement started"); }

    @AfterReturning("execution(* com.example.service.OrderService.placeOrder(..))")
    public void logAfterReturn() { System.out.println("Order successfully placed"); }

    @After("execution(* com.example.service.OrderService.placeOrder(..))")
    public void logAfter() { System.out.println("Order processing completed"); }

    @AfterThrowing("execution(* com.example.service.OrderService.placeOrder(..))")
    public void logException() { System.out.println("Exception occurred"); }

    @Around("execution(* com.example.service.OrderService.placeOrder(..))")
    public Object aroundAdvice(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("Around before");
        Object result = jp.proceed();
        System.out.println("Around after");
        return result;
    }
}

Reusing Pointcuts

@Pointcut("execution(* com.example.service.OrderService.*(..))")
public void orderServiceLayer() { }

@Before("orderServiceLayer()")
public void beforeAnyOrderMethod() { … }

Aspect Ordering

Use @Order(n) on the aspect class; smaller numbers have higher precedence.

XML‑Schema Based AOP

When annotations are not preferred, <aop:config> allows declarative aspect definition.

<aop:config>
    <aop:pointcut id="businessMethods" expression="execution(* com.example.service.*.*(..))"/>
    <aop:aspect ref="loggingAspect">
        <aop:before method="logBefore" pointcut-ref="businessMethods"/>
        <aop:after-returning method="logAfterReturn" pointcut-ref="businessMethods"/>
    </aop:aspect>
</aop:config>

Data Access with JdbcTemplate

JdbcTemplate simplifies JDBC operations, handling resource management and exception translation. Configure a DataSource and the JdbcTemplate bean.

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/library"/>
    <property name="username" value="root"/>
    <property name="password" value="secret"/>
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

DAO classes receive the template via @Autowired.

@Repository
public class LibraryItemDao {
    @Autowired
    private JdbcTemplate jdbc;
    
    public void insert(LibraryItem item) {
        String sql = "INSERT INTO catalog(id, title, status) VALUES (?,?,?)";
        jdbc.update(sql, item.getId(), item.getTitle(), item.getStatus());
    }
    
    public void update(LibraryItem item) {
        String sql = "UPDATE catalog SET title=?, status=? WHERE id=?";
        jdbc.update(sql, item.getTitle(), item.getStatus(), item.getId());
    }
    
    public void delete(String id) {
        String sql = "DELETE FROM catalog WHERE id=?";
        jdbc.update(sql, id);
    }
    
    public int count() {
        return jdbc.queryForObject("SELECT COUNT(*) FROM catalog", Integer.class);
    }
    
    public LibraryItem findById(String id) {
        String sql = "SELECT * FROM catalog WHERE id=?";
        return jdbc.queryForObject(sql, new BeanPropertyRowMapper<>(LibraryItem.class), id);
    }
    
    public List<LibraryItem> findAll() {
        String sql = "SELECT * FROM catalog";
        return jdbc.query(sql, new BeanPropertyRowMapper<>(LibraryItem.class));
    }
    
    public void batchInsert(List<Object[]> batchArgs) {
        String sql = "INSERT INTO catalog(id, title, status) VALUES (?,?,?)";
        jdbc.batchUpdate(sql, batchArgs);
    }
}

Transaction Management

Transactions group multiple operations into an atomic unit. Spring provides both programmatic and declarative transaction management. Declarative transactions use AOP under the hood.

Annotation‑Driven Declarative Transactions

First, define a PlatformTransactionManager (e.g., DataSourceTransactionManager). Then enable annotation‑driven transactions with <tx:annotation-driven>.

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>

Annotate service classes or methods with @Transactional. The following example models a bank transfer:

@Service
@Transactional
public class BankService {
    @Autowired
    private AccountDao accountDao;
    
    public void transfer(String from, String to, double amount) {
        accountDao.debit(from, amount);
        accountDao.credit(to, amount);
    }
}

@Repository
public class AccountDao {
    @Autowired
    private JdbcTemplate jdbc;
    
    public void debit(String account, double amount) {
        jdbc.update("UPDATE accounts SET balance = balance - ? WHERE acc_no = ?", amount, account);
    }
    
    public void credit(String account, double amount) {
        jdbc.update("UPDATE accounts SET balance = balance + ? WHERE acc_no = ?", amount, account);
    }
}

Transaction Settings

@Transactional accepts attributes like propagation (e.g., REQUIRED), isolation (e.g., READ_COMMITTED), timeout (seconds), readOnly (optimisation for reads), rollbackFor and noRollbackFor.

XML‑Based Declarative Transactions

Use <tx:advice> and AOP to weave transaction behaviour:

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="transfer*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut id="serviceOps" expression="execution(* com.example.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOps"/>
</aop:config>

Fully Annotated Transaction Configuration

A configuration class can define everything without XML:

@Configuration
@EnableTransactionManagement
@ComponentScan("com.example")
public class TxAppConfig {
    @Bean
    public DruidDataSource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/banking");
        ds.setUsername("root");
        ds.setPassword("secret");
        return ds;
    }
    @Bean
    public JdbcTemplate jdbcTemplate(DataSource ds) {
        JdbcTemplate t = new JdbcTemplate();
        t.setDataSource(ds);
        return t;
    }
    @Bean
    public PlatformTransactionManager txManager(DataSource ds) {
        return new DataSourceTransactionManager(ds);
    }
}

Spring 5 Modern Features

  • Logging: Spring 5 uses Log4j2 by default; configure log4j2.xml.
  • @Nullable: Annotate methods, parameters, or fields to indicate they may accept or return null, improving integration with static analysis tools.
  • Functional Bean Registration: Use GenericApplicationContext to register beans with lambda expressions: ``` GenericApplicationContext ctx = new GenericApplicationContext(); ctx.refresh(); ctx.registerBean("sample", Sample.class, () -> new Sample()); Sample s = ctx.getBean(Sample.class);
  • JUnit 5 Integration: With @ExtendWith(SpringExtension.class) and @ContextConfiguration, or the composite @SpringJUnitConfig, tests seamlessly inject beans.
  • Reactive Web: Spring WebFlux

Reactive Programming with Spring WebFlux

WebFlux is a non‑blocking, asynchronous web framework introduced in Spring 5. It runs on Netty (or Servlet 3.1+ containers) and is built on Project Reactor.

Reactor Core: Mono and Flux

Mono emits 0 or 1 item; Flux emits 0..N items. Both adhere to the Reactive Streams specification and emit item signals, error signals, and completion signals. Subscribe to trigger data flow.

Flux.just("Spring", "Reactor", "WebFlux")
    .map(String::toUpperCase)
    .subscribe(System.out::println);

Annotation‑Based WebFlux

Similar to Spring MVC but returns reactive types:

@RestController
public class UserController {
    @Autowired
    private UserService userService;
    
    @GetMapping("/users/{id}")
    public Mono<User> getUser(@PathVariable int id) {
        return userService.findById(id);
    }
    
    @GetMapping("/users")
    public Flux<User> listUsers() {
        return userService.findAll();
    }
}

Functional Endpoints

Define routes with RouterFunction and handle requests with HandlerFunction:

public class UserHandler {
    private final UserService service;
    public UserHandler(UserService service) { this.service = service; }
    
    public Mono<ServerResponse> getById(ServerRequest req) {
        int id = Integer.parseInt(req.pathVariable("id"));
        return service.findById(id)
            .flatMap(user -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(user))
            .switchIfEmpty(ServerResponse.notFound().build());
    }
}

@Bean
public RouterFunction<ServerResponse> route(UserHandler handler) {
    return RouterFunctions.route()
        .GET("/users/{id}", handler::getById)
        .GET("/users", req -> handler.listUsers(req))
        .build();
}

WebClient

For reactive HTTP calls, use WebClient:

WebClient client = WebClient.create("http://localhost:8080");
Mono<User> user = client.get().uri("/users/1")
    .accept(MediaType.APPLICATION_JSON)
    .retrieve()
    .bodyToMono(User.class);

Spring 5 consolidates classic dependency injection and AOP with forward‑looking reactive patterns, making it a versatile choice for both traditional and high‑throughput applications.

Tags: Spring Framework IoC Dependency Injection aop aspectj

Posted on Mon, 24 Aug 2026 16:45:58 +0000 by moreshion