掌握Spring框架這十個擴展點,開發(fā)效率直接翻倍!
兄弟們,大家真的把Spring 框架用明白了嗎?很多人只停留在@Controller、@Service這些基礎(chǔ)注解上,卻不知道 Spring 藏著很多 "黑科技"—— 那些能讓你少寫千行代碼、輕松搞定復(fù)雜需求的擴展點。今天就帶你深挖 Spring 最實用的 10 個擴展點,看完這篇文章,同事看你代碼的眼神都會不一樣!
1. 全局異常處理:@RestControllerAdvice 拯救重復(fù)代碼
先說個扎心的場景:你辛辛苦苦寫了十個接口,每個接口都要寫一堆try-catch處理異常,就為了返回統(tǒng)一格式的錯誤信息。老板路過看了眼代碼:"小伙子,代碼不夠精煉??!" 你心里直呼冤枉,但又確實沒轍 —— 直到發(fā)現(xiàn)了@RestControllerAdvice這個神器。
這個擴展點本質(zhì)上是個增強版的@Component,專門用來處理全局異常。它能像漁網(wǎng)一樣捕獲整個應(yīng)用里拋出的異常,讓你從重復(fù)的異常處理代碼中解放出來。
@RestControllerAdvice
public class GlobalExceptionHandler {
// 處理算術(shù)異常(比如除零錯誤)
@ExceptionHandler(ArithmeticException.class)
public ResultDTO handleArithmeticException(ArithmeticException e) {
return ResultDTO.fail("數(shù)學(xué)不好別亂除:" + e.getMessage());
}
// 處理空指針異常
@ExceptionHandler(NullPointerException.class)
public ResultDTO handleNullPointerException(NullPointerException e) {
return ResultDTO.fail("對象為空啦:" + e.getMessage());
}
// 處理所有未捕獲的異常
@ExceptionHandler(Exception.class)
public ResultDTO handleException(Exception e) {
return ResultDTO.fail("系統(tǒng)開小差了:" + e.getMessage());
}
}用了這個擴展點后,你的業(yè)務(wù)代碼可以徹底告別try-catch:
@GetMapping("/divide")
public int divide(int a, int b) {
// 這里只管業(yè)務(wù)邏輯,異常交給全局處理器
return a / b;
}當(dāng)用戶訪問/divide?a=10&b=0時,會自動返回:
{
"code": 500,
"msg": "數(shù)學(xué)不好別亂除:/ by zero",
"data": null
}核心價值:把異常處理代碼從業(yè)務(wù)邏輯中剝離,實現(xiàn) "一處定義,處處生效",代碼量直降 30%,維護成本大大降低。記住,優(yōu)秀的程序員懂得讓代碼各司其職。
2. 自定義攔截器:HandlerInterceptor 控制請求生死權(quán)
你有沒有遇到過這些需求:記錄接口響應(yīng)時間、驗證用戶登錄狀態(tài)、打印請求日志?如果每個接口都手動實現(xiàn)這些功能,怕是要寫到手軟。這時HandlerInterceptor攔截器就該登場了,它就像小區(qū)門口的保安,能在請求到達接口前、接口執(zhí)行后甚至請求完成時做一些通用處理。
實現(xiàn)攔截器分兩步:首先定義攔截器邏輯,然后注冊它。
// 1. 定義攔截器
public class LogInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(LogInterceptor.class);
private ThreadLocal<Long> startTime = new ThreadLocal<>();
// 請求處理前調(diào)用(返回false則阻止請求繼續(xù))
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
startTime.set(System.currentTimeMillis());
log.info("請求開始:{} {}", request.getMethod(), request.getRequestURI());
// 這里可以做登錄驗證,比如:
if (request.getSession().getAttribute("user") == null) {
response.setStatus(401);
return false;
}
return true;
}
// 接口執(zhí)行后調(diào)用(還沒返回給客戶端)
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) {
log.info("接口執(zhí)行完成:{}", request.getRequestURI());
}
// 整個請求完成后調(diào)用(包括視圖渲染)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
long cost = System.currentTimeMillis() - startTime.get();
log.info("請求結(jié)束:{},耗時:{}ms", request.getRequestURI(), cost);
startTime.remove(); // 清理ThreadLocal,避免內(nèi)存泄漏
}
}
// 2. 注冊攔截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/**") // 攔截所有路徑
.excludePathPatterns("/login", "/register"); // 排除登錄注冊接口
}
}這個攔截器能幫你完成:
- 接口訪問日志記錄
- 接口響應(yīng)時間統(tǒng)計
- 登錄狀態(tài)驗證
- 接口訪問頻率限制
避坑指南:攔截器是單例的,千萬別在里面定義成員變量存儲請求相關(guān)數(shù)據(jù),要用ThreadLocal來保證線程安全。另外注意攔截順序,多個攔截器的執(zhí)行順序可以通過@Order注解控制。
3. 容器對象獲?。篈pplicationContextAware 拿 Bean 不求人
有沒有過這種經(jīng)歷:在非 Spring 管理的類里想調(diào)用 Spring 的 Bean,結(jié)果翻遍全網(wǎng)都找不到好辦法?比如在工具類里需要用到UserService,但工具類又沒法用@Autowired注入。這時候ApplicationContextAware就是你的救星,它能讓你直接拿到 Spring 容器,想要什么 Bean 自己拿!
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
// Spring會自動調(diào)用這個方法,把容器對象傳進來
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
applicationContext = context;
}
// 獲取容器中的Bean
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
// 根據(jù)名字獲取Bean
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
// 判斷Bean是否存在
public static boolean containsBean(String name) {
return applicationContext.containsBean(name);
}
}有了這個工具類,隨時隨地獲取 Bean:
// 在普通工具類中使用Spring的Bean
public class UserUtil {
public static User getUserById(Long id) {
// 直接從容器拿UserService
UserService userService = SpringContextHolder.getBean(UserService.class);
return userService.getById(id);
}
}原理揭秘:Spring 在初始化 Bean 時,會檢查 Bean 是否實現(xiàn)了Aware接口家族,如果實現(xiàn)了,就會調(diào)用對應(yīng)的setXxx方法注入相關(guān)對象。除了ApplicationContextAware,常用的還有:
- BeanNameAware:獲取 Bean 在容器中的名字
- BeanFactoryAware:獲取 BeanFactory 對象
- EnvironmentAware:獲取環(huán)境配置對象
注意事項:這個工具類一定要交給 Spring 管理(加@Component),否則 Spring 不會調(diào)用setApplicationContext方法。另外盡量避免濫用,能通過依賴注入解決的就不要直接拿容器。
4. 啟動初始化:CommandLineRunner 與 ApplicationRunner 預(yù)熱系統(tǒng)
你負責(zé)的項目有沒有這種需求:應(yīng)用啟動時需要加載字典數(shù)據(jù)到緩存、初始化數(shù)據(jù)庫連接池、或者發(fā)送服務(wù)啟動通知?總不能每次部署后手動執(zhí)行吧?Spring 提供了兩個專門用于啟動初始化的擴展點:CommandLineRunner和ApplicationRunner。
這哥倆功能差不多,區(qū)別在于參數(shù)類型:
// CommandLineRunner接收原始字符串?dāng)?shù)組參數(shù)
@Component
@Order(1) // 多個初始化器可以指定順序
public class CacheInitializer implements CommandLineRunner {
@Autowired
private DictService dictService;
@Override
public void run(String... args) throws Exception {
log.info("開始加載字典緩存...");
dictService.loadDictToCache();
log.info("字典緩存加載完成!");
}
}
// ApplicationRunner接收包裝后的ApplicationArguments
@Component
@Order(2)
public class ConfigPrinter implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("應(yīng)用啟動參數(shù):{}", args.getOptionNames());
log.info("非選項參數(shù):{}", args.getNonOptionArgs());
}
}當(dāng)應(yīng)用啟動時,Spring 會自動調(diào)用這些實現(xiàn)類的run方法,而且支持通過@Order注解控制執(zhí)行順序(數(shù)字越小越先執(zhí)行)。實際應(yīng)用場景:
- 加載熱點數(shù)據(jù)到本地緩存,提高首次訪問速度
- 檢查數(shù)據(jù)庫表結(jié)構(gòu)是否存在,自動創(chuàng)建缺失的表
- 注冊本服務(wù)到注冊中心
- 發(fā)送應(yīng)用啟動成功的消息通知
小貼士:如果初始化邏輯可能耗時很長,可以考慮用@Async注解讓它異步執(zhí)行,避免阻塞應(yīng)用啟動。但要注意,異步初始化的邏輯不能是應(yīng)用啟動的必要條件。
5. 上下文初始化:ApplicationContextInitializer 預(yù)先配置環(huán)境
這個擴展點比較特殊,它的執(zhí)行時機非常早 —— 在 Spring 容器刷新之前。想象一下,你需要在應(yīng)用啟動初期就設(shè)置一些環(huán)境變量,或者動態(tài)注冊一些配置源,這時候ApplicationContextInitializer就能派上用場了。
它就像裝修房子前的設(shè)計規(guī)劃,在正式施工(容器刷新)前做好準(zhǔn)備工作:
public class CustomContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// 設(shè)置環(huán)境變量
ConfigurableEnvironment env = applicationContext.getEnvironment();
env.setActiveProfiles("prod"); // 強制激活生產(chǎn)環(huán)境配置
// 添加自定義配置源
Map<String, Object> customProps = new HashMap<>();
customProps.put("app.name", "my-awesome-app");
customProps.put("app.version", "1.0.0");
env.getPropertySources().addFirst(new MapPropertySource("customProps", customProps));
log.info("自定義上下文初始化完成,已添加自定義配置");
}
}注冊這個初始化器有三種方式:
- 在spring.factories中配置:
org.springframework.context.ApplicationContextInitializer=com.example.CustomContextInitializer- 在 SpringApplication 中添加:
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MyApp.class);
app.addInitializers(new CustomContextInitializer());
app.run(args);
}- 在 application.properties 中配置:
context.initializer.classes=com.example.CustomContextInitializer典型用途:
- 多環(huán)境動態(tài)配置切換
- 從外部服務(wù)獲取配置信息
- 注冊加密配置解碼器
- 容器啟動前的檢查工作
執(zhí)行時機揭秘:這個擴展點在 Spring Boot 啟動流程中位于prepareContext階段,早于BeanDefinition的加載,所以不能在這里獲取 Bean,只能做一些環(huán)境配置相關(guān)的工作。
6. Bean 后置處理器:BeanPostProcessor 定制 Bean 的 "成長過程"
如果說 Spring 容器是個 "育兒園",那么BeanPostProcessor就是最敬業(yè)的 "育兒師"—— 它能在 Bean 初始化前后做一些加工處理。不管是給 Bean 添加屬性,還是替換 Bean 實例,甚至是給 Bean 做代理增強,它都能搞定。
這個接口有兩個核心方法:
@Component
publicclass LogBeanPostProcessor implements BeanPostProcessor {
// Bean初始化方法調(diào)用前執(zhí)行
@Override
publicObject postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 給所有Service接口添加日志標(biāo)記
if (bean.getClass().getName().endsWith("Service")) {
log.info("Bean初始化前:{}", beanName);
// 可以在這里給Bean設(shè)置額外屬性
if (bean instanceof UserService) {
((UserService) bean).setLogEnabled(true);
}
}
return bean; // 返回的對象會作為最終的Bean
}
// Bean初始化方法調(diào)用后執(zhí)行
@Override
publicObject postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 對Controller進行特殊處理
if (bean.getClass().isAnnotationPresent(Controller.class)) {
log.info("Bean初始化后:{}", beanName);
// 這里可以返回代理對象,實現(xiàn)AOP功能
}
return bean;
}
}神奇用途:
- 自動為 Bean 注入通用屬性
- 實現(xiàn) Bean 的動態(tài)代理(Spring AOP 就是這么玩的)
- 檢查 Bean 是否符合特定規(guī)范
- 實現(xiàn)自定義注解功能
原理深挖:Spring 內(nèi)部有很多內(nèi)置的BeanPostProcessor,比如:
- AutowiredAnnotationBeanPostProcessor:處理@Autowired注解的依賴注入
- AnnotationAwareAspectJAutoProxyCreator:創(chuàng)建 AOP 代理對象
- InitDestroyAnnotationBeanPostProcessor:處理@PostConstruct和@PreDestroy注解
注意事項:BeanPostProcessor本身也是 Bean,所以不要在它里面依賴其他 Bean,可能會導(dǎo)致循環(huán)依賴或初始化順序問題。如果需要多個后置處理器,可以通過Ordered接口指定順序。
7. Bean 定義后置處理器:BeanFactoryPostProcessor 修改 Bean 藍圖
你知道 Spring 是怎么讀取@Configuration和@Bean注解的嗎?秘密就藏在BeanFactoryPostProcessor里。這個擴展點能讓你在 Bean 實例化之前修改 Bean 的定義信息,比如修改屬性值、添加新的 Bean 定義,甚至替換已有的 Bean 定義。
它就像修改建筑藍圖,在施工前調(diào)整設(shè)計:
@Component
publicclass CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 獲取UserService的Bean定義
BeanDefinition userServiceDefinition = beanFactory.getBeanDefinition("userService");
// 修改Bean的作用域(從單例改為原型)
userServiceDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
// 添加屬性值
MutablePropertyValues propertyValues = userServiceDefinition.getPropertyValues();
propertyValues.add("maxQuerySize", 100);
// 注冊新的Bean定義
BeanDefinition orderServiceDefinition = new RootBeanDefinition(OrderService.class);
beanFactory.registerBeanDefinition("orderService", orderServiceDefinition);
log.info("BeanFactoryPostProcessor處理完成,已修改UserService定義");
}
}高級玩法:
- 動態(tài)修改 Bean 的屬性配置,實現(xiàn)多環(huán)境適配
- 根據(jù)條件注冊不同的 Bean 實現(xiàn)類
- 批量設(shè)置 Bean 的屬性,比如給所有數(shù)據(jù)源 Bean 設(shè)置連接超時時間
- 實現(xiàn)自定義注解驅(qū)動的 Bean 配置
MyBatis 的MapperScannerConfigurer就是用這個擴展點實現(xiàn)的,它會掃描指定包下的接口,自動創(chuàng)建 Mapper 接口的 Bean 定義。
執(zhí)行時機:BeanFactoryPostProcessor在 Bean 定義加載完成后,Bean 實例化之前執(zhí)行。和BeanPostProcessor的區(qū)別是:前者處理 Bean 定義,后者處理 Bean 實例。
8. 自定義導(dǎo)入:@Import 與 ImportSelector 動態(tài)裝配 Bean
當(dāng)你的項目越來越大,配置類越來越多,是不是覺得管理起來很麻煩?@Import注解能幫你簡化配置,它可以直接導(dǎo)入普通類、配置類,甚至通過ImportSelector動態(tài)選擇要導(dǎo)入的類。
這就像點餐時的 "套餐" 功能,一鍵導(dǎo)入多個相關(guān)配置:
// 1. 導(dǎo)入普通類(會自動注冊為Bean)
@Import(UserUtil.class)
@Configuration
publicclass AppConfig {
// UserUtil會被自動注冊到容器中
}
// 2. 導(dǎo)入配置類
@Import({DataSourceConfig.class, MyBatisConfig.class})
@Configuration
publicclass AppConfig {
// 相當(dāng)于同時加載了DataSourceConfig和MyBatisConfig
}
// 3. 通過ImportSelector動態(tài)導(dǎo)入
publicclass LogImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 根據(jù)條件動態(tài)決定導(dǎo)入哪些類
boolean enableLog = ClassUtils.isPresent("org.slf4j.Logger", getClass().getClassLoader());
if (enableLog) {
returnnew String[]{LogService.class.getName(), LogAspect.class.getName()};
} else {
returnnew String[]{DefaultLogService.class.getName()};
}
}
}
// 使用自定義ImportSelector
@Import(LogImportSelector.class)
@Configuration
publicclass AppConfig {
// 會根據(jù)是否有SLF4J依賴導(dǎo)入不同的日志組件
}實際應(yīng)用:
- Spring Boot 的自動配置就是通過@Import + ImportSelector實現(xiàn)的
- 根據(jù)環(huán)境動態(tài)選擇不同的 Bean 實現(xiàn)
- 模塊化管理配置,按需導(dǎo)入
- 實現(xiàn)類似@EnableXXX的注解(如@EnableCaching、@EnableAsync)
Spring Cloud 的@EnableEurekaClient就是個典型例子,它通過ImportSelector導(dǎo)入了 Eureka 客戶端的相關(guān)配置類,讓你一行代碼就能開啟服務(wù)注冊發(fā)現(xiàn)功能。
9. 工廠 Bean:FactoryBean 創(chuàng)建復(fù)雜對象的 "專屬工廠"
有些對象創(chuàng)建過程特別復(fù)雜,比如數(shù)據(jù)源、線程池、HttpClient 這些,需要設(shè)置一大堆參數(shù)。如果用普通的@Bean方法配置,代碼會非常臃腫。這時候FactoryBean就能幫你封裝復(fù)雜對象的創(chuàng)建過程,讓配置類保持清爽。
// 自定義FactoryBean創(chuàng)建HttpClient
publicclass HttpClientFactoryBean implements FactoryBean<CloseableHttpClient> {
privateint connectTimeout = 5000;
privateint socketTimeout = 30000;
privateint maxConnections = 100;
// 設(shè)置屬性的setter方法
public void setConnectTimeout(int connectTimeout) { this.connectTimeout = connectTimeout; }
public void setSocketTimeout(int socketTimeout) { this.socketTimeout = socketTimeout; }
public void setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; }
// 創(chuàng)建對象的核心方法
@Override
public CloseableHttpClient getObject() throws Exception {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(maxConnections);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(connectTimeout)
.setSocketTimeout(socketTimeout)
.build();
return HttpClientBuilder.create()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.build();
}
// 返回對象類型
@Override
public Class<?> getObjectType() {
return CloseableHttpClient.class;
}
// 是否單例
@Override
public boolean isSingleton() {
returntrue;
}
}
// 在配置類中使用
@Configuration
publicclass HttpClientConfig {
@Bean
public HttpClientFactoryBean httpClient() {
HttpClientFactoryBean factoryBean = new HttpClientFactoryBean();
factoryBean.setConnectTimeout(3000);
factoryBean.setSocketTimeout(20000);
factoryBean.setMaxConnections(50);
return factoryBean;
}
}當(dāng)你從容器中獲取httpClient Bean 時,Spring 會自動調(diào)用FactoryBean的getObject()方法返回實際的CloseableHttpClient對象。如果你想獲取FactoryBean本身,可以在 Bean 名稱前加&符號,比如applicationContext.getBean("&httpClient")。
常見應(yīng)用:
- MyBatis 的MapperFactoryBean用于創(chuàng)建 Mapper 接口的代理對象
- Spring Data JPA 的JpaRepositoryFactoryBean
- 各種連接池、客戶端的創(chuàng)建封裝
優(yōu)點:
- 封裝復(fù)雜對象的創(chuàng)建邏輯,分離關(guān)注點
- 支持懶加載,只有當(dāng)真正需要對象時才會創(chuàng)建
- 可以更精細地控制對象的創(chuàng)建過程
10. AOP 切面:Advisor 實現(xiàn)無侵入的功能增強
最后一個必須是 Spring 的王牌功能 ——AOP(面向切面編程)。通過Advisor擴展點,你可以在不修改原有代碼的情況下給方法添加額外功能,比如日志記錄、性能監(jiān)控、事務(wù)控制等。
這就像給手機貼鋼化膜,不改變手機本身,卻能提供額外保護:
// 1. 定義切面邏輯
publicclass LogAdvice implements MethodInterceptor {
privatestaticfinal Logger log = LoggerFactory.getLogger(LogAdvice.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 方法執(zhí)行前
long startTime = System.currentTimeMillis();
log.info("方法開始:{},參數(shù):{}",
invocation.getMethod().getName(),
Arrays.toString(invocation.getArguments()));
try {
// 執(zhí)行目標(biāo)方法
Object result = invocation.proceed();
// 方法執(zhí)行后
log.info("方法結(jié)束:{},返回值:{}",
invocation.getMethod().getName(),
result);
return result;
} catch (Exception e) {
// 異常處理
log.error("方法異常:{},錯誤:{}",
invocation.getMethod().getName(),
e.getMessage(), e);
throw e;
} finally {
// 計算耗時
long cost = System.currentTimeMillis() - startTime;
log.info("方法耗時:{}ms", cost);
}
}
}
// 2. 配置Advisor
@Configuration
publicclass AopConfig {
@Bean
public LogAdvice logAdvice() {
returnnew LogAdvice();
}
@Bean
public Advisor logAdvisor() {
// 定義切點:匹配所有Service中的方法
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.example.service..*(..))");
// 創(chuàng)建Advisor,將切點和通知關(guān)聯(lián)
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(logAdvice());
return advisor;
}
}這段代碼會自動給所有 Service 類的方法添加完整的日志記錄,包括:
- 方法調(diào)用前記錄方法名和參數(shù)
- 方法執(zhí)行后記錄返回值
- 方法異常時記錄錯誤信息
- 統(tǒng)計方法執(zhí)行耗時
AOP 能做的遠不止日志:
- 事務(wù)管理:@Transactional注解就是通過 AOP 實現(xiàn)的
- 權(quán)限控制:方法執(zhí)行前檢查權(quán)限
- 緩存控制:自動實現(xiàn)方法結(jié)果緩存
- 性能監(jiān)控:統(tǒng)計方法執(zhí)行時間
- 異常重試:方法失敗時自動重試
Spring 的聲明式事務(wù)就是最經(jīng)典的 AOP 應(yīng)用,通過@Transactional注解,你不需要手動編寫beginTransaction、commit、rollback等代碼,AOP 會在后臺自動幫你完成這些操作。
總結(jié):讓 Spring 為你打工,而不是你為 Spring 打工
掌握這些擴展點后,你會發(fā)現(xiàn) Spring 框架變得無比靈活。不再是你被動適應(yīng)框架,而是框架主動配合你的需求。這 10 個擴展點就像 10 把瑞士軍刀,能幫你輕松解決開發(fā)中的各種復(fù)雜問題:
擴展點 | 核心作用 | 最佳實踐 |
@RestControllerAdvice | 全局異常處理 | 統(tǒng)一 API 響應(yīng)格式 |
HandlerInterceptor | 請求攔截 | 日志、權(quán)限、性能監(jiān)控 |
ApplicationContextAware | 獲取容器對象 | 非 Spring 類中使用 Bean |
CommandLineRunner | 啟動初始化 | 緩存預(yù)熱、服務(wù)注冊 |
ApplicationContextInitializer | 上下文初始化 | 環(huán)境配置、配置源注冊 |
BeanPostProcessor | Bean 加工 | 屬性注入、代理增強 |
BeanFactoryPostProcessor | 修改 Bean 定義 | 動態(tài)配置調(diào)整 |
@Import + ImportSelector | 動態(tài)導(dǎo)入配置 | 模塊化配置、條件裝配 |
FactoryBean | 復(fù)雜對象創(chuàng)建 | 數(shù)據(jù)源、客戶端創(chuàng)建 |
Advisor | AOP 增強 | 日志、事務(wù)、權(quán)限 |
最好的程序員不是寫最多代碼的人,而是最會利用工具的人。Spring 已經(jīng)為我們提供了這么強大的擴展能力,充分利用這些擴展點,讓你的代碼更簡潔、更靈活、更易于維護。
收藏這篇文章,下次遇到類似問題時翻出來看看,你會發(fā)現(xiàn)曾經(jīng)頭疼的問題,用對擴展點就能輕松解決。覺得有用的話別忘了轉(zhuǎn)發(fā)給你的同事,一起提升開發(fā)效率!



































