Skip to content

最佳实践 - 独立使用

使用 LCGYL Framework 独立构建应用的最佳实践。

项目结构

推荐的目录结构

my-app/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/app/
│   │   │       ├── Application.java          # 应用主类
│   │   │       ├── config/                   # 配置类
│   │   │       │   ├── DataSourceConfig.java
│   │   │       │   └── WebConfig.java
│   │   │       ├── controller/               # 控制器
│   │   │       │   └── UserController.java
│   │   │       ├── service/                  # 服务层
│   │   │       │   └── UserService.java
│   │   │       ├── repository/               # 数据访问层
│   │   │       │   └── UserRepository.java
│   │   │       ├── model/                    # 实体类
│   │   │       │   └── User.java
│   │   │       ├── event/                    # 事件
│   │   │       │   └── UserCreatedEvent.java
│   │   │       └── listener/                 # 事件监听器
│   │   │           └── UserEventListener.java
│   │   └── resources/
│   │       ├── application.properties        # 配置文件
│   │       ├── logback.xml                   # 日志配置
│   │       └── static/                       # 静态资源
│   └── test/
│       └── java/
│           └── com/example/app/
│               └── service/
│                   └── UserServiceTest.java
├── build.gradle                              # Gradle 配置
└── README.md

依赖注入

使用构造器注入

java
// ✅ 推荐:构造器注入
@Component
public class UserService {
    
    private final UserRepository userRepository;
    private final EventBus eventBus;
    
    @Inject
    public UserService(UserRepository userRepository, EventBus eventBus) {
        this.userRepository = userRepository;
        this.eventBus = eventBus;
    }
}

// ❌ 不推荐:字段注入
@Component
public class UserService {
    
    @Inject
    private UserRepository userRepository;
    
    @Inject
    private EventBus eventBus;
}

优点

  • 依赖明确
  • 易于测试
  • 支持 final 字段
  • 避免循环依赖

避免循环依赖

java
// ❌ 错误:循环依赖
@Component
public class ServiceA {
    @Inject
    private ServiceB serviceB;
}

@Component
public class ServiceB {
    @Inject
    private ServiceA serviceA;
}

// ✅ 正确:使用事件解耦
@Component
public class ServiceA {
    @Inject
    private EventBus eventBus;
    
    public void doSomething() {
        eventBus.publish(new SomeEvent());
    }
}

@Component
public class ServiceB {
    @EventListener
    public void onEvent(SomeEvent event) {
        // 处理事件
    }
}

配置管理

配置文件组织

resources/
├── application.properties           # 通用配置
├── application-dev.properties       # 开发环境
├── application-test.properties      # 测试环境
└── application-prod.properties      # 生产环境

使用环境变量

properties
# application.properties
datasource.url=${DB_URL:jdbc:mysql://localhost:3306/db}
datasource.username=${DB_USERNAME:root}
datasource.password=${DB_PASSWORD:password}

app.name=${APP_NAME:My Application}
app.version=${APP_VERSION:1.0.0}

配置类型安全

java
// ✅ 推荐:使用配置类
@ConfigurationProperties(prefix = "datasource")
public class DataSourceConfig {
    private String url;
    private String username;
    private String password;
    private int maxPoolSize = 20;
    
    // Getters and Setters
}

// 使用
@Component
public class DataSourceProvider {
    
    @Inject
    private DataSourceConfig config;
    
    public DataSource dataSource() {
        return DataSourceBuilder.create()
            .url(config.getUrl())
            .username(config.getUsername())
            .password(config.getPassword())
            .maxPoolSize(config.getMaxPoolSize())
            .build();
    }
}

异常处理

统一异常处理

java
@Component
public class GlobalExceptionHandler {
    
    @ExceptionHandler(NotFoundException.class)
    public ErrorResponse handleNotFound(NotFoundException e) {
        return ErrorResponse.builder()
            .status(404)
            .message(e.getMessage())
            .build();
    }
    
    @ExceptionHandler(BusinessException.class)
    public ErrorResponse handleBusiness(BusinessException e) {
        return ErrorResponse.builder()
            .status(400)
            .message(e.getMessage())
            .code(e.getCode())
            .build();
    }
    
    @ExceptionHandler(Exception.class)
    public ErrorResponse handleGeneral(Exception e) {
        logger.error("未处理的异常", e);
        return ErrorResponse.builder()
            .status(500)
            .message("服务器内部错误")
            .build();
    }
}

自定义异常

java
public class BusinessException extends RuntimeException {
    private final String code;
    
    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }
}

public class NotFoundException extends BusinessException {
    public NotFoundException(String message) {
        super("NOT_FOUND", message);
    }
}

事务管理

事务最佳实践

java
// ✅ 推荐:事务方法简洁
@Component
public class UserService {
    
    @Transactional
    public User createUser(User user) {
        // 只包含数据库操作
        return userRepository.save(user);
    }
    
    public void registerUser(User user) {
        // 非事务操作
        validateUser(user);
        
        // 事务操作
        User savedUser = createUser(user);
        
        // 非事务操作
        sendWelcomeEmail(savedUser);
    }
}

// ❌ 不推荐:事务方法包含非数据库操作
@Component
public class UserService {
    
    @Transactional
    public User registerUser(User user) {
        validateUser(user);           // 不需要事务
        User savedUser = userRepository.save(user);
        sendWelcomeEmail(savedUser);  // 不需要事务
        return savedUser;
    }
}

