整理一下重要的前置知识。
1 SpringBoot的注解
1.1 Java的注解
内置注解
@Override
@Deprecated
@SuppressWarnings
主要是给编译器用的,好像用处也不是很大。
元注解
就是注解的注解
@Target
@Retention
@Documented
@Inherited
@Repeatable (Java 8+)
@NonNull
自定义注解
AnnotationProcessor.java
import java.lang.reflect.Method;
public class AnnotationProcessor {
public static void processAnnotations(Object obj) {
Class<?> clazz = obj.getClass();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Loggable.class)) {
Loggable loggable = method.getAnnotation(Loggable.class);
String description = loggable.value().isEmpty()
? method.getName() : loggable.value();
Loggable.LogLevel level = loggable.level();
boolean trackTime = loggable.trackTime();
System.out.printf("[%s] 开始执行: %s%n", level, description);
long startTime = System.currentTimeMillis();
try {
method.invoke(obj);
} catch (Exception e) {
System.err.printf("[ERROR] 方法执行失败: %s%n", e.getCause());
}
if (trackTime) {
long duration = System.currentTimeMillis() - startTime;
System.out.printf("[%s] 执行完成, 耗时: %dms%n", level, duration);
} else {
System.out.printf("[%s] 执行完成%n", level);
}
}
}
}
public static void main(String[] args) {
BusinessService service = new BusinessService();
processAnnotations(service);
}
}
BusinessService.java
public class BusinessService {
@Loggable("执行用户查询操作")
public void queryUsers() {
System.out.println("查询用户数据...");
}
@Loggable(level = Loggable.LogLevel.WARN, trackTime = true) // 修正这里
public void processOrder() {
System.out.println("处理订单中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Loggable(value = "危险操作", level = Loggable.LogLevel.ERROR) // 修正这里
public void dangerousOperation() {
System.out.println("执行危险操作...");
}
}
Loggable.java
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Loggable {
String value() default "";
LogLevel level() default LogLevel.INFO;
boolean trackTime() default false;
// 将枚举定义在注解内部
enum LogLevel {
DEBUG, INFO, WARN, ERROR
}
}
编译:
javac Loggable.java BusinessService.java AnnotationProcessor.java
运行:
hp@DESKTOP-430500P:~/test$ java AnnotationProcessor
[INFO] 开始执行: 执行用户查询操作
查询用户数据...
[INFO] 执行完成
[WARN] 开始执行: processOrder
处理订单中...
[WARN] 执行完成, 耗时: 1000ms
[ERROR] 开始执行: 危险操作
执行危险操作...
[ERROR] 执行完成
这里可以看出,一个自定义注解的流程。
1 在@interface中声明要处理的注解。这里要用@Target和@Retentio做限定。
2 在AnnotationProcessor的processAnnotations中处理注解的具体流程。
3 在要使用的地方增加注解,类似这样@Loggable。。。
简化流程:
// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecution {
String value() default "";
}
// 使用注解
class Service {
@LogExecution("耗时监控")
public void process() { /*...*/ }
}
// 处理注解(需手动通过反射)
Method method = Service.class.getMethod("process");
if (method.isAnnotationPresent(LogExecution.class)) {
LogExecution anno = method.getAnnotation(LogExecution.class);
System.out.println("注解值: " + anno.value());
}
最后,感觉Java的注解和python的装饰器真的挺类似的。
# 定义装饰器
def log_execution(desc=""):
def decorator(func):
def wrapper(*args, **kwargs):
print(f"开始执行: {desc}")
result = func(*args, **kwargs)
print("执行结束")
return result
return wrapper
return decorator
# 使用装饰器(直接生效)
@log_execution("耗时监控")
def process():
print("正在处理...")
# 调用时自动执行装饰逻辑
process()
从使用上来,唯一的区别就是Java需要手动处理反射,在那个位置处理注解的流程。Python是直接在装饰器里面。
特性 | Java 注解 | Python 装饰器 |
---|---|---|
本质 | 元数据(附加信息) | 高阶函数(可执行代码) |
运行时行为 | 被动(需要反射/APT处理) | 主动(直接修改函数/类行为) |
实现方式 | 通过 @interface 定义 | 通过普通函数实现 |
作用阶段 | 编译时/运行时 | 代码加载时立即执行 |
1.2 Srpingboot的注解
类型 | 常用注解 |
---|---|
启动入口 | @SpringBootApplication |
定义组件 | @Component , @Service , @Repository |
配置管理 | @Configuration , @Bean , @Value |
Web 控制器 | @RestController , @RequestMapping , @GetMapping , @RequestBody |
调度与异步 | @Scheduled , @Async , @EnableScheduling |
测试注解 | @SpringBootTest , @WebMvcTest |
2 Bean
通过上面的注解,可以将类直接包装成Bean。
这个Bean主要用在一个服务中使用另一个类的服务,其实不用也可以。
// EmailService 实现
public class EmailService {
public void sendEmail(String to, String content) {
System.out.println("Sending email to " + to + ": " + content);
// 实际发送邮件的逻辑
}
}
// UserService 实现
public class UserService {
private EmailService emailService;
// 需要手动创建依赖
public UserService() {
this.emailService = new EmailService();
}
public void registerUser(String username) {
System.out.println("Registering user: " + username);
emailService.sendEmail(username, "Welcome to our platform!");
}
}
// 使用方式
public class Application {
public static void main(String[] args) {
// 需要手动创建和管理所有对象
UserService userService = new UserService();
userService.registerUser("test@example.com");
}
}
使用
// EmailService 作为 Bean
@Component
public class EmailService {
public void sendEmail(String to, String content) {
System.out.println("Sending email to " + to + ": " + content);
}
}
// UserService 作为 Bean 并自动注入 EmailService
@Service
public class UserService {
private final EmailService emailService;
// 通过构造器自动注入
@Autowired
public UserService(EmailService emailService) {
this.emailService = emailService;
}
public void registerUser(String username) {
System.out.println("Registering user: " + username);
emailService.sendEmail(username, "Welcome to our platform!");
}
}
// 配置类
@Configuration
@ComponentScan
public class AppConfig {
}
// 使用方式
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.registerUser("test@example.com");
}
}
在类的前面加一个@Component/@Service/@Controller/@Repository...就可以标注为bean。
@Autowired实现自动注入。
看起来就是将一些服务类集中管理了,需要的时候直接用单例方式传入。不用再自己去new了。。。
3 面向切面编程AOP
一个例子,请求拦截器。
public class AuthInterceptor implements HandlerInterceptor {
// 在Controller方法执行前调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (!validateToken(token)) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), "无效的token");
return false; // 中断请求
}
return true; // 继续执行
}
// 在Controller方法执行后,视图渲染前调用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// 可以修改ModelAndView
}
// 在整个请求完成后调用
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 可以进行资源清理
}
}
在这里注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthInterceptor())
.addPathPatterns("/api/**") // 拦截路径
.excludePathPatterns("/api/login"); // 排除路径
}
}
可以在这个切面上针对具体场景进行控制,就是访问api接口时,/api/**。大概是这个意思吧。
当然,除此之外的场景还有很多,比如日志,性能,事务等等。
4 DAO
也就是(Data Access Object)。说真的,看了下感觉这个感觉没啥特别的。
有个接口:
public interface UserDao {
// 添加用户
void addUser(User user);
}
有个实现:
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UserDaoImpl implements UserDao {
// 假设使用JDBC连接
private Connection getConnection() throws SQLException {
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb",
"username",
"password");
}
@Override
public void addUser(User user) {
String sql = "INSERT INTO users(username, email) VALUES(?, ?)";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, user.getUsername());
ps.setString(2, user.getEmail());
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
用的时候直接:
public class Main {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
// 添加用户
User newUser = new User();
newUser.setUsername("john_doe");
newUser.setEmail("john@example.com");
userDao.addUser(newUser);
}
就是将数据库操作做了一个封装。然后就说是DAO。。。Are you kidding me?
5 事务
就是@Transactional关键字,这个是spring框架所提供。在springboot中做了增强。
例子
@Service
public class BankService {
@Autowired
private AccountRepository accountRepository;
// 最简单的转账事务方法
@Transactional
public void transferMoney(Long fromId, Long toId, Double amount) {
// 转出账户扣款
accountRepository.updateBalance(fromId, -amount);
// 转入账户加款
accountRepository.updateBalance(toId, amount);
}
}
增加Transactional注解后,如果第二个update失败,第一个update也会跟着回滚。
6 WebFlux
更高性能的API处理架构。。。这个有时间再看吧,估计一下也用不到。