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"));}@Overridepublic Book findById(int id) {return store.get(id);}
}
④ Service
package com.lib.service;
@Service
public class BookService {private final BookRepository repo; // ① 構造器注入@Autowiredpublic 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; // ② 字段注入(演示用)@Autowiredpublic void setBookService(BookService bookService) {this.bookService = bookService; // ③ Setter 注入}@GetMapping("/{id}")@ResponseBodypublic 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 {@PostConstructpublic void init() {System.out.println("[@PostConstruct] repo init, store size=" + store.size());}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("[InitializingBean] afterPropertiesSet");}@PreDestroypublic void preDestroy() {System.out.println("[@PreDestroy] repo will destroy");}@Overridepublic 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 {@Beanpublic 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 ?溫和關閉 |