Spring#

The core goals of Spring are:

  • Dependency Injection (DI): Promotes loose coupling by managing object creation and wiring. This makes testing significantly better.

  • Aspect-Oriented Programming (AOP): Helps separate cross-cutting concerns.[2]

  • Simplified Configuration: Spring reduces boilerplate code through annotations and Java-based configuration, letting developers focus on business logic rather than setup.

  • Integration Support: Easily integrates with other frameworks like JUnit and Mockito, making it easier for developers to write clean, maintainable, and testable code.

  • Web Development: Spring MVC provides a powerful way to build web applications.

While Spring is mostly for Web Development, we will be focusing on the features beneficial to a client application. In particular, we will look at how it helps us create services and supports Dependency Injection.

Spring vs Spring Boot

Spring is a large framework with many modules: dependency injection, AOP, data access, web, security, and more. The problem is that wiring all of these together traditionally required a significant amount of configuration. Developers spent considerable time writing XML files or Java configuration classes just to get a basic application running before writing any real business logic.

Spring Boot is not a replacement for Spring, it’s Spring with the setup done for you. It was introduced to eliminate that configuration burden through three key ideas.[4]

Services#

A service should be stateless, thread-safe, and idempotent[1]. With these qualities we can create a Singleton object, one and only instance of the service to field all the requests.

Service simply means “business logic layer” and doesn’t necessarily imply an online, web-based architecture. It’s the go-to naming convention for classes that orchestrate operations and coordinate between different concerns.

Spring allows a developer to easily create a Service with the @Service annotation.

Annotations#

There are many annotations that come with Spring Boot. Here are some of the top-level annotations.

Core Component Annotations#

  • @Component is the most generic stereotype annotation. It marks a class as a Spring-managed component, meaning Spring will detect it during classpath scanning and register it as a bean in the application context. Use this when your class doesn’t fit into a more specific category.

  • @Service is a specialization of @Component that indicates a class holds business logic. While functionally identical to @Component, it provides semantic meaning and makes your code more readable. Use this for your service layer classes.

  • @Repository is another @Component specialization for data access classes. It provides additional benefits like automatic exception translation from database-specific exceptions to Spring’s DataAccessException hierarchy.

  • @Controller marks a class as a Spring MVC controller that handles web requests. The @RestController annotation combines @Controller with @ResponseBody, making it ideal for RESTful web services.

Dependency Injection:#

  • @Autowired tells Spring to automatically inject dependencies into your class. You can use it on constructors (preferred), setters, or fields. (See Dependency Injection) Spring will find a matching bean in the application context and wire it in. Constructor injection is generally recommended over field injection for better testability.

  • @MockitoBean is an annotation that uses Mockito under the hood to replace a bean (to Mock a dependency) in the Spring ApplicationContext. See the Spring Configuration and Testing in Dependency Injection

Configuration:#

  • @Configuration indicates a class contains bean definitions. Methods annotated with @Bean inside a @Configuration class will be called by Spring to register those beans in the application context.

  • @Bean is used on methods within @Configuration classes to explicitly define beans. The method’s return value is registered as a bean in Spring’s Application Context.
    -@SpringBootApplication is put on the application Main class with the main method.

Testing:#

  • @SpringBootTest is used in integration tests to load the complete application context. It bootstraps the entire Spring Boot application, allowing you to test with all your actual beans and configurations. While this is convenient and easy, it is discouraged because it is heavy-weight. For faster, more isolated unit tests, developers should try to avoid this annotation. Using it can launch the application and trigger unintended behavior (e.g. GUI appearing).

  • @TestConfiguration is used to define additional beans or override existing beans specifically for testing purposes. Unlike @Configuration, it won’t be picked up by component scanning in your main application. You typically use it as a static inner class within your test or import it explicitly in your test with @Import. This is helpful when you need to mock certain beans or provide test-specific configurations without affecting your production code.

These annotations work together to provide Spring’s dependency injection, component scanning, and configuration capabilities that make Spring Boot development straightforward.

Beans#

In Spring Boot, a bean is simply an object that is created, managed, and maintained by the Spring IoC (Inversion of Control) container.

