The Spring framework is the most common framework we use for enterprise-level development. In this chapter, we will learn about the Spring framework and its IOC features as well as its implementation principles: annotations and reflections.

Introduction to Spring Framework

Spring is a lightweight Inversion of Control (IOC) and Aspect oriented Programming (AOP) container framework that provides a one-stop shop for enterprise development.

The advantages of Spring include

1. Convenient decoupling and simplified development

With the IoC container provided by Spring, we can leave dependencies between objects under Spring’s control, avoiding excessive program coupling caused by hard coding. With Spring, users no longer have to code for low-level requirements such as singleton pattern classes and property file parsing, and can focus more on upper-level applications.

2.AOP programming support

The AOP functionality provided by Spring makes it easy to do section-oriented programming, and many functions that are not easy to implement with traditional OOP can be easily handled with AOP.

3. Support for declarative transactions

In Spring, we can free ourselves from tedious transaction management code and use declarative methods to manage transactions flexibly, improving development efficiency and quality.

4. Facilitate the test of the program

Almost all testing can be done in non-container-dependent programming, and in Spring testing is no longer an expensive operation, but something you can do on the fly. For example, Spring’s support for Junit4 makes it easy to test Spring programs with annotations.

5. Convenient integration of various excellent frameworks

Spring does not exclude good open source frameworks. Instead, Spring makes them easier to use by providing direct support for good frameworks such as Struts,Hibernate, Hessian, Quartz, and more.

6. Ease the use of Java EE apis

Spring provides a thin layer of encapsulation for many hard-to-use Java EE apis such as JDBC, JavaMail, remote calls, etc. These Java EE apis are much easier to use through Spring’s easy encapsulation.

  

The composition of the Spring



Spring Core: The Core of the Spring framework. The rest of Spring’s components rely on core components to provide services such as IOC primarily through the BeanFactory.

Spring Context: The Sprin Context is a configuration file that provides Context information to the Spring framework. The Spring context includes enterprise services, such as JNDI, EJB, E-mail, internationalization, validation, and scheduling capabilities.

Spring AOP: The Spring AOP module integrates section-oriented programming functionality directly into the Spring framework through the configuration management feature.

Spring ORM: The Spring framework inserts several ORM frameworks to provide ORM’s object-relational tools, including JDO, Hibernate, and iBatisSQL Map. All follow Spring’s common transaction and DAO exception hierarchies.

Spring DAO: The DAO abstraction layer provides a meaningful exception hierarchy that can be used to manage exception handling and error messages thrown by different database vendors. The exception hierarchy simplifies error handling and greatly reduces the amount of exception code you need to write (such as opening and closing connections). Spring DAO’s JDBC-oriented exceptions follow a common DAO exception hierarchy.

Spring Web: The Web context module builds on the application context module and provides the context for Web-based applications. So, the Spring framework supports integration with Jakarta Struts. The Web module also simplifies handling multi-part requests and binding request parameters to domain objects.

Spring Web MVC: Provides model View Control (MVC) and REST Web services implementations for Web applications. Spring’s MVC framework allows for a complete separation of domain model code from Web forms and can be integrated with all the other features of the Spring framework.

  

The IOC and DI

The Inverse Of Control (IOC) stands for Inverse Of Control, which serves to reduce the degree Of coupling between objects.

In general, we create objects directly inside the object through new, and the program creates dependent objects. IoC has a special container to create these objects, that is, the IoC container controls the creation of objects; This allows the container to control the object, not our object, and completes the inversion of control.

DI(Dependency Injection) : The Dependency relationships between components are determined by the container at runtime. In other words, the container dynamically injects a Dependency into the component. The purpose of dependency injection is not to bring more functions to the software system, but to increase the frequency of component reuse and build a flexible and extensible platform for the system. Through the dependency injection mechanism, we can specify the resources needed by the target and complete our business logic through simple configuration without any code, regardless of where the specific resources come from and who implements them.

  

IOC principle: annotation + reflection

