最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入了解Spring中最常用的11個擴展點

 更新時間:2022年09月20日 09:57:30   作者:蘇三說技術  
我們一說到spring,可能第一個想到的是?IOC(控制反轉)?和?AOP(面向切面編程)。除此之外,我們在使用spring的過程中,有沒有發(fā)現它的擴展能力非常強。今天就來跟大家一起聊聊,在Spring中最常用的11個擴展點

前言

我們一說到spring,可能第一個想到的是 IOC(控制反轉) 和 AOP(面向切面編程)。

沒錯,它們是spring的基石,得益于它們的優(yōu)秀設計,使得spring能夠從眾多優(yōu)秀框架中脫穎而出。

除此之外,我們在使用spring的過程中,有沒有發(fā)現它的擴展能力非常強。由于這個優(yōu)勢的存在,讓spring擁有強大的包容能力,讓很多第三方應用能夠輕松投入spring的懷抱。比如:rocketmq、mybatis、redis等。

今天跟大家一起聊聊,在Spring中最常用的11個擴展點。

1.自定義攔截器

spring mvc攔截器根spring攔截器相比,它里面能夠獲取HttpServletRequestHttpServletResponse等web對象實例。

spring mvc攔截器的頂層接口是:HandlerInterceptor,包含三個方法:

  • preHandle 目標方法執(zhí)行前執(zhí)行
  • postHandle 目標方法執(zhí)行后執(zhí)行
  • afterCompletion 請求完成時執(zhí)行

為了方便我們一般情況會用HandlerInterceptor接口的實現類HandlerInterceptorAdapter類。

假如有權限認證、日志、統(tǒng)計的場景,可以使用該攔截器。

第一步,繼承HandlerInterceptorAdapter類定義攔截器:

public class AuthInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String requestUrl = request.getRequestURI();
        if (checkAuth(requestUrl)) {
            return true;
        }

        return false;
    }

    private boolean checkAuth(String requestUrl) {
        System.out.println("===權限校驗===");
        return true;
    }
}

第二步,將該攔截器注冊到spring容器:

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

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

第三步,在請求接口時spring mvc通過該攔截器,能夠自動攔截該接口,并且校驗權限。

2.獲取Spring容器對象

在我們日常開發(fā)中,經常需要從Spring容器中獲取Bean,但你知道如何獲取Spring容器對象嗎?

2.1 BeanFactoryAware接口

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

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

    public void add() {
        Person person = (Person) beanFactory.getBean("person");
    }
}

實現BeanFactoryAware接口,然后重寫setBeanFactory方法,就能從該方法中獲取到spring容器對象。

2.2 ApplicationContextAware接口

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

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

    public void add() {
        Person person = (Person) applicationContext.getBean("person");
    }
}

實現ApplicationContextAware接口,然后重寫setApplicationContext方法,也能從該方法中獲取到spring容器對象。

2.3 ApplicationListener接口

@Service
public class PersonService3 implements ApplicationListener<ContextRefreshedEvent> {
    private ApplicationContext applicationContext;
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        applicationContext = event.getApplicationContext();
    }

    public void add() {
        Person person = (Person) applicationContext.getBean("person");
    }
}

3.全局異常處理

以前我們在開發(fā)接口時,如果出現異常,為了給用戶一個更友好的提示,例如:

@RequestMapping("/test")
@RestController
public class TestController {

    @GetMapping("/add")
    public String add() {
        int a = 10 / 0;
        return "成功";
    }
}

如果不做任何處理請求add接口結果直接報錯:

what?用戶能直接看到錯誤信息?

這種交互方式給用戶的體驗非常差,為了解決這個問題,我們通常會在接口中捕獲異常:

@GetMapping("/add")
public String add() {
    String result = "成功";
    try {
        int a = 10 / 0;
    } catch (Exception e) {
        result = "數據異常";
    }
    return result;
}

接口改造后,出現異常時會提示:“數據異常”,對用戶來說更友好。

看起來挺不錯的,但是有問題。。。

如果只是一個接口還好,但是如果項目中有成百上千個接口,都要加上異常捕獲代碼嗎?

答案是否定的,這時全局異常處理就派上用場了:RestControllerAdvice

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        if (e instanceof ArithmeticException) {
            return "數據異常";
        }
        if (e instanceof Exception) {
            return "服務器內部異常";
        }
        retur nnull;
    }
}

只需在handleException方法中處理異常情況,業(yè)務接口中可以放心使用,不再需要捕獲異常(有人統(tǒng)一處理了)。真是爽歪歪。

4.類型轉換器

spring目前支持3中類型轉換器:

  • Converter<S,T>:將 S 類型對象轉為 T 類型對象
  • ConverterFactory<S, R>:將 S 類型對象轉為 R 類型及子類對象
  • GenericConverter:它支持多個source和目標類型的轉化,同時還提供了source和目標類型的上下文,這個上下文能讓你實現基于屬性上的注解或信息來進行類型轉換。

