Unify Multi-Channel Login with Spring Boot Factory & Strategy Patterns
Learn how to replace tangled if‑else login code with a clean, extensible architecture using Spring Boot, combining the Factory pattern to instantiate strategy beans and the Strategy pattern to encapsulate each login method (password, WeChat, SMS, etc.), enabling easy addition of new authentication types.
1. Requirement Analysis
Projects often need to support many login methods (username/password, WeChat QR code, SMS verification, etc.). A naïve implementation puts all branches in a single if‑else block, leading to poor extensibility, mixed responsibilities, and duplicated validation logic.
Low extensibility : Adding a new method requires modifying the existing conditional chain, violating the Open‑Closed Principle.
Mixed responsibilities : All logic lives in one class, making the code hard to read and maintain.
Reuse difficulty : Common checks (e.g., user lock status) cannot be extracted cleanly.
2. Design Pattern Selection
To solve the above problems we combine two classic patterns:
Strategy pattern : Encapsulate each login algorithm in its own class that implements a common LoginStrategy interface.
Factory pattern : Provide a LoginStrategyFactory that creates the appropriate strategy instance based on the loginType supplied by the client.
Spring Boot’s auto‑wiring makes it easy to collect all @Service beans that implement LoginStrategy and build a map of loginType → bean.
3. Project Setup
Create a standard Spring Boot project with the spring-boot-starter-web dependency. The package layout is:
src/main/java/com/example/login
├── config
│ └── StrategyConfig.java // custom bean names
├── controller
│ └── LoginController.java
├── factory
│ └── LoginStrategyFactory.java
├── model
│ └── LoginRequest.java // loginType + params map
├── service
│ ├── impl
│ │ ├── PasswordLoginStrategy.java
│ │ ├── WechatLoginStrategy.java
│ │ └── SmsLoginStrategy.java
│ └── LoginStrategy.java // interface
└── Application.java4. Strategy Interface
public interface LoginStrategy {
// e.g. "password", "wechat", "sms"
String getLoginType();
// params contain the fields required by each concrete strategy
String execute(Map<String, Object> params);
}5. Concrete Strategies
5.1 Password Login
@Service
public class PasswordLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() { return "password"; }
@Override
public String execute(Map<String, Object> params) {
String username = (String) params.get("username");
String password = (String) params.get("password");
// Simulated password check
if (!"123456".equals(password)) {
throw new IllegalArgumentException("密码错误");
}
checkUserLocked(username);
return "登录成功(用户名密码)";
}
private void checkUserLocked(String username) {
System.out.println("检查用户" + username + "是否锁定");
}
}5.2 WeChat QR‑Code Login
@Service
public class WechatLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() { return "wechat"; }
@Override
public String execute(Map<String, Object> params) {
String authCode = (String) params.get("authCode");
String openId = callWechatApi(authCode);
String userId = getUserIdByOpenId(openId);
if (userId == null) {
throw new IllegalArgumentException("微信账号未绑定系统用户");
}
return "登录成功(微信扫码)";
}
private String callWechatApi(String authCode) {
System.out.println("调用微信接口,authCode=" + authCode);
return "wechat_open_id_123"; // mock
}
private String getUserIdByOpenId(String openId) {
// mock DB lookup
return "user123";
}
}5.3 SMS Verification Login
@Service
public class SmsLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() { return "sms"; }
@Override
public String execute(Map<String, Object> params) {
String phone = (String) params.get("phone");
String code = (String) params.get("code");
if (!"666888".equals(code)) {
throw new IllegalArgumentException("验证码错误");
}
checkPhoneRegistered(phone);
return "登录成功(手机号验证码)";
}
private void checkPhoneRegistered(String phone) {
System.out.println("检查手机号" + phone + "是否注册");
}
}6. Factory Implementation
@Component
public class LoginStrategyFactory {
private final Map<String, LoginStrategy> strategyMap;
public LoginStrategyFactory(Map<String, LoginStrategy> beans) {
// Build a map keyed by the value returned by getLoginType()
this.strategyMap = new HashMap<>();
beans.forEach((name, strategy) ->
this.strategyMap.put(strategy.getLoginType(), strategy));
}
public LoginStrategy getStrategy(String loginType) {
LoginStrategy strategy = strategyMap.get(loginType);
if (strategy == null) {
throw new IllegalArgumentException("不支持的登录类型:" + loginType);
}
return strategy;
}
}7. Controller – Unified Entry Point
@RestController
@RequestMapping("/login")
public class LoginController {
private final LoginStrategyFactory factory;
@Autowired
public LoginController(LoginStrategyFactory factory) {
this.factory = factory;
}
@PostMapping
public String login(@RequestBody LoginRequest request) {
String loginType = request.getLoginType();
Map<String, Object> params = request.getParams();
LoginStrategy strategy = factory.getStrategy(loginType);
return strategy.execute(params);
}
}
public class LoginRequest {
private String loginType; // e.g. "password", "wechat", "sms"
private Map<String, Object> params; // method‑specific parameters
// getters & setters omitted for brevity
}8. Testing the Endpoints
Three example JSON payloads illustrate how the same /login endpoint can handle different methods:
{
"loginType": "password",
"params": {"username": "user123", "password": "123456"}
}
{
"loginType": "wechat",
"params": {"authCode": "wechat_auth_code_456"}
}
{
"loginType": "sms",
"params": {"phone": "13800138000", "code": "666888"}
}9. Extending the System
Adding a new method (e.g., Alipay) only requires a new class that implements LoginStrategy and annotating it with @Service("alipayStrategy"). No changes to the factory or controller are needed.
@Service("alipayStrategy")
public class AlipayLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() { return "alipay"; }
@Override
public String execute(Map<String, Object> params) {
// Alipay specific logic
return "登录成功(支付宝)";
}
}10. Core Advantages
Strategy decouples algorithms : each login flow is isolated, improving readability.
Easy to extend : new authentication types follow the Open‑Closed Principle.
Testability : each strategy can be unit‑tested independently.
Factory hides creation details : callers only need the loginType string.
Spring auto‑wiring automatically registers all strategy beans.
11. Best Practices
Validate loginType and required parameters in the controller before delegating to a strategy.
Extract common logic (e.g., token generation) into an abstract base strategy class.
Log each login attempt and wrap strategy execution in a try‑catch block to convert technical exceptions into user‑friendly messages.
Store supported login types in configuration so the front‑end can query them dynamically.
12. Conclusion
By combining the Factory and Strategy patterns within a Spring Boot application, the login module becomes a plug‑in architecture that gracefully handles requirement changes. The code stays clean, maintainable, and ready for future authentication mechanisms without touching the core flow.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
