自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

掌握 Spring 框架這十個(gè)擴(kuò)展點(diǎn),讓你的能力更上一層樓

開(kāi)發(fā) 架構(gòu)
Spring 具有很強(qiáng)的擴(kuò)展性。許多第三方應(yīng)用程序,如 rocketmq、mybatis、redis 等,都可以輕松集成到 Spring 系統(tǒng)中。讓我們一起來(lái)看看 Spring 中最常用的十個(gè)擴(kuò)展點(diǎn)。

當(dāng)我們提到 Spring 時(shí),或許首先映入腦海的是 IOC(控制反轉(zhuǎn))和 AOP(面向切面編程)。它們可以被視為 Spring 的基石。正是憑借其出色的設(shè)計(jì),Spring 才能在眾多優(yōu)秀框架中脫穎而出。

Spring 具有很強(qiáng)的擴(kuò)展性。許多第三方應(yīng)用程序,如 rocketmq、mybatis、redis 等,都可以輕松集成到 Spring 系統(tǒng)中。讓我們一起來(lái)看看 Spring 中最常用的十個(gè)擴(kuò)展點(diǎn)。

1. 全局異常處理

過(guò)去,在開(kāi)發(fā)接口時(shí),如果發(fā)生異常,我們通常需要給用戶一個(gè)更友好的提示。但如果不進(jìn)行錯(cuò)誤處理,例如:

@RequestMapping("/test")
@RestController
public class TestController {
    @GetMapping("/division")
    public String division(@RequestParam("a") int a, @RequestParam("b")int b) {
        return String.valueOf(a / b);
    }
}

這是一個(gè)計(jì)算 a/b 結(jié)果的方法,通過(guò)127.0.0.1:8080/test/division?a=10&b=2訪問(wèn)后會(huì)出現(xiàn)以下結(jié)果:

圖片

什么?用戶能直接看到如此詳細(xì)的錯(cuò)誤信息嗎?

這種報(bào)錯(cuò)方式給用戶帶來(lái)了非常糟糕的體驗(yàn)。為了解決這個(gè)問(wèn)題,我們通常在接口中捕獲異常。

@GetMapping("/division")
public String division(@RequestParam("a") int a, @RequestParam("b") int b) {
    String result = "";
    try {
        result = String.valueOf(a / b);
    } catch (ArithmeticException e) {
        result = "params error";
    }
    return result;
}

接口改造后,當(dāng)發(fā)生異常時(shí),會(huì)提示:“params error”,用戶體驗(yàn)會(huì)更好。

如果只是一個(gè)接口,那沒(méi)問(wèn)題。但如果項(xiàng)目中有成百上千個(gè)接口,我們是否需要為所有接口添加異常處理代碼呢?

肯定不能這樣做的。這時(shí),全局異常處理就派上用場(chǎng)了:RestControllerAdvice。

@RestControllerAdvice
publicclass GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        if (e instanceof ArithmeticException) {
            return"params error";
        }
        if (e instanceof Exception) {
            return"Internal server exception";
        }
        returnnull;
    }
}

只需在 handleException 方法中處理異常情況。業(yè)務(wù)接口可以放心使用,不再需要捕獲異常(遵循統(tǒng)一的處理邏輯)。

2. 自定義攔截器

與 Spring 攔截器相比,Spring MVC 攔截器可以在內(nèi)部獲取 HttpServletRequest 和 HttpServletResponse 等 Web 對(duì)象實(shí)例。

Spring MVC 攔截器的頂級(jí)接口是:HandlerInterceptor,它包含三個(gè)方法:

  • preHandle:在目標(biāo)方法執(zhí)行前執(zhí)行。
  • postHandle:在目標(biāo)方法執(zhí)行后執(zhí)行。
  • afterCompletion:在請(qǐng)求完成時(shí)執(zhí)行。

為了方便起見(jiàn),在一般情況下,我們通常使用 HandlerInterceptor 接口的實(shí)現(xiàn)類(lèi) HandlerInterceptorAdapter。

如果存在權(quán)限認(rèn)證、日志記錄和統(tǒng)計(jì)等場(chǎng)景,可以使用此攔截器。

第一步,通過(guò)繼承 HandlerInterceptorAdapter 類(lèi)定義一個(gè)攔截器:

public class AuthInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestUrl = request.getRequestURI();
        if (checkAuth(requestUrl)) {
            returntrue;
        }
        returnfalse;
    }

    private boolean checkAuth(String requestUrl) {
        System.out.println("===Authority Verificatinotallow===");
        returntrue;
    }
}

第二步,在 Spring 容器中注冊(cè)此攔截器。