這3種類型轉換器使用的場景不一樣,我們以Converter<S,T>為例。假如:接口中接收參數的實體對象中,有個字段的類型是Date,但是實際傳參的是字符串類型:2021-01-03 10:20:15,要如何處理呢?

第一步,定義一個實體User:

@Data
public class User {

    private Long id;
    private String name;
    private Date registerDate;
}

第二步,實現Converter接口:

public class DateConverter implements Converter<String, Date> {

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public Date convert(String source) {
        if (source != null && !"".equals(source)) {
            try {
                simpleDateFormat.parse(source);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

第三步,將新定義的類型轉換器注入到spring容器中:

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new DateConverter());
    }
}

第四步,調用接口

@RequestMapping("/user")
@RestController
public class UserController {

    @RequestMapping("/save")
    public String save(@RequestBody User user) {
        return "success";
    }
}

請求接口時User對象中registerDate字段會被自動轉換成Date類型。

5.導入配置

有時我們需要在某個配置類中引入另外一些類,被引入的類也加到spring容器中。這時可以使用@Import注解完成這個功能。

如果你看過它的源碼會發(fā)現,引入的類支持三種不同類型。

但是我認為最好將普通類和@Configuration注解的配置類分開講解,所以列了四種不同類型:

5.1 普通類

這種引入方式是最簡單的,被引入的類會被實例化bean對象。

public class A {
}

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

通過@Import注解引入A類,spring就能自動實例化A對象,然后在需要使用的地方通過@Autowired注解注入即可:

@Autowired
private A a;

是不是挺讓人意外的?不用加@Bean注解也能實例化bean。

5.2 配置類

這種引入方式是最復雜的,因為@Configuration注解還支持多種組合注解,比如:

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

public class B {
}

@Import(B.class)
@Configuration
public class AConfiguration {

    @Bean
    public A a() {
        return new A();
    }
}

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

通過@Import注解引入@Configuration注解的配置類,會把該配置類相關@Import、@ImportResource、@PropertySource等注解引入的類進行遞歸,一次性全部引入。

5.3 ImportSelector

這種引入方式需要實現ImportSelector接口:

public class AImportSelector implements ImportSelector {

private static final String CLASS_NAME = "com.sue.cache.service.test13.A";
    
 public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{CLASS_NAME};
    }
}

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

這種方式的好處是selectImports方法返回的是數組,意味著可以同時引入多個類,還是非常方便的。

5.4 ImportBeanDefinitionRegistrar

這種引入方式需要實現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 {
}

這種方式是最靈活的,能在registerBeanDefinitions方法中獲取到BeanDefinitionRegistry容器注冊對象,可以手動控制BeanDefinition的創(chuàng)建和注冊。

6.項目啟動時

有時候我們需要在項目啟動時定制化一些附加功能,比如:加載一些系統(tǒng)參數、完成初始化、預熱本地緩存等,該怎么辦呢?

好消息是springboot提供了:

  • CommandLineRunner
  • ApplicationRunner

這兩個接口幫助我們實現以上需求。

它們的用法還是挺簡單的,以ApplicationRunner接口為例:

@Component
public class TestRunner implements ApplicationRunner {

    @Autowired
    private LoadDataService loadDataService;

    public void run(ApplicationArguments args) throws Exception {
        loadDataService.load();
    }
}

實現ApplicationRunner接口,重寫run方法,在該方法中實現自己定制化需求。

如果項目中有多個類實現了ApplicationRunner接口,他們的執(zhí)行順序要怎么指定呢?

答案是使用@Order(n)注解,n的值越小越先執(zhí)行。當然也可以通過@Priority注解指定順序。

7.修改BeanDefinition

Spring IOC在實例化Bean對象之前,需要先讀取Bean的相關屬性,保存到BeanDefinition對象中,然后通過BeanDefinition對象,實例化Bean對象。

如果想修改BeanDefinition對象中的屬性,該怎么辦呢?

答:我們可以實現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", "蘇三說技術");
        defaultListableBeanFactory.registerBeanDefinition("user", beanDefinitionBuilder.getBeanDefinition());
    }
}

在postProcessBeanFactory方法中,可以獲取BeanDefinition的相關對象,并且修改該對象的屬性。

8.初始化Bean前后

有時,你想在初始化Bean前后,實現一些自己的邏輯。

這時可以實現:BeanPostProcessor接口。

該接口目前有兩個方法:

  • postProcessBeforeInitialization 該在初始化方法之前調用。
  • postProcessAfterInitialization 該方法再初始化方法之后調用。

例如:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof User) {
            ((User) bean).setUserName("蘇三說技術");
        }
        return bean;
    }
}

如果spring中存在User對象,則將它的userName設置成:蘇三說技術。

其實,我們經常使用的注解,比如:@Autowired、@Value、@Resource、@PostConstruct等,是通過AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor實現的。

9.初始化方法

目前spring中使用比較多的初始化bean的方法有:

  • 使用@PostConstruct注解
  • 實現InitializingBean接口