事务传播

java
@Component
public class OrderService {
    
    @Transactional
    public void createOrder(Order order) {
        orderRepository.save(order);
        
        // 独立事务,不影响主事务
        logService.log("订单已创建");
    }
}

@Component
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String message) {
        logRepository.save(new Log(message));
    }
}

性能优化

使用缓存

java
@Component
public class UserService {
    
    private final Cache<Long, User> cache = CacheBuilder.newBuilder()
        .maximumSize(1000)
        .expireAfterWrite(10, TimeUnit.MINUTES)
        .build();
    
    public User findById(Long id) {
        return cache.get(id, () -> userRepository.findById(id)
            .orElseThrow(() -> new NotFoundException("用户不存在"))
        );
    }
    
    @Transactional
    public User update(User user) {
        User updated = userRepository.save(user);
        cache.put(user.getId(), updated);
        return updated;
    }
}

批量操作

java
// ❌ 不推荐:循环查询
for (Long id : ids) {
    User user = userRepository.findById(id);
    process(user);
}

// ✅ 推荐:批量查询
List<User> users = userRepository.findByIds(ids);
for (User user : users) {
    process(user);
}

异步处理

java
@Component
public class OrderService {
    
    @Inject
    private EventBus eventBus;
    
    @Transactional
    public Order createOrder(Order order) {
        Order saved = orderRepository.save(order);
        
        // 异步发送通知
        eventBus.publishAsync(new OrderCreatedEvent(saved));
        
        return saved;
    }
}

@Component
public class NotificationService {
    
    @EventListener(async = true)
    public void onOrderCreated(OrderCreatedEvent event) {
        // 异步发送邮件
        sendEmail(event.order());
    }
}

日志记录

日志级别

java
@Component
public class UserService {
    
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    
    public User findById(Long id) {
        logger.debug("查找用户: {}", id);
        
        User user = userRepository.findById(id)
            .orElseThrow(() -> {
                logger.warn("用户不存在: {}", id);
                return new NotFoundException("用户不存在");
            });
        
        logger.info("找到用户: {}", user.getUsername());
        return user;
    }
    
    public void processOrder(Order order) {
        try {
            // 处理订单
            logger.info("处理订单: {}", order.getId());
        } catch (Exception e) {
            logger.error("处理订单失败: {}", order.getId(), e);
            throw e;
        }
    }
}

日志配置

xml
<!-- logback.xml -->
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/app.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>
    
    <logger name="com.example.app" level="DEBUG" />
</configuration>

测试

单元测试

java
public class UserServiceTest {
    
    private UserService userService;
    private UserRepository userRepository;
    
    @BeforeMethod
    public void setUp() {
        userRepository = mock(UserRepository.class);
        userService = new UserService(userRepository);
    }
    
    @Test
    public void testFindById() {
        // Given
        User user = new User(1L, "John", "john@example.com");
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));
        
        // When
        User found = userService.findById(1L);
        
        // Then
        assertEquals(found.getId(), 1L);
        assertEquals(found.getName(), "John");
    }
}

集成测试

java
public class UserServiceIntegrationTest {
    
    private LcgylApplication app;
    private UserService userService;
    
    @BeforeClass
    public void setUp() {
        app = LcgylApplication.create(TestApplication.class);
        app.loadPlugin("lcgyl-cache-plugin");
        app.start();
        
        userService = app.getContainer().get(UserService.class);
    }
    
    @AfterClass
    public void tearDown() {
        app.stop();
    }
    
    @Test
    public void testCreateUser() {
        User user = new User();
        user.setName("John");
        user.setEmail("john@example.com");
        
        User created = userService.save(user);
        
        assertNotNull(created.getId());
        assertEquals(created.getName(), "John");
    }
}

安全

密码加密

java
@Component
public class UserService {
    
    @Inject
    private PasswordEncoder passwordEncoder;
    
    public void register(String username, String password) {
        // 加密密码
        String encodedPassword = passwordEncoder.encode(password);
        
        User user = new User();
        user.setUsername(username);
        user.setPassword(encodedPassword);
        
        userRepository.save(user);
    }
    
    public boolean login(String username, String password) {
        User user = userRepository.findByUsername(username)
            .orElseThrow(() -> new BusinessException("用户不存在"));
        
        // 验证密码
        return passwordEncoder.matches(password, user.getPassword());
    }
}

SQL 注入防护

java
// ✅ 推荐:参数化查询
String sql = "SELECT * FROM users WHERE username = ?";
User user = jdbcTemplate.queryForObject(sql, User.class, username);

// ❌ 危险:字符串拼接
String sql = "SELECT * FROM users WHERE username = '" + username + "'";
User user = jdbcTemplate.queryForObject(sql, User.class);

资源管理

优雅关闭

java
public class Application {
    
    public static void main(String[] args) {
        LcgylApplication app = LcgylApplication.create(Application.class);
        
        // 注册关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            logger.info("应用正在关闭...");
            app.stop();
            logger.info("应用已关闭");
        }));
        
        app.start();
        app.startWebServer(8080);
    }
}

连接池管理

java
@Component
public class DataSourceConfig {
    
    public DataSource dataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:mysql://localhost:3306/db")
            .username("root")
            .password("password")
            // 连接池配置
            .maxPoolSize(20)
            .minPoolSize(5)
            .maxWait(30000)
            .testQuery("SELECT 1")
            .build();
    }
}

下一步

Released under the Apache License 2.0