@Configuration
public class WebAuthConfig extends WebMvcConfigurerAdapter {
    @Bean
    public AuthInterceptor getAuthInterceptor() {
        return new AuthInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new AuthInterceptor());
    }
}

隨后,當(dāng)請(qǐng)求接口時(shí),Spring MVC 可以通過(guò)此攔截器自動(dòng)攔截接口并驗(yàn)證權(quán)限。

3. 獲取 Spring 容器對(duì)象

在日常開(kāi)發(fā)中,我們經(jīng)常需要從 Spring 容器中獲取 Beans。但是你知道如何獲取 Spring 容器對(duì)象嗎?

3.1 BeanFactoryAware 接口

@Service
public class StudentService implements BeanFactoryAware {
    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    public void add() {
        Student student = (Student) beanFactory.getBean("student");
    }
}

實(shí)現(xiàn) BeanFactoryAware 接口,然后重寫(xiě) setBeanFactory 方法。從這個(gè)方法中,可以獲取 Spring 容器對(duì)象。

3.2 ApplicationContextAware 接口

@Service
public class StudentService2 implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void add() {
        Student student = (Student) applicationContext.getBean("student");
    }
}

4. 導(dǎo)入配置

有時(shí)我們需要在某個(gè)配置類(lèi)中導(dǎo)入其他一些類(lèi),并且導(dǎo)入的類(lèi)也會(huì)被添加到 Spring 容器中。此時(shí),可以使用@Import 注解來(lái)完成此功能。

如果你看過(guò)它的源代碼,會(huì)發(fā)現(xiàn)導(dǎo)入的類(lèi)支持三種不同的類(lèi)型。

然而,我認(rèn)為最好將普通類(lèi)和帶有@Configuration 注解的配置類(lèi)分開(kāi)解釋。因此,列出了四種不同的類(lèi)型:

4.1 導(dǎo)入普通類(lèi)

這種導(dǎo)入方式最簡(jiǎn)單。導(dǎo)入的類(lèi)將被實(shí)例化為一個(gè) bean 對(duì)象。

public class A {
}

@Import(A.class)
@Configuration
public class TestConfiguration {
}

通過(guò)@Import 注解導(dǎo)入類(lèi) A,Spring 可以自動(dòng)實(shí)例化對(duì)象 A。然后,可以在需要的地方通過(guò)@Autowired 注解進(jìn)行注入:

@Autowired
private A a;

是不是很神奇?不需要添加@Bean 注解就可以實(shí)例化對(duì)象。

4.2 導(dǎo)入帶有@Configuration 注解的配置類(lèi)

這種導(dǎo)入方式最復(fù)雜,因?yàn)锧Configuration 注解還支持多種組合注解,例如:

  • @Import
  • @ImportResource
  • @PropertySource 等
public class A {
}

publicclass B {
}

@Import(B.class)
@Configuration
public class AConfiguration {
    @Bean
    public A a() {
        returnnew A();
    }
}

@Import(AConfiguration.class)
@Configuration
public class TestConfiguration {
}

通過(guò)@Import 注解導(dǎo)入一個(gè)帶有@Configuration 注解的配置類(lèi),與該配置類(lèi)相關(guān)的@Import、@ImportResource 和@PropertySource 等注解導(dǎo)入的所有類(lèi)將一次性全部導(dǎo)入。

4.3 ImportSelector

這種導(dǎo)入方式需要實(shí)現(xiàn) ImportSelector 接口:

public class AImportSelector implements ImportSelector {
    private static final String CLASS_NAME = "com.demo.cache.service.A";

    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{CLASS_NAME};
    }
}

@Import(AImportSelector.class)
@Configuration
public class TestConfiguration {
}

這種方法的優(yōu)點(diǎn)是 selectImports 方法返回一個(gè)數(shù)組,這意味著可以非常方便的導(dǎo)入多個(gè)類(lèi)。

4.4 ImportBeanDefinitionRegistrar

這種導(dǎo)入方式需要實(shí)現(xiàn) ImportBeanDefinitionRegistrar 接口:

public class AImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(A.class);
        registry.registerBeanDefinition("a", rootBeanDefinition);
    }
}

@Import(AImportBeanDefinitionRegistrar.class)
@Configuration
public class TestConfiguration {
}

5. 項(xiàng)目啟動(dòng)時(shí)的附加功能

有時(shí)我們需要在項(xiàng)目啟動(dòng)時(shí)自定義一些附加邏輯,例如加載一些系統(tǒng)參數(shù)、資源初始化、預(yù)熱本地緩存等。我們?cè)撛趺醋瞿??Spring Boot 提供了兩個(gè)接口來(lái)幫助我們實(shí)現(xiàn)上述要求:

  • CommandLineRunner
  • ApplicationRunner

