Preparation for Enterprise Frameworks
Before tackling frameworks like Spring, it's essential to understand the architectural patterns that underlie them: Dependency Injection and Inversion of Control.
What You'll Learn
- Inversion of Control (IoC)
- Dependency Injection (DI)
- Repository and Service patterns
- Preparation for Spring Framework
Inversion of Control (IoC)
With IoC, the control of creating and managing dependencies is "inverted" from application code to an external container.
// WITHOUT IoC: the application creates dependencies
class UserService {
private UserRepository repository;
public UserService() {
// The class decides how to create the dependency
this.repository = new MySqlUserRepository();
}
}
// WITH IoC: dependencies are injected from outside
class UserService {
private final UserRepository repository;
// The dependency is provided by an external container
public UserService(UserRepository repository) {
this.repository = repository;
}
}
Dependency Injection
// 1. CONSTRUCTOR INJECTION (preferred)
class OrderService {
private final OrderRepository repository;
private final PaymentService paymentService;
public OrderService(OrderRepository repository, PaymentService paymentService) {
this.repository = repository;
this.paymentService = paymentService;
}
}
// 2. SETTER INJECTION
class OrderService {
private OrderRepository repository;
public void setRepository(OrderRepository repository) {
this.repository = repository;
}
}
// 3. FIELD INJECTION (not recommended without framework)
class OrderService {
@Inject // Requires DI framework
private OrderRepository repository;
}
Simple DI Container
import java.util.*;
import java.util.function.Supplier;
// Simple DI container
class DIContainer {
private Map<Class<?>, Supplier<?>> factories = new HashMap<>();
private Map<Class<?>, Object> singletons = new HashMap<>();
public <T> void register(Class<T> type, Supplier<T> factory) {
factories.put(type, factory);
}
public <T> void registerSingleton(Class<T> type, T instance) {
singletons.put(type, instance);
}
@SuppressWarnings("unchecked")
public <T> T get(Class<T> type) {
if (singletons.containsKey(type)) {
return (T) singletons.get(type);
}
Supplier<?> factory = factories.get(type);
if (factory == null) {
throw new RuntimeException("No registration for: " + type);
}
return (T) factory.get();
}
}
// Usage
public class Application {
public static void main(String[] args) {
DIContainer container = new DIContainer();
// Register dependencies
container.registerSingleton(UserRepository.class,
new InMemoryUserRepository());
container.register(UserService.class,
() -> new UserService(container.get(UserRepository.class)));
// Resolve
UserService service = container.get(UserService.class);
}
}
Repository Pattern
// Repository interface
interface Repository<T, ID> {
Optional<T> findById(ID id);
List<T> findAll();
T save(T entity);
void delete(T entity);
boolean existsById(ID id);
}
// Specific implementation
interface UserRepository extends Repository<User, Long> {
Optional<User> findByEmail(String email);
List<User> findByRole(String role);
}
// Concrete implementation
class JpaUserRepository implements UserRepository {
// Implementation with JPA/JDBC
}
class InMemoryUserRepository implements UserRepository {
private Map<Long, User> storage = new HashMap<>();
private long nextId = 1;
@Override
public Optional<User> findById(Long id) {
return Optional.ofNullable(storage.get(id));
}
@Override
public User save(User user) {
if (user.getId() == null) {
user.setId(nextId++);
}
storage.put(user.getId(), user);
return user;
}
// ... other methods
}
Layered Architecture
// LAYER 1: Controller (handles HTTP requests)
class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
public Response createUser(CreateUserRequest request) {
User user = userService.createUser(request);
return Response.ok(user);
}
}
// LAYER 2: Service (business logic)
class UserService {
private final UserRepository repository;
private final EmailService emailService;
public UserService(UserRepository repository, EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}
public User createUser(CreateUserRequest request) {
// Validation
if (repository.findByEmail(request.getEmail()).isPresent()) {
throw new UserAlreadyExistsException();
}
// Creation
User user = new User(request.getName(), request.getEmail());
User saved = repository.save(user);
// Side effect
emailService.sendWelcome(saved);
return saved;
}
}
// LAYER 3: Repository (data access)
// Already defined above
Next Steps: Spring Framework
What You'll Learn with Spring
- Spring Core: IoC container, Beans, @Autowired
- Spring Boot: auto-configuration, starters
- Spring Data: JPA, MongoDB, Redis
- Spring MVC: REST API, @Controller
- Spring Security: authentication, authorization
// With Spring Boot, the code becomes much simpler
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<User> createUser(@RequestBody CreateUserRequest request) {
User user = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
}
@Service
public class UserService {
@Autowired
private UserRepository repository;
// Business logic...
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
Congratulations!
You've completed the Java course! Now you have a solid foundation to tackle enterprise frameworks like Spring, Jakarta EE, or Quarkus.







