Spring 6 IoC Container Deep Dive

IoC Container

Inversion of Control

The IoC container serves as the practical implementation of the IoC design pattern in Spring. Components managed by the IoC container are referred to as beans. Before creating a bean, the IoC container must be instantiated first. Spring provides two primary implementations:

① BeanFactory

The fundamental implementation of the IoC container, serving as a internal Spring interface. Designed for Spring's internal use, not intended for application developers.

② ApplicationContext

A sub-interface of BeanFactory offering advanced features. Geared toward Spring users, it is the preferred choice in virtually all scenarios rather than the underlying BeanFactory.

③ Key ApplicationContext Implementations

Retrieving Beans: Three Approcahes

package com.example.spring6.iocxml;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("bean.xml");
        
        // Method 1: retrieve by bean ID
        User user1 = (User) context.getBean("user1");
        System.out.println("" + user1);
        
        // Method 2: retrieve by type
        User user2 = context.getBean(User.class);
        System.out.println("" + user2);
        
        // Method 3: retrieve by both ID and type
        User user3 = context.getBean("user", User.class);
        System.out.println("" + user3);
    }
}

Can a bean be retrieved by its interface type?

Yes, provided the bean is unique.

If an interface has multiple implementations and all are configured as beans, can you retrieve by interface?

No, because the bean is not unique.

Dependency Injection

When a class has properties, the values must be set during object creation.

Approach One: Setter Injection

package com.example.spring6.iocxml.di;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestBook {
    @Test
    public void testSetter() {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("bean-di.xml");
        Book book = context.getBean("book", Book.class);
        System.out.println(book);
    }
}

Book.java

package com.example.spring6.iocxml.di;

public class Book {
    private String bname;
    private String author;

    @Override
    public String toString() {
        return "Book{" +
                "bname='" + bname + '\'' +
                ", author='" + author + '\'' +
                '}';
    }

    public String getBname() {
        return bname;
    }

    public void setBname(String bname) {
        this.bname = bname;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Book(String bname, String author) {
        this.bname = bname;
        this.author = author;
    }

    public Book() {
    }
}

bean-di.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="book" class="com.example.spring6.iocxml.di.Book">
        <property name="author" value="backend"/>
        <property name="bname" value="java"/>
    </bean>
</beans>

Approach Two: Constructor Injection

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- setter injection -->
    <bean id="book" class="com.example.spring6.iocxml.di.Book">
        <property name="author" value="backend"/>
        <property name="bname" value="java"/>
    </bean>
    <!-- constructor injection -->
    <bean id="bookCon" class="com.example.spring6.iocxml.di.Book">
        <constructor-arg name="author" value="java development"/>
        <constructor-arg name="bname" value="spring"/>
    </bean>
</beans>
package com.example.spring6.iocxml.di;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestBook {
    @Test
    public void testConstructor() {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("bean-di.xml");
        Book book = context.getBean("bookCon", Book.class);
        System.out.println(book);
    }
}

Special Value Handling

Literal Value Assignment

What is a literal?

int a = 10; declares variable a initialized to 10. When referencing a, you get the value 10.

However, if a has quotes: 'a', it is no longer a variable but represents the letter a itself—this is a literal. Literals have no extended meaning; they are the data as displayed.

<!-- Spring treats value attribute as literal when assigning properties -->
<property name="name" value="John"/>

null Values

<property name="name">
    <null/>
</property>

Note:

<property name="name" value="null"></property>

This assigns the string null, not the Java null value.

XML Entities

<!-- Less-than symbol in XML denotes tag start and cannot be used freely -->
<!-- Solution 1: use XML entity -->
<property name="expression" value="a &amp;lt; b"/>

CDATA Section

<property name="expression">
    <!-- Solution 2: CDATA section -->
    <!-- C in CDATA stands for Character, indicating plain text -->
    <!-- XML parser recognizes CDATA as pure text and won't parse as XML -->
    <!-- Any symbols inside CDATA are allowed -->
    <value><![CDATA[a < b]]></value>
</property>

Assigning Object-Type Properties

Approach One: External Bean Reference

  1. Create two classes: Department and Employee
  2. In Employee bean, use property to reference Department's bean
package com.example.spring6.iocxml.ditest;

public class Department {
    private String dname;

    public void setDname(String dname) {
        this.dname = dname;
    }

    public void info() {
        System.out.println("Department: " + dname);
    }
}
package com.example.spring6.iocxml.ditest;

public class Employee {
    private Department dept;
    private String ename;
    private Integer age;

    public Department getDept() {
        return dept;
    }

    public void work() {
        System.out.println(ename + " working..." + age);
        dept.info();
    }

    public void setDept(Department dept) {
        this.dept = dept;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
package com.example.spring6.iocxml.ditest;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestEmp {
    public static void main(String[] args) {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("bean-ditest.xml");
        Employee emp = context.getBean("emp", Employee.class);
        emp.work();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dept" class="com.example.spring6.iocxml.ditest.Department">
        <property name="dname" value="Operations"/>
    </bean>
    <bean id="emp" class="com.example.spring6.iocxml.ditest.Employee">
        <property name="age" value="23"/>
        <property name="ename" value="alice"/>
        <property name="dept" ref="dept"/>
    </bean>
</beans>

Approach Two: Inner Bean Injection

<!-- inner bean injection -->
<bean id="emp2" class="com.example.spring6.iocxml.ditest.Employee">
    <property name="age" value="25"/>
    <property name="ename" value="bob"/>
    <property name="dept">
        <bean id="dept2" class="com.example.spring6.iocxml.ditest.Department">
            <property name="dname" value="HR"/>
        </bean>
    </property>
</bean>

Approach Three: Cascading Assignment

<!-- cascading assignment -->
<bean id="dept3" class="com.example.spring6.iocxml.ditest.Department">
    <property name="dname" value="R&D"/>
</bean>
<bean id="emp3" class="com.example.spring6.iocxml.ditest.Employee">
    <property name="ename" value="Tom"/>
    <property name="age" value="22"/>
    <property name="dept" ref="dept3"/>
    <property name="dept.dname" value="QA"/>
</bean>

Array Property Injection

package com.example.spring6.iocxml.ditest;

import java.util.Arrays;

public class Employee {
    private Department dept;
    private String ename;
    private Integer age;
    private String[] loves;

    public void work() {
        System.out.println(ename + " working..." + age);
        dept.info();
        System.out.println(Arrays.toString(loves));
    }

    public void setLoves(String[] loves) {
        this.loves = loves;
    }

    public Department getDept() {
        return dept;
    }

    public void setDept(Department dept) {
        this.dept = dept;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="dept" class="com.example.spring6.iocxml.ditest.Department">
        <property name="dname" value="Operations"/>
    </bean>
    <bean id="emp" class="com.example.spring6.iocxml.ditest.Employee">
        <property name="age" value="23"/>
        <property name="ename" value="alice"/>
        <property name="dept" ref="dept"/>
        <property name="loves">
            <array>
                <value>sleeping</value>
                <value>music</value>
                <value>movies</value>
            </array>
        </property>
    </bean>
</beans>

Collection Property Injection

List Collection Injection

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="empOne" class="com.example.spring6.iocxml.ditest.Employee">
        <property name="age" value="23"/>
        <property name="ename" value="alice"/>
    </bean>
    <bean id="empTwo" class="com.example.spring6.iocxml.ditest.Employee">
        <property name="age" value="33"/>
        <property name="ename" value="bob"/>
    </bean>
    <bean id="dept" class="com.example.spring6.iocxml.ditest.Department">
        <property name="dname" value="Tech"/>
        <property name="empList">
            <list>
                <ref bean="empOne"/>
                <ref bean="empTwo"/>
            </list>
        </property>
    </bean>
</beans>
package com.example.spring6.iocxml.ditest;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestDept {
    public static void main(String[] args) {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("bean-dilist.xml");
        Department dept = context.getBean("dept", Department.class);
        dept.info();
    }
}
package com.example.spring6.iocxml.ditest;

import java.util.List;

public class Department {
    private List<Employee> empList;

    public List<Employee> getEmpList() {
        return empList;
    }

    public void setEmpList(List<Employee> empList) {
        this.empList = empList;
    }

    private String dname;

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public void info() {
        System.out.println("Department: " + dname);
        for (Employee emp : empList) {
            System.out.println(emp.getEname());
        }
    }
}

Map Collection Injection

Student.java

package com.example.spring6.iocxml.dimap;

import java.util.Map;

public class Student {
    private String sid;
    private String name;
    private Map<String, Teacher> teacherMap;

    public void run() {
        System.out.println("Student ID: " + sid + " Name: " + name);
        System.out.println(teacherMap);
    }

    @Override
    public String toString() {
        return "Student{" +
                "sid='" + sid + '\'' +
                ", name='" + name + '\'' +
                ", teacherMap=" + teacherMap +
                '}';
    }

    public Map<String, Teacher> getTeacherMap() {
        return teacherMap;
    }

    public void setTeacherMap(Map<String, Teacher> teacherMap) {
        this.teacherMap = teacherMap;
    }

    public String getSid() {
        return sid;
    }

    public void setSid(String sid) {
        this.sid = sid;
    }

    public String getName() {
        return name;
    }

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

Teacher.java

package com.example.spring6.iocxml.dimap;

public class Teacher {
    private String teacherId;
    private String teacherName;

    @Override
    public String toString() {
        return "Teacher{" +
                "teacherId='" + teacherId + '\'' +
                ", teacherName='" + teacherName + '\'' +
                '}';
    }

    public String getTeacherId() {
        return teacherId;
    }

    public void setTeacherId(String teacherId) {
        this.teacherId = teacherId;
    }

    public String getTeacherName() {
        return teacherName;
    }

    public void setTeacherName(String teacherName) {
        this.teacherName = teacherName;
    }
}
package com.example.spring6.iocxml.dimap;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestStu {
    @Test
    public void testStu() {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("bean-dimap.xml");
        Student student = context.getBean("student", Student.class);
        student.run();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="student" class="com.example.spring6.iocxml.dimap.Student">
        <property name="name" value="alice"/>
        <property name="sid" value="111"/>
        <property name="teacherMap">
            <map>
                <entry>
                    <key>
                        <value>10001</value>
                    </key>
                    <ref bean="teacherOne"/>
                </entry>
                <entry>
                    <key>
                        <value>10099</value>
                    </key>
                    <ref bean="teacherTwo"/>
                </entry>
            </map>
        </property>
    </bean>
    <bean id="teacherOne" class="com.example.spring6.iocxml.dimap.Teacher">
        <property name="teacherId" value="22"/>
        <property name="teacherName" value="Wang"/>
    </bean>
    <bean id="teacherTwo" class="com.example.spring6.iocxml.dimap.Teacher">
        <property name="teacherId" value="33"/>
        <property name="teacherName" value="Li"/>
    </bean>
</beans>

Extracting Collection Beans

  1. Create three objects
  2. Inject primitive properties
  3. Use util: namespace for definitions
  4. Reference the util collection bean in Student bean for list and map injection

Lesson.java

package com.example.spring6.iocxml.dimap;

public class Lesson {
    private String lessonName;

    @Override
    public String toString() {
        return "Lesson{" +
                "lessonName='" + lessonName + '\'' +
                '}';
    }

    public String getLessonName() {
        return lessonName;
    }

    public void setLessonName(String lessonName) {
        this.lessonName = lessonName;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd
               http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="student" class="com.example.spring6.iocxml.dimap.Student">
        <property name="name" value="lucy"/>
        <property name="sid" value="111"/>
        <property name="lessonList" ref="lessonList"/>
        <property name="teacherMap" ref="teacherMap"/>
    </bean>
    <util:list id="lessonList">
        <ref bean="lessonOne"/>
        <ref bean="lessonTwo"/>
    </util:list>
    <util:map id="teacherMap">
        <entry>
            <key>
                <value>10001</value>
            </key>
            <ref bean="teacherOne"/>
        </entry>
        <entry>
            <key>
                <value>1208</value>
            </key>
            <ref bean="teacherTwo"/>
        </entry>
    </util:map>
    <bean id="lessonOne" class="com.example.spring6.iocxml.dimap.Lesson">
        <property name="lessonName" value="Java Development"/>
    </bean>
    <bean id="lessonTwo" class="com.example.spring6.iocxml.dimap.Lesson">
        <property name="lessonName" value="Frontend Development"/>
    </bean>
    <bean id="teacherOne" class="com.example.spring6.iocxml.dimap.Teacher">
        <property name="teacherId" value="2201"/>
        <property name="teacherName" value="Wang"/>
    </bean>
    <bean id="teacherTwo" class="com.example.spring6.iocxml.dimap.Teacher">
        <property name="teacherId" value="2200"/>
        <property name="teacherName" value="Liu"/>
    </bean>
</beans>

p Namespace

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util.xsd
               http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- p namespace injection -->
    <bean id="studentP" class="com.example.spring6.iocxml.dimap.Student"
          p:sid="111" p:name="stream" p:lessonList-ref="lessonList" p:teacherMap-ref="teacherMap">
    </bean>
    <bean id="student" class="com.example.spring6.iocxml.dimap.Student">
        <property name="name" value="lucy"/>
        <property name="sid" value="111"/>
        <property name="lessonList" ref="lessonList"/>
        <property name="teacherMap" ref="teacherMap"/>
    </bean>
    <util:list id="lessonList">
        <ref bean="lessonOne"/>
        <ref bean="lessonTwo"/>
    </util:list>
    <util:map id="teacherMap">
        <entry>
            <key>
                <value>10001</value>
            </key>
            <ref bean="teacherOne"/>
        </entry>
        <entry>
            <key>
                <value>1208</value>
            </key>
            <ref bean="teacherTwo"/>
        </entry>
    </util:map>
    <bean id="lessonOne" class="com.example.spring6.iocxml.dimap.Lesson">
        <property name="lessonName" value="Java Development"/>
    </bean>
    <bean id="lessonTwo" class="com.example.spring6.iocxml.dimap.Lesson">
        <property name="lessonName" value="Frontend Development"/>
    </bean>
    <bean id="teacherOne" class="com.example.spring6.iocxml.dimap.Teacher">
        <property name="teacherId" value="2201"/>
        <property name="teacherName" value="Wang"/>
    </bean>
    <bean id="teacherTwo" class="com.example.spring6.iocxml.dimap.Teacher">
        <property name="teacherId" value="2200"/>
        <property name="teacherName" value="Liu"/>
    </bean>
</beans>
package com.example.spring6.iocxml.dimap;

import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestStu {
    @Test
    public void testStu() {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("bean-diref.xml");
        Student student = context.getBean("studentP", Student.class);
        student.run();
    }
}

External Property Files

Maven dependencies

<!-- MySQL Driver -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>
<!-- DataSource -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
</dependency>

jdbc.properties

jdbc.user=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/spring?serverTimezone=UTC
jdbc.driver=com.mysql.cj.jdbc.Driver

Bean configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
    </bean>
</beans>

Test

package com.example.spring6.iocxml.jdbc;

import com.alibaba.druid.pool.DruidDataSource;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestJdbc {
    @Test
    public void demo1() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    }

    @Test
    public void demo2() {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("bean-jdbc.xml");
        DruidDataSource dataSource = context.getBean(DruidDataSource.class);
        System.out.println(dataSource.getUrl());
    }
}

Bean Scope and Lifecycle

package com.example.spring6.iocxml.life;

public class User {
    private String name;

    public User() {
        System.out.println("1 Bean instance created via no-arg constructor");
    }

    public void initMethod() {
        System.out.println("4 Bean initialization via specified init method");
    }

    public void destroyMethod() {
        System.out.println("7 Bean destruction via specified destroy method");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        System.out.println("2 Setting property values on bean");
        this.name = name;
    }
}
package com.example.spring6.iocxml.life;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;

public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("3 BeanPostProcessor before initialization");
        System.out.println(beanName + "::" + bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("5 BeanPostProcessor after initialization");
        System.out.println(beanName + "::" + bean);
        return bean;
    }
}
package com.example.spring6.iocxml.life;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUser {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new
                ClassPathXmlApplicationContext("bean-life.xml");
        User user = context.getBean("user", User.class);
        System.out.println("6 Bean creation complete, ready for use");
        System.out.println(user);
        context.close();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.example.spring6.iocxml.life.User"
          scope="singleton" init-method="initMethod" destroy-method="destroyMethod">
        <property name="name" value="lucy"/>
    </bean>
    <!-- BeanPostProcessor must be registered in the container to take effect -->
    <bean id="myBeanPost" class="com.example.spring6.iocxml.life.MyBeanPost"/>
</beans>

FactoryBean

FactoryBean is a Spring mechanism for integrating third-party frameworks. Unlike regular beans, when configuring a FactoryBean-type bean, what you get is not the class specified in the class attribute, but the return value of the getObject() method. Through this mechanism, Spring hides complex component creation details and presents a simplified interface.

When integrating MyBatis, Spring uses the FactoryBean mechanism to create SqlSessionFactory objects.

package com.example.spring6.iocxml.factorybean;

import org.springframework.beans.factory.FactoryBean;

public class MyFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}
package com.example.spring6.iocxml.factorybean;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("bean-factorybean.xml");
        User user = (User) context.getBean("user");
        System.out.println(user);
    }
}
package com.example.spring6.iocxml.factorybean;

public class User {
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.example.spring6.iocxml.factorybean.MyFactoryBean"/>
</beans>

XML-Based Autowiring

package com.example.spring6.iocxml.auto.controller;

import com.example.spring6.iocxml.auto.service.UserService;
import com.example.spring6.iocxml.auto.service.UserServiceImpl;

public class UserController {
    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void addUser() {
        System.out.println("controller method executed");
        userService.addUserService();
    }
}
package com.example.spring6.iocxml.auto.service;

public interface UserService {
    void addUserService();
}
package com.example.spring6.iocxml.auto.service;

import com.example.spring6.iocxml.auto.dao.UserDao;
import com.example.spring6.iocxml.auto.dao.UserDaoImpl;

public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void addUserService() {
        System.out.println("userService method executed");
        userDao.addUserDao();
    }
}
package com.example.spring6.iocxml.auto.dao;

public class UserDaoImpl implements UserDao {
    @Override
    public void addUserDao() {
        System.out.println("userDao method executed");
    }
}
package com.example.spring6.iocxml.auto.dao;

public interface UserDao {
    void addUserDao();
}
package com.example.spring6.iocxml.auto;

import com.example.spring6.iocxml.auto.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("bean-auto.xml");
        UserController controller = context.getBean("userController", UserController.class);
        controller.addUser();
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- autowire by type -->
    <!-- <bean id="userController" class="com.example.spring6.iocxml.auto.controller.UserController" autowire="byType"/> -->
    <!-- <bean id="userService" class="com.example.spring6.iocxml.auto.service.UserServiceImpl" autowire="byType"/> -->
    <!-- <bean id="userDao" class="com.example.spring6.iocxml.auto.dao.UserDaoImpl"/> -->

    <!-- autowire by name -->
    <bean id="userController" class="com.example.spring6.iocxml.auto.controller.UserController" autowire="byName"/>
    <bean id="userService" class="com.example.spring6.iocxml.auto.service.UserServiceImpl" autowire="byName"/>
    <bean id="userDao" class="com.example.spring6.iocxml.auto.dao.UserDaoImpl"/>
</beans>

Autowire mode: byName

Matches property names as bean IDs in the IoC container to find corresponding beans for assignment.

Annotation-Based Bean Management

Enabling Component Scanning

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <context:component-scan base-package="com.example"/>
</beans>

Scenario 1: Basic scanning

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

Scenario 2: Exclude specified components

<context:component-scan base-package="com.example.spring6">
    <!-- context:exclude-filter specifies exclusion rules -->
    <!-- type: sets exclusion/inclusion criteria -->
    <!-- type="annotation" excludes by annotation, expression contains full class name -->
    <!-- type="assignable" excludes by type, expression contains full class name -->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <!-- <context:exclude-filter type="assignable" expression="com.example.spring6.controller.UserController"/> -->
</context:component-scan>

Scenario 3: Scan only specified components

<context:component-scan base-package="com.example" use-default-filters="false">
    <!-- context:include-filter specifies additional rules -->
    <!-- use-default-filters="false" disables default scanning rules -->
    <!-- must set use-default-filters="false" because default scans all classes in package -->
    <!-- type: sets exclusion/inclusion criteria -->
    <!-- type="annotation" includes by annotation, expression contains full class name -->
    <!-- type="assignable" includes by type, expression contains full class name -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <!-- <context:include-filter type="assignable" expression="com.example.spring6.controller.UserController"/> -->
</context:component-scan>

Bean Creation via Annotations

All four annotations can create beans:

package com.example.bean;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

@Component(value = "user")  // equivalent to <bean id="user" class="..."/>
// @Controller
// @Repository
// @Service
public class User {
}

Property Injection

Using @Autowired alone defaults to byType injection.

package com.example.autowired.controller;

import com.example.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    @Autowired  // find by type, complete injection
    private UserService userService;

    public void add() {
        System.out.println("controller...");
        userService.add();
    }
}
package com.example.autowired.service;

import com.example.autowired.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    private UserDao userDao;

    // Constructor injection
    @Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void add() {
        System.out.println("service...");
        userDao.add();
    }
}
package com.example.autowired.controller;

import com.example.autowired.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {
    private UserService userService;

    // Constructor parameter injection
    public UserController(@Autowired UserService userService) {
        this.userService = userService;
    }

    public void add() {
        System.out.println("controller...");
        userService.add();
    }
}

@Autowired can be appllied to: fields, setter methods, constructors, and constructor parameters.

  • When a constructor has only one parameter, @Autowired can be omitted.
  • @Autowired defaults to byType injection. Use @Qualifier for byName injection.

@Resource Injection

@Resource also performs property injection. Key differences from @Autowired:

  • @Resource is part of the JDK extension (JSR-250 standard), making it more universal.
  • @Autowired is Spring-specific.
  • @Resource defaults to byName; if name is unspecified, uses field name. Falls back to byType if not found.
  • @Autowired defaults to byType; use @Qualifier for byName.
  • @Resource applies to fields and setter methods.
  • @Autowired applies to fields, setter methods, constructors, and constructor parameters.

@Resource requires additional dependency for JDK versions other than JDK 8:

<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>2.1.1</version>
</dependency>
package com.example.resource.controller;

import com.example.resource.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Controller;

@Controller("myUserController")
public class UserController {
    // byName injection
    // @Resource(name = "myUserService")
    // private UserService userService;

    // byType injection
    @Resource
    private UserService userService;

    public void add() {
        System.out.println("controller...");
        userService.add();
    }
}

Full Annotation-Based Development

package com.example.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.example")
public class SpringConfig {
}
package com.example.resource;

import com.example.config.SpringConfig;
import com.example.resource.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestUserControllerAnno {
    public static void main(String[] args) {
        ApplicationContext context = new
                AnnotationConfigApplicationContext(SpringConfig.class);
        UserController controller = context.getBean(UserController.class);
        controller.add();
    }
}

Java Reflection Mechanism

package com.example;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestCar {
    // 1. Multiple ways to obtain Class object

    @Test
    public void test01() throws Exception {
        // Method 1: Class.class
        Class<?> clazz1 = Car.class;
        // Method 2: object.getClass()
        Class<?> clazz2 = new Car().getClass();
        // Method 3: Class.forName("fully.qualified.name")
        Class<?> clazz3 = Class.forName("com.example.Car");

        Car car = (Car) clazz3.getDeclaredConstructor().newInstance();
        System.out.println(car);
    }

    // 2. Obtain constructors via reflection
    @Test
    public void test02() throws Exception {
        Class<?> clazz = Car.class;

        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> c : constructors) {
            System.out.println("Method: " + c.getName() + " Parameter count: " + c.getParameterCount());
        }

        // 1. Public constructor
        // Constructor<?> c1 = clazz.getConstructor(String.class, int.class, String.class);
        // Car car1 = (Car) c1.newInstance("HQ", 10, "Yellow");

        // 2. Private constructor
        Constructor<?> c2 = clazz.getDeclaredConstructor(String.class, int.class, String.class);
        c2.setAccessible(true);
        Car car2 = (Car) c2.newInstance("HQ", 11, "Yellow");
        System.out.println(car2);
    }

    // 3. Obtain fields via reflection
    @Test
    public void test03() throws Exception {
        Class<?> clazz = Car.class;

        Car car = (Car) clazz.getDeclaredConstructor().newInstance();

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (field.getName().equals("name")) {
                field.setAccessible(true);
                field.set(car, "Audi");
            }
            System.out.println(field.getName());
        }
        System.out.println(car);
    }

    // 4. Obtain methods via reflection
    @Test
    public void test04() throws Exception {
        Car car = new Car("BMW", 19, "Black");
        Class<?> clazz = car.getClass();

        Method[] methods = clazz.getMethods();
        for (Method m : methods) {
            if (m.getName().equals("toString")) {
                String invoke = (String) m.invoke(car);
            }

            Method[] allMethods = clazz.getDeclaredMethods();
            for (Method m2 : allMethods) {
                if (m2.getName().equals("run")) {
                    m2.setAccessible(true);
                    m2.invoke(car);
                }
            }
        }
    }
}

Implementing a Custom IoC Container

ApplicationContext.java

package com.example.bean;

import com.example.anno.Bean;

public interface ApplicationContext {
    Object getBean(Class<?> clazz);
}

AnnotationApplicationContext.java

package com.example.bean;

import com.example.anno.Bean;
import com.example.anno.Di;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class AnnotationApplicationContext implements ApplicationContext {
    private Map<Class<?>, Object> beanFactory = new HashMap<>();
    private static String rootPath;

    @Override
    public Object getBean(Class<?> clazz) {
        return beanFactory.get(clazz);
    }

    public AnnotationApplicationContext(String basePackage) {
        try {
            String packagePath = basePackage.replaceAll("\\.", "\\\\");
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), "utf-8");
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());
                loadBean(new File(filePath));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        loadDi();
    }

    private void loadBean(File file) throws Exception {
        if (file.isDirectory()) {
            File[] childrenFiles = file.listFiles();
            if (childrenFiles == null || childrenFiles.length == 0) {
                return;
            }
            for (File child : childrenFiles) {
                if (child.isDirectory()) {
                    loadBean(child);
                } else {
                    String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
                    if (pathWithClass.contains(".class")) {
                        String allName = pathWithClass.replaceAll("\\\\", ".")
                                .replace(".class", "");
                        Class<?> clazz = Class.forName(allName);
                        if (!clazz.isInterface()) {
                            Bean annotation = clazz.getAnnotation(Bean.class);
                            if (annotation != null) {
                                Object instance = clazz.getConstructor().newInstance();
                                if (clazz.getInterfaces().length > 0) {
                                    beanFactory.put(clazz.getInterfaces()[0], instance);
                                } else {
                                    beanFactory.put(clazz, instance);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void loadDi() {
        Set<Map.Entry<Class<?>, Object>> entries = beanFactory.entrySet();
        for (Map.Entry<Class<?>, Object> entry : entries) {
            Object obj = entry.getValue();
            Class<?> clazz = obj.getClass();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field field : declaredFields) {
                Di annotation = field.getAnnotation(Di.class);
                if (annotation != null) {
                    field.setAccessible(true);
                    try {
                        field.set(obj, beanFactory.get(field.getType()));
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}

Bean.java

package com.example.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}

Di.java

package com.example.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}

TestUser.java

package com.example;

import com.example.bean.AnnotationApplicationContext;
import com.example.bean.ApplicationContext;
import com.example.service.UserService;

public class TestUser {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationApplicationContext("com.example");
        UserService userService = (UserService) context.getBean(UserService.class);
        System.out.println(userService);
        userService.add();
    }
}

Tags: Spring IoC Dependency Injection Bean Annotation

Posted on Thu, 17 Sep 2026 16:55:10 +0000 by Travis Estill