《Clean Architecture》架构下,非Spring环境如何创建包级访问修饰符服务?
Great question! Let's break this down step by step—since you're aiming to keep that tight encapsulation pattern Simon Brown outlines in Clean Architecture (hiding implementation classes behind interfaces, with package-level access on implementations), we'll cover both Spring Java Config solutions and a fully Spring-free approach.
First: Fixing the Spring Java Config Issue
The problem with your initial AppConfig is that OrderServiceImpl has package-level access, so if your config class lives outside the com.my.service.impl package, it can't directly instantiate the class. Here are two clean ways to resolve this:
1. Move the Configuration Class to the Implementation Package
The simplest, most idiomatic fix is to place your @Configuration class in the same package as your implementation. This lets you legally access the package-level OrderServiceImpl:
// File: com/my/service/impl/AppConfig.java @Configuration public class AppConfig { @Bean OrderService orderService(){ return new OrderServiceImpl(); // No access issues now—same package! } }
This preserves the encapsulation: external code only ever references the OrderService interface, and the implementation stays hidden.
2. Use Reflection (Last Resort)
If you can't move the config class (for project structure reasons), you can use reflection to bypass the access modifier. Note: this breaks the spirit of encapsulation, so only use this if you have no other option:
@Configuration public class AppConfig { @Bean OrderService orderService() throws Exception { // Get the package-level constructor Constructor<OrderServiceImpl> constructor = OrderServiceImpl.class.getDeclaredConstructor(); // Disable access checks temporarily constructor.setAccessible(true); return constructor.newInstance(); } }
Fully Spring-Free Implementation
To replicate this pattern without any Spring dependencies, the key is to keep instance creation logic within the same package as your implementation class, so it can access the package-level OrderServiceImpl. Here are two solid approaches:
1. Simple Manual DI Container
Create a container class inside the impl package that handles instantiation, and exposes only the interface to the outside:
// File: com/my/service/impl/ServiceContainer.java public class ServiceContainer { private final OrderService orderService; public ServiceContainer() { this.orderService = new OrderServiceImpl(); } // Expose only the interface public OrderService getOrderService() { return orderService; } }
Then use it in your application code:
public class App { public static void main(String[] args) { ServiceContainer container = new ServiceContainer(); OrderService orderService = container.getOrderService(); // Use orderService as needed—no reference to OrderServiceImpl anywhere! } }
2. Factory Pattern (Cleaner for Scalability)
Define a factory interface in your public service package, then implement it as a package-level class in impl. Add a static helper method to the interface to get the factory:
// File: com/my/service/OrderService.java public interface OrderService { List<Order> getOrders(); // Static factory accessor—no external dependencies static OrderService createInstance() { return new DefaultOrderServiceFactory().createOrderService(); } // Internal factory interface interface OrderServiceFactory { OrderService createOrderService(); } } // File: com/my/service/impl/DefaultOrderServiceFactory.java // Package-level access—only visible to same package class DefaultOrderServiceFactory implements OrderService.OrderServiceFactory { @Override public OrderService createOrderService() { return new OrderServiceImpl(); } }
Now external code can get an instance without ever touching the implementation:
OrderService orderService = OrderService.createInstance();
All these approaches maintain the core idea from Simon Brown's chapter: hiding implementation details behind interfaces, with package-level access modifiers enforcing that no external code can depend on concrete classes.
内容的提问来源于stack exchange,提问作者Pavel Petrashov

