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 温和关闭 |