Skip to main content

Week 3 Day 5 — How Spring Uses OOP

Goal

Today I want Spring as ordinary Java OOP plus a container, so Week 1 of the Spring book is not a new language.

Main questions:

  1. What is a bean in OOP terms?
  2. What is DI in OOP terms?
  3. What is a proxy in OOP terms?
  4. Why do annotations not replace types?
  5. What do I say in an interview that ties Java to Spring?

1. A bean is an object

@Service
public class OrderService {
private final OrderRepository orders;

public OrderService(OrderRepository orders) {
this.orders = orders;
}

public Order place(CreateOrderRequest request) {
return orders.save(Order.from(request));
}
}

Strip the annotations. This is a class, a constructor invariant, a private field, a public method. Encapsulation, composition, polymorphism if OrderRepository is an interface.

@Service does not change the bytecode of place. It is metadata for the container: “create one instance, inject collaborators, manage the lifecycle.”

Memory sentence:

A Spring bean is a Java object the container constructs and holds. The annotations are instructions, not the object.


2. Dependency Injection is constructor composition

Without Spring:

OrderRepository orders = new JpaOrderRepository(entityManager);
PaymentGateway payments = new StripeGateway(apiKey);
OrderService service = new OrderService(orders, payments);

With Spring, I delete the new graph. I keep the constructors. The container is a composition root.

That is Dependency Inversion from Day 4: the service still names the abstractions. Something else provides the instances.

IoC (“Inversion of Control”) is the broader idea: I no longer control construction. DI is the technique Spring uses.


3. A proxy is a substitute of the same type

When I put @Transactional on place, Spring typically does not rewrite OrderService. It wraps the object.

Caller
│ place()

Proxy (same type as OrderService or its interface)
│ start transaction
│ delegate

Real OrderService.place()
│ commit / rollback

OOP inventory:

  • Substitution: callers depend on the type; they receive a proxy.
  • Override / interface implementation: the proxy supplies the method.
  • Composition: the proxy has-a target and delegates.
  • Polymorphism: the extra behavior runs because the runtime type is the proxy.

Limits that come from Week 2:

  • private methods: not on the type API, not overridden, no proxy.
  • final class/method: CGLIB cannot subclass/override.
  • this.place() inside the class: this is the target, not the proxy. Self-invocation skips the transaction.

Memory sentence:

A Spring proxy is a polymorphic wrapper: same type, extra behavior, then delegate.


4. Annotations are not types

@Autowired, @Service, @Transactional are not OOP. They are markers the container reads.

The OOP design still has to be right:

  • If I depend on a concrete StripeGateway everywhere, @Autowired still couples me.
  • If OrderService has a public mutable List of current requests, the singleton still shares it.
  • If I have no constructor, I have no invariant — field injection will hide that.

Spring Boot auto-configuration creates beans (objects) when conditions match. It does not replace the need to understand classes.


5. The interview bridge

A 90-second answer when they ask “How does Spring relate to OOP?”:

Spring manages objects. A bean is an instance with a lifecycle. I design classes with constructor injection, which is composition and dependency inversion: the service depends on interfaces, the container supplies implementations. Cross-cutting things like transactions use proxies — wrappers of the same type that override methods and delegate. That is why private methods and self-invocation skip @Transactional. I still need equals/hashCode, immutability, and encapsulation; the container does not fix those.

Then I shut up.


6. Where to go next

I now have enough Java to start the Spring book:

Keep speaking Java:


7. Common traps

Trap 1: “Spring is annotations.”
Annotations are metadata. The runtime is objects, proxies, and a graph.

Trap 2: “IoC means I do not use new anywhere.”
I still new value objects, DTOs, and entities. I do not new services and repositories that should be shared or swapped.

Trap 3: Magical @Transactional without the proxy story.

Trap 4: Treating the ApplicationContext as a service locator (getBean) from random classes.
That undoes Dependency Inversion. Ask for collaborators in the constructor.


Practice Questions and Answers

Question 1

What is a Spring bean in Java terms?

Answer:

An object whose construction, wiring, and lifecycle are managed by the container. The class is ordinary Java. The container decides how many instances and which constructor arguments.


Question 2

How is DI composition?

Answer:

The service has-a repository and has-a gateway. Those fields are set from the constructor. Spring is the code that calls that constructor with other beans. I could do the same new graph by hand in a main method.


Question 3

Why does @Transactional need a proxy?

Answer:

The extra start/commit behavior has to run around the method. A wrapper of the same type intercepts the call. That wrapper is a subclass or an interface proxy. The real object stays focused on business code.


Question 4

Why does this.save() skip the transaction?

Answer:

this is the target instance. The proxy is only on the reference the container injected into other beans. An internal call does not enter the wrapper, so no override runs, so no transaction advice.


Question 5

What should I still new in a Spring app?

Answer:

Values and DTOs: new Money(...), new CreateOrderRequest(...), entities in factories. I do not new @Service / @Repository types in application code. Tests may new a service with fakes.


Memory sentences

A bean is an object the container constructs and holds.

DI is constructor composition with a composition root.

A proxy is a polymorphic wrapper. this is not the proxy.

I new values. The container news services.

Next: Week 3 Review