A bean is just a regular Java object, but instead of creating it with the new keyword, Spring creates it and keeps track of it. Spring handles the bean’s entire lifecycle—instantiation, dependency injection, initialization, and destruction. This can make the code more modular, testable, and loosely coupled.

Spring discovers and registers beans using the annotations @Component and @Service. In the following example both ImageService and ImageProcessor are beans, and the ImageProcessor has a dependency on ImageService which is injected in the constructor.

@Service
public class ImageService {
    // This becomes a bean managed by Spring
}

@Component
public class ImageProcessor {
    private final ImageService imageService;
    
    @Autowired
    public ImageProcessor(ImageService imageService) {
        // Spring automatically injects the ImageService bean argument.
        // This is called `Constructor Injection` and is the preferred method of DI.
        this.imageService = imageService;
    }
}

When the application starts, Spring creates one instance of ImageService (by default, beans are singletons) and automatically injects it into ImageProcessor. The application code never includes new ImageService() because Spring handles it all. However, when writing tests, one would want to explicitly instantiate services, real or mocked, to control the tests to make them predictable.

temp

Details on @Autowired#

@Autowired is a Spring‑provided annotation that enables Dependency Injection (DI). It tells Spring:

“Find a bean of this type and inject it here.”

You can place @Autowired on a constructor, setter, or field.

constructor injection is the recommended approach.

How Spring knows what to inject#

To be injected, a class must be created and managed by the Spring IoC container. This happens when the class is annotated with:

  • @Component

  • @Service

  • or defined in a @Configuration class using a @Bean method

Spring scans the project for these annotations (component scanning) and creates singletons by default. [3]

When multiple beans share the same interface#

If more than one bean implements the same interface, Spring cannot choose automatically. In that case you can:

  • Mark one bean as the default using @Primary

  • Select a specific bean by name using @Qualifier("beanName")

Using @Configuration and @Bean#

Instead of relying only on component scanning, you can also explicitly declare beans using:

@Configuration  
public class AppConfig {  
    @Bean("stripe")  
    public PaymentService stripePaymentService() {
        // Set a required constructor argument
        StripePaymentService sps = new StripePaymentService(Service.STRIPE_SERVICE);  

        // Customize the object further with some post-creation initialization.
        // PaymentHelper is an external library and cannot be injected because
        // it cannot be annotated with @Component.
        sps.setPrimaryHelper(new PaymentHelper());
    }  
}

This allows fine‑grained control over object creation which is useful for setting constructor parameters, or customizing the objects with some extra initialization (as shown above). This configuration code is leveraged in the Service Locator Pattern discussed below.

Example#

// A named Component
// @Qualifier("paypal") // alternative way to name the component 
@Component("paypal")  
public class PaypalPaymentService implements PaymentService { 
    @Autowired // field injection
    private CreditCardService cardService;
    
    // this will be set via Setter Injection
    private InspectionService inspectionService;

    @Autowired // Setting injection
    public void setInspectionService(InspectionService inspectionService) { ... }
}

@Service  
@Primary  
public class CheckoutService {  
	private final PaymentService paymentService;

	@Autowired  // constructor injection with qualifier
	public CheckoutService(@Qualifier("paypal") PaymentService paymentService) {  
    	this.paymentService = paymentService;  
	}  
}

Quick Recap#

  • @Autowired injects dependencies managed by Spring.

  • Spring finds injectable classes through @Component/@Service or through @Bean methods.

  • Use @Qualifier("name") when multiple implementations exist. Or, use @Primary to define the preferred implementation.

Spring Configuration and Testing#

Spring will look at the return type of methods annotated with @Bean and call these methods to instantiate beans.

@Configuration
public class AppConfig {
    @Bean
    public ImageService imageService() {
        MySpecialImageService imgSvc = new MySpecialImageService();
        imgSvc.customInit();
        return imgSvc;
    }
}

// In a Test we can override the registered beans
@SpringBootTest
@Import(MyTest.TestConfig.class)
public class MyTest {
    @TestConfiguration
    static class TestConfig {        
        @Bean
        @Primary // use this bean over any other
        public ImageService imageService() {
            ImageService mocked = new ImageService() {
                @Override
                public BufferedImage getImage(String name) {
                    return Mockito.mock(BufferedImage.class);
                }
            };
            return mocked;
        }
    }
}
// Alternatively a Test can override the registered beans with Mockito
@SpringBootTest
@Import(MyTest.TestConfig.class)
public class MyTest {
    @TestConfiguration
    static class TestConfig {        
        @Bean
        @Primary
        public ImageService imageService() {
            ImageService mocked = Mockito.mock(ImageService.class);
            when(mocked.getImage(anyString()))
                .thenReturn(Mockito.mock(BufferedImage.class));
            return mocked;
        }
    }
}