9.1 使用@PostConstruct注解

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

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

9.2 實現InitializingBean接口

@Service
public class BService implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("===初始化===");
    }
}

實現InitializingBean接口,重寫afterPropertiesSet方法,該方法中可以完成初始化功能。

10.關閉容器前

有時候,我們需要在關閉spring容器前,做一些額外的工作,比如:關閉資源文件等。

這時可以實現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容器銷毀前,會調用該destroy方法,做一些額外的工作。

通常情況下,我們會同時實現InitializingBean和DisposableBean接口,重寫初始化方法和銷毀方法。

11.自定義作用域

我們都知道spring默認支持的Scope只有兩種:

  • singleton 單例,每次從spring容器中獲取到的bean都是同一個對象。
  • prototype 多例,每次從spring容器中獲取到的bean都是不同的對象。

spring web又對Scope進行了擴展,增加了:

  • RequestScope 同一次請求從spring容器中獲取到的bean都是同一個對象。
  • SessionScope 同一個會話從spring容器中獲取到的bean都是同一個對象。

即便如此,有些場景還是無法滿足我們的要求。

比如,我們想在同一個線程中從spring容器獲取到的bean都是同一個對象,該怎么辦?

這就需要自定義Scope了。

第一步實現Scope接口:

public class ThreadLocalScope implements Scope {
    private static final 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();
        return null;
    }

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

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

    @Override
    public String getConversationId() {
        return null;
    }
}

第二步將新定義的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() {
    }
}

以上就是深入了解Spring中最常用的11個擴展點的詳細內容,更多關于Spring擴展點的資料請關注腳本之家其它相關文章!

相關文章

  • 圖文講解IDEA中根據數據庫自動生成實體類

    圖文講解IDEA中根據數據庫自動生成實體類

    這篇文章主要以圖文講解IDEA中根據數據庫自動生成實體類,本文主要以Mysql數據庫為例,應該會對大家有所幫助,如果有錯誤的地方,還望指正
    2023-03-03
  • jackson反序列化時如何忽略不需要的字段

    jackson反序列化時如何忽略不需要的字段

    這篇文章主要介紹了jackson反序列化時如何忽略不需要的字段,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • java compiler沒有1.8怎么解決

    java compiler沒有1.8怎么解決

    這篇文章主要介紹了java compiler沒有1.8的解決方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-08-08
  • 解析Flink內核原理與實現核心抽象

    解析Flink內核原理與實現核心抽象

    Flink API提供了開發(fā)的接口,此外,為了實現業(yè)務邏輯,還必須為開發(fā)者提供自定義業(yè)務邏輯的能力,下面為大家解析Flink內核原理與實現核心抽象
    2021-08-08
  • Java管道流實現線程間通信過程解析

    Java管道流實現線程間通信過程解析

    這篇文章主要介紹了Java管道流實現線程間通信過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-03-03
  • 基于Spring BeanUtils的copyProperties方法使用及注意事項

    基于Spring BeanUtils的copyProperties方法使用及注意事項

    這篇文章主要介紹了基于Spring BeanUtils的copyProperties方法使用及注意事項,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • IDEA教程創(chuàng)建SpringBoot前后端分離項目示例圖解

    IDEA教程創(chuàng)建SpringBoot前后端分離項目示例圖解

    在使用spring、mybatis等框架時,配置文件很復雜,有時復雜的讓人想放棄Java,使用C#。springboot出現這一切問題就都不是問題
    2021-10-10
  • java實現字符串和日期類型相互轉換的方法

    java實現字符串和日期類型相互轉換的方法

    這篇文章主要介紹了java實現字符串和日期類型相互轉換的方法,涉及java針對日期與字符串的轉換與運算相關操作技巧,需要的朋友可以參考下
    2017-02-02
  • 解決@Cacheable在同一個類中方法調用不起作用的問題

    解決@Cacheable在同一個類中方法調用不起作用的問題

    這篇文章主要介紹了解決@Cacheable在同一個類中方法調用不起作用的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java中的Semaphore源碼分析

    Java中的Semaphore源碼分析

    這篇文章主要介紹了Java中的Semaphore源碼分析,Semaphore是一個訪問公共資源的線程數量如限流、停車等,它是一個基于AQS實現的共享鎖,主要是通過控制state變量來實現,需要的朋友可以參考下
    2023-11-11

最新評論

阿巴嘎旗| 天台县| 沁水县| 本溪| 余干县| 雅安市| 仙居县| 扬中市| 全南县| 安多县| 隆子县| 祥云县| 弥勒县| 内江市| 沂水县| 香河县| 满城县| 江城| 浪卡子县| 扶风县| 永修县| 绵阳市| 铜鼓县| 黑山县| 福贡县| 繁峙县| 天全县| 昔阳县| 鹰潭市| 云阳县| 景德镇市| 宜宾县| 东宁县| 麻城市| 酉阳| 平顺县| 郁南县| 丹巴县| 西和县| 泰兴市| 乌兰浩特市|