它們的用法非常簡(jiǎn)單。以 ApplicationRunner 接口為例:

@Component
publicclass MyApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 在這里編寫(xiě)項(xiàng)目啟動(dòng)時(shí)需要執(zhí)行的代碼
        System.out.println("項(xiàng)目啟動(dòng)時(shí)執(zhí)行附加功能,加載系統(tǒng)參數(shù)...");
        // 假設(shè)這里從配置文件中加載系統(tǒng)參數(shù)并進(jìn)行處理
        Properties properties = new Properties();
        try (InputStream inputStream = new FileInputStream("application.properties")) {
            properties.load(inputStream);
            String systemParam = properties.getProperty("system.param");
            System.out.println("加載的系統(tǒng)參數(shù)值為:" + systemParam);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代碼中,我們實(shí)現(xiàn)了 ApplicationRunner 接口,并重寫(xiě)了 run 方法。在 run 方法中,我們可以編寫(xiě)在項(xiàng)目啟動(dòng)時(shí)需要執(zhí)行的附加功能代碼,例如加載系統(tǒng)參數(shù)、初始化資源、預(yù)熱緩存等。這里只是簡(jiǎn)單地模擬了從配置文件中加載系統(tǒng)參數(shù)并打印出來(lái),實(shí)際應(yīng)用中可以根據(jù)具體需求進(jìn)行更復(fù)雜的操作。

當(dāng)項(xiàng)目啟動(dòng)時(shí),Spring Boot 會(huì)自動(dòng)檢測(cè)并執(zhí)行實(shí)現(xiàn)了 ApplicationRunner 或 CommandLineRunner 接口的類(lèi)中的 run 方法,從而實(shí)現(xiàn)項(xiàng)目啟動(dòng)時(shí)的附加功能。

這兩個(gè)接口的區(qū)別在于參數(shù)類(lèi)型不同,ApplicationRunner 的 run 方法參數(shù)是 ApplicationArguments,它提供了更多關(guān)于應(yīng)用程序參數(shù)的信息,而 CommandLineRunner 的 run 方法參數(shù)是原始的字符串?dāng)?shù)組,直接包含了命令行參數(shù)。根據(jù)具體需求可以選擇使用其中一個(gè)接口來(lái)實(shí)現(xiàn)項(xiàng)目啟動(dòng)時(shí)的附加功能。

6. 修改 BeanDefinition

在實(shí)例化 Bean 對(duì)象之前,Spring IOC 需要先讀取 Bean 的相關(guān)屬性,將它們保存在 BeanDefinition 對(duì)象中,然后通過(guò) BeanDefinition 對(duì)象實(shí)例化 Bean 對(duì)象。

如果你想修改 BeanDefinition 對(duì)象中的屬性,該怎么做呢?我們可以實(shí)現(xiàn) BeanFactoryPostProcessor 接口。

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
        beanDefinitionBuilder.addPropertyValue("id", 123);
        beanDefinitionBuilder.addPropertyValue("name", "Dylan Smith");
        defaultListableBeanFactory.registerBeanDefinition("user", beanDefinitionBuilder.getBeanDefinition());
    }
}

在 postProcessBeanFactory 方法中,可以獲取 BeanDefinition 的相關(guān)對(duì)象并修改該對(duì)象的屬性。

7. 初始化方法

目前,Spring 中比較常用的初始化 bean 的方法有:

  1. 使用@PostConstruct 注解。
  2. 實(shí)現(xiàn) InitializingBean 接口。

7.1 使用@PostConstruct 注解

@Service
public class AService {
    @PostConstruct
    public void init() {
        System.out.println("===Initializing===");
    }
}

在需要初始化的方法上添加@PostConstruct 注解。這樣,它就具有了初始化的能力。

7.2 實(shí)現(xiàn) InitializingBean 接口

@Service
public class BService implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("===Initializing===");
    }
}

8. 在初始化 Bean 前后添加邏輯

有時(shí),你希望在初始化 bean 之前和之后實(shí)現(xiàn)一些自己的邏輯。

這時(shí),可以實(shí)現(xiàn) BeanPostProcessor 接口。

這個(gè)接口目前有兩個(gè)方法:

  • postProcessBeforeInitialization:在初始化方法之前調(diào)用。
  • postProcessAfterInitialization:在初始化方法之后調(diào)用。

例如:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof User) {
            ((User) bean).setUserName("Dylan Smith");
        }
        return bean;
    }
}