// Alternatively, Mockito can be used as follows:
@SpringBootTest
public class MyTest {
    @MockitoBean
    private ImageService imageService;
    
    @Test
    public void testSomething() {
        when(imageService.getImage(anyString()))
            .thenReturn(Mockito.mock(BufferedImage.class));
        // your test code
    }
}

See Also

For basic JUnit setup, assertions, and test lifecycle, see the JUnit lesson.

For detailed Mockito usage and mocking techniques, see the Mockito lesson.

Service Locator Pattern#

Another IoC style is the Service Locator pattern. It is NOT recommended as it is often considered an anti-pattern. The way it works it to have an ApplicationContextProvider class that is accessed via a Singleton pattern. It will locate and invoke @Configuration code that has defined @Beans.

Here is a sample code that creates a Service Locator object:

/**
 * This is a Singleton Pattern. This is a Component that will get automatically
 * registered by the IoC SpringBoot framework. The ApplicationContext object
 * will be saved and made available via the static field. This will allow all
 * other objects, Component or not, to use IoC to fulfill dependencies. Example:
 * 
 *    imageService = ApplicationContextProvider
 *                      .getApplicationContext()
 *                      .getBean(ImageService.class);
 */
@Component
public class ApplicationContextProvider implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) {
        context = applicationContext;
     }

    public static ApplicationContext getApplicationContext() {
        return context;
    }
}

What’s so Important? Billy#

  • Dependency Injection (DI) : Spring automatically creates and wires your objects (beans), giving you loose coupling, cleaner code, and easier testing.

  • Component Scanning & Annotations : With annotations like @Component, @Service, and @Controller, Spring finds and manages your classes without manual setup. This also known as Bean Management.

  • Test‑Friendly Configuration : Features like @Configuration, @Bean, @TestConfiguration, and @MockitoBean let you override or mock dependencies cleanly during testing.

  • Beans are Singletons : By default, when a Spring application starts up, it will instantiate all the Beans it discovers, and only one instance of that Bean. This means that every @Component and @Service is created exactly once.[3]

  • AOP & Integration Support : Spring makes cross‑cutting concerns (e.g. logging and security) and integration with tools like JUnit and Mockito easy and consistent.

Footnotes#

[1] Def idempotent:

An idempotent method is one that can be called multiple times with the same inputs and produces the same result, without causing additional side effects.

[2] AOP is Aspect Oriented Programming. Learn more here.

[3] Beans and Singletons:
By default, when a Spring application starts up, it will instantiate all the Beans it discovers, creating only one instance of each. However, this behavior can be altered by specifying a different scope.

If multiple instances are desired, one can annotate the component with @Scope("prototype") to create a new instance every time the bean is requested.

@Component
@Scope("prototype")
public class ReportGenerator {
    // a new instance is created every time this bean is requested or injected
}

[4] Spring vs Spring Boot
Spring Boot is not a replacement for Spring, it’s Spring with the setup done for you. It was introduced to eliminate configuration burden through three key ideas:

  1. Auto-configuration: Spring Boot looks at your dependencies and makes sensible default configuration decisions automatically. If it sees you’ve added a database driver, it configures a data source. If it sees a web dependency, it sets up a web server.

  2. Starter dependencies: Instead of hunting down a compatible set of libraries, Spring Boot provides curated “starters”; it pulls in everything you need for a given feature.

  3. Embedded server: Traditional Spring applications were packaged and deployed to an external server. Spring Boot embeds the server directly in your application, so you run it like any ordinary Java program.

A useful analogy:

Spring is like all the ingredients and appliances in a professional kitchen. Spring Boot is like a meal kit. Everything is pre-measured and the instructions are straightforward, but the same kitchen is doing the cooking.

In practice today, almost nobody starts a new Spring project without Spring Boot.