1. 建三层类
包结构:
com.lib
├─ config
├─ controller
├─ service
├─ repository
├─ model
└─ annotation // 自定义限定符
① 实体 Book
package com.lib.model;
public class Book {
private Integer id;
private String title;
// 全参构造 + getter/setter/toString 省略
}
② Repository 接口
package com.lib.repository;
public interface BookRepository {
Book findById(int id);
}
③ Repository 实现
package com.lib.repository;
@Repository
public class InMemoryBookRepository implements BookRepository {
private Map<Integer, Book> store = new ConcurrentHashMap<>();
public InMemoryBookRepository() {
store.put(1, new Book(1, "Spring in Action"));
}
@Override
public Book findById(int id) {
return store.get(id);
}
}
④ Service
package com.lib.service;
@Service
public class BookService {
private final BookRepository repo; // ① 构造器注入
@Autowired
public BookService(BookRepository repo) {
this.repo = repo;
}
public Book query(int id) {
return repo.findById(id);
}
}
⑤ Controller
package com.lib.controller;
@Controller
@RequestMapping("/book")
public class BookController {
private BookService bookService; // ② 字段注入(演示用)
@Autowired
public void setBookService(BookService bookService) {
this.bookService = bookService; // ③ Setter 注入
}
@GetMapping("/{id}")
@ResponseBody
public Book get(@PathVariable int id) {
return bookService.query(id);
}
}
2. 三种注入方式对比
方式 | 写法 | 备注 | |
---|---|---|---|
构造器 | @Autowired 全参构造 |
不可变、易测试 | |
Setter | @Autowired setXxx |
允许后期重配置 | |
字段 | 直接 @Autowired |
单元测试需反射 |
验证:
浏览器访问 http://localhost:8080/library/book/1
→ 返回 JSON
{"id":1,"title":"Spring in Action"}
即注入成功。
3. Bean 作用域实验
在 InMemoryBookRepository
类上加:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
写测试控制器:
@GetMapping("/repo")
@ResponseBody
public String repoScope() {
return "repo hash=" + bookService.getRepoHash(); // 在 Service 里返回 repo.toString()
}
连续刷新页面:
singleton(默认)→ hash 不变
prototype → 每次 hash 变化
4. 生命周期回调(30 min)
① 让 Repository 实现 2 个接口
@Repository
public class InMemoryBookRepository implements BookRepository,
InitializingBean, DisposableBean {
@PostConstruct
public void init() {
System.out.println("[@PostConstruct] repo init, store size=" + store.size());
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("[InitializingBean] afterPropertiesSet");
}
@PreDestroy
public void preDestroy() {
System.out.println("[@PreDestroy] repo will destroy");
}
@Override
public void destroy() throws Exception {
System.out.println("[DisposableBean] destroy");
}
}
② 启动/关闭 Tomcat,观察控制台顺序:
[@PostConstruct] repo init...
[InitializingBean] afterPropertiesSet...
...
[@PreDestroy] repo will destroy...
[DisposableBean] destroy...
截图保存,生命周期七连击完成。
5. Environment & 外部化配置
① 新建 app.properties
放 classpath
library.default.book.id=99
library.default.book.title=Default Book
② 配置类
@Configuration
@PropertySource("classpath:app.properties")
public class AppConfig {
@Bean
public Book defaultBook(Environment env) {
return new Book(
env.getProperty("library.default.book.id", Integer.class),
env.getProperty("library.default.book.title")
);
}
}
③ 验证
@Autowired
private Book defaultBook; // 字段注入,仅演示
@GetMapping("/default")
@ResponseBody
public Book def() {
return defaultBook; // {"id":99,"title":"Default Book"}
}
8. 常见错误速查
异常 | 原因 | 解决 |
---|---|---|
No qualifying bean of type 'BookRepository' |
忘记 @Repository |
加注解或 @ComponentScan |
Bean instantiation failed: No default constructor |
自己写了带参构造却未 @Autowired |
补上构造器 @Autowired |
@PreDestroy not called |
外部 Tomcat 强杀 | 用 catalina stop 温和关闭 |