如果 Spring 中有一個(gè) User 對(duì)象,將其 userName 設(shè)置為:Dylan Smith。

實(shí)際上,我們經(jīng)常使用的注解,如@Autowired、@Value、@Resource、@PostConstruct 等,都是通過(guò) AutowiredAnnotationBeanPostProcessor 和 CommonAnnotationBeanPostProcessor 實(shí)現(xiàn)的。

9. 在關(guān)閉容器之前添加操作

有時(shí),我們需要在關(guān)閉 Spring 容器之前做一些額外的工作,例如關(guān)閉資源文件。

這時(shí),我們可以實(shí)現(xiàn) DisposableBean 接口并覆蓋其 destroy 方法:

@Service
public class DService implements InitializingBean, DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean destroy");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean afterPropertiesSet");
    }
}

這樣,在 Spring 容器銷(xiāo)毀之前會(huì)調(diào)用 destroy 方法。通常,我們會(huì)同時(shí)實(shí)現(xiàn) InitializingBean 和 DisposableBean 接口,并覆蓋初始化方法和銷(xiāo)毀方法。

10. 自定義作用域

我們都知道,Spring 只支持兩種默認(rèn)的 Scope:

  • singleton:在單例作用域中,從 Spring 容器中獲取的每個(gè) bean 都是同一個(gè)對(duì)象。
  • prototype:在原型作用域中,從 Spring 容器中獲取的每個(gè) bean 都是不同的對(duì)象。

Spring Web 擴(kuò)展了 Scope 并添加了:

  • RequestScope:在同一個(gè)請(qǐng)求中,從 Spring 容器中獲取的 bean 都是同一個(gè)對(duì)象。
  • SessionScope:在同一個(gè)會(huì)話中,從 Spring 容器中獲取的 bean 都是同一個(gè)對(duì)象。

即便如此,有些場(chǎng)景仍然無(wú)法滿足我們的要求。

例如,如果我們希望在同一個(gè)線程中從 Spring 容器中獲取的所有 bean 都是同一個(gè)對(duì)象,該怎么辦呢?

這就需要自定義 Scope。

第一步,實(shí)現(xiàn) Scope 接口:

public class ThreadLocalScope implements Scope {
    privatestaticfinal ThreadLocal THREAD_LOCAL_SCOPE = new ThreadLocal();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Object value = THREAD_LOCAL_SCOPE.get();
        if (value!= null) {
            return value;
        }
        Object object = objectFactory.getObject();
        THREAD_LOCAL_SCOPE.set(object);
        return object;
    }

    @Override
    public Object remove(String name) {
        THREAD_LOCAL_SCOPE.remove();
        returnnull;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }

    @Override
    public Object resolveContextualObject(String key) {
        returnnull;
    }

    @Override
    public String getConversationId() {
        returnnull;
    }
}

第二步,將新定義的“Scope”注入到 Spring 容器中:

@Component
public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope("threadLocalScope", new ThreadLocalScope());
    }
}

第三步,使用新定義的“Scope”:

@Scope("threadLocalScope")
@Service
public class CService {
    public void add() {
    }
}

總結(jié)

好了,今天的內(nèi)容就到這里。對(duì) Spring 框架感興趣的讀者可以關(guān)注我,后續(xù)會(huì)分享更多有關(guān) Spring 的相關(guān)知識(shí)。

責(zé)任編輯:武曉燕 來(lái)源: 程序猿技術(shù)充電站
相關(guān)推薦

2023-12-19 18:08:47

MySQL方法優(yōu)化查詢

2014-08-18 14:54:54

Git

2018-05-10 14:34:48

薪資Java開(kāi)發(fā)

2019-12-24 09:05:08

框架薪資Web

2023-09-24 23:07:24

流量抑制風(fēng)暴控制

2012-05-28 14:18:33

Web

2023-11-01 13:34:37

Python

2023-07-21 08:01:13

CSSInherit?

2011-03-31 09:57:54

Windows XP

2023-07-04 08:33:46

Python對(duì)象編程

2021-09-21 15:17:09

API微服務(wù)后端

2011-03-31 09:51:45

Windows XP

2009-10-23 14:46:43

2024-06-20 13:22:13

C++11C++模板

2017-08-02 11:38:15

AndroidCoding技巧

2015-03-30 09:48:33

程序員更上一層樓

2019-08-26 10:10:57

數(shù)據(jù)中心運(yùn)維宕機(jī)

2021-01-21 11:24:16

智能安全首席信息安全官CISO

2019-08-26 14:53:32

數(shù)據(jù)中心運(yùn)維管理宕機(jī)

2023-12-06 16:50:01

Godot 4.2開(kāi)源
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)