IoC / DI 实操

发布于:2025-09-14 ⋅ 阅读:(23) ⋅ 点赞:(0)

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