The following example explains the principle of IOC. It simulates the configuration of different CPU and memory for a computer. There are TWO kinds of CPU, AMD and INTEL, and two kinds of memory, DDR8G and DDR16G

  1. / * *
  2. * CPU interface
  3. * /
  4. public interface Cpu {
  5. void run();
  6. }
  7. / * *
  8. * Memory interface
  9. * /
  10. public interface Memory {
  11. void read();
  12. void write();
  13. }
  14. / * *
  15. * AMD的CPU
  16. * /
  17. public class AMDCpu implements Cpu {
  18. public void run() {
  19. System.out.println(“AMD CPU is running….” );
  20. }
  21. }
  22. / * *
  23. * Intel的CPU
  24. * /
  25. public class IntelCpu implements Cpu{
  26. public void run() {
  27. System.out.println(“Intel CPU running….” );
  28. }
  29. }
  30. / * *
  31. * DDR8GB memory
  32. * /
  33. public class DDR8GMemory implements Memory {
  34. public void read() {
  35. System.out.println(” Read data using DDR8G….” );
  36. }
  37. public void write() {
  38. System.out.println(” Write data using DDR8G….” );
  39. }
  40. }
  41. / * *
  42. * DDR16 GB memory
  43. * /
  44. public class DDR16GMemory implements Memory {
  45. public void read() {
  46. System.out.println(” Use DDR16G memory to read data….” );
  47. }
  48. public void write() {
  49. System.out.println(” Use DDR16G memory to write data….” );
  50. }
  51. }
  52. public class TestComputer {
  53. @Test
  54. public void testComputer(){
  55. // Hardcode the object
  56. Computer computer = new Computer();
  57. Cpu cpu = new IntelCpu();
  58. Memory memory = new DDR16GMemory();
  59. computer.setCpu(cpu);
  60. computer.setMemory(memory);
  61. computer.start();
  62. }
  63. }

The above is a hardcoded way to create the CPU and memory properties of the computer, and the code is tightly coupled to the specific subclasses, which is not easy to maintain and expand later.

The idea is that instead of having the program actively create CPU and memory objects, it annotates the CPU and memory types and uses reflection to inject CPU and memory objects into the computer’s properties.

Add code:

  1. / * *
  2. * Notes for computer components
  3. * /
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Target(ElementType.FIELD)
  6. public @interface MyComponent {
  7. / * *
  8. * Component type
  9. * @return
  10. * /
  11. Class componentClass();
  12. }
  13. / * *
  14. * the computer class
  15. * /
  16. public class Computer {
  17. @MyComponent(componentClass = IntelCpu.class)
  18. private Cpu cpu;
  19. @MyComponent(componentClass = DDR8GMemory.class)
  20. private Memory memory;
  21. . }
  22. public class TestComputer {
  23. @Test
  24. public void testComputer(){
  25. // Use reflection and annotations to inject CPU and memory attributes
  26. try {
  27. // Get the Computer type
  28. Class<Computer> computerClass = Computer.class;
  29. // Create a Computer object
  30. Computer computer = computerClass.newInstance();
  31. // Get the properties of the Computer object
  32. Field[] fields = computerClass.getDeclaredFields();
  33. // Iterate over attributes
  34. for(Field field : fields){
  35. // Get the MyComponent annotation defined on the property
  36. MyComponent anno = field.getDeclaredAnnotation(MyComponent.class);
  37. // Get the configured component type
  38. Class aClass = anno.componentClass();
  39. // Create an object for this component
  40. Object comp = aClass.newInstance();
  41. // Call the set method to assign a value to the property
  42. String name = field.getName();
  43. Name = “set” + name.substring(0,1).touppercase () + name.substring(1);
  44. // Get the method by method name and parameter type
  45. Method method = computerClass.getDeclaredMethod(name, field.getType());
  46. // Call the method
  47. method.invoke(computer,comp);
  48. }
  49. // Start the computer
  50. computer.start();
  51. } catch (Exception e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }

After the program is modified as above, if you need to modify the configuration of the computer in the later period, you only need to modify the type of the annotation configuration, you can inject different computer components, so as to reduce the coupling between the code, maintenance code becomes simpler.

@MyComponent(componentClass = AMDCpu.class)

private Cpu cpu;

@MyComponent(componentClass = DDR16GMemory.class)

private Memory memory;

conclusion

IOC (Inversion of Control) is the most important principle of Spring. It gives the initiative to create objects to the Spring container. Spring programs can use different objects only with some configuration, which greatly reduces the code coupling and improves the flexibility of programs.