Эти навыки кода весной, от которых невозможно оторваться

Java

предисловие

В последнее время все больше читателей узнают мои статьи, что очень приятно. Некоторые читатели в частном порядке написали мне, что надеются в будущем поделиться еще статьями о весне, чтобы они могли пригодиться в практической работе. Так уж получилось, что я провел некоторое исследование исходного кода Spring, и в сочетании с моим фактическим опытом работы за последние несколько лет я подытожу знания Spring, которые я считаю хорошими, и я надеюсь, что это будет полезно для ты.

1 Как получить объект контейнера Spring

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. Реализуйте интерфейс 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 из этого метода.

Недавно я случайно получил заметку о чистке, написанную крупным производителем BAT, которая открыла мне сразу вторую линейку Ren и Du, и я все больше чувствую, что алгоритм не так сложен, как я себе представлял.Заметки о чистке, написанные боссом BAT, позвольте мне мягко получить предложение

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");
    }

}

выполнитьApplicationListenerИнтерфейс, следует отметить, что общий тип, полученный этим интерфейсом,ContextRefreshedEventкласс, затем переопределитьonApplicationEventметод, вы также можете получить объект контейнера Spring из этого метода.

Кроме того, необходимо упомянутьAwareИнтерфейс, который на самом деле является пустым интерфейсом, не содержит никаких методов.

Он представляет смысл восприятия, и указанный объект может быть получен через этот тип интерфейса, например:

  • Получить BeanFactory через BeanFactoryAware
  • Получить ApplicationContext через ApplicationContextAware
  • Получить BeanName и т. д. через BeanNameAware

AwareИнтерфейс является очень часто используемой функцией и в настоящее время включает в себя следующие функции:

2 Как инициализировать бины

Spring поддерживает три метода инициализации bean-компонентов:

  • Укажите метод init-method в xml
  • Аннотировать с помощью @PostConstruct
  • Реализовать интерфейс InitializingBean

Первый способ слишком старый, и мало кто его сейчас использует, поэтому конкретное использование не приводится.

1. Аннотируйте с помощью @PostConstruct

@Service
public class AService {

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

Добавьте в метод, который необходимо инициализировать@PostConstructАннотация, чтобы была возможность инициализировать.

2. Реализовать интерфейс InitializingBean

@Service
public class BService implements InitializingBean {

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

выполнитьInitializingBeanинтерфейс, переопределениеafterPropertiesSetметод, в котором функция инициализации может быть завершена.

Вот кстати интересный вопрос:init-method,PostConstructиInitializingBeanКаков порядок выполнения?

Код клавиши, определяющий порядок их вызова, находится вAbstractAutowireCapableBeanFactoryКатегорияinitializeBeanметод.

Этот код вызовет сначалаBeanPostProcessor的postProcessBeforeInitializationметод, в то время какPostConstructчерезInitDestroyAnnotationBeanPostProcessorпонял, этоBeanPostProcessor,такPostConstructВыполнить первым.

иinvokeInitMethodsКод в методе:

Решил сначала позвонить.InitializingBean, затем позвонитеinit-method.

Итак, в заключение, порядок их вызова:

Три Настройте свой собственный объем

Мы все знаем, что Spring по умолчанию поддерживаетScopeИх всего два:

  • singleton singleton, компонент, полученный из контейнера Spring, каждый раз является одним и тем же объектом.
  • Существует несколько экземпляров прототипа, и бобы, полученные из контейнера Spring, каждый раз являются разными объектами.

Spring web снова расширил Scope, добавив:

  • RequestScope Компоненты, полученные из контейнера Spring в одном и том же запросе, являются одним и тем же объектом.
  • SessionScope Компоненты, полученные одним и тем же сеансом из контейнера Spring, являются одним и тем же объектом.

Тем не менее, есть некоторые сценарии, которые не соответствуют нашим требованиям.

Например, бины, которые мы хотим получить из контейнера Spring в том же потоке, являются одним и тем же объектом, что нам делать?

Для этого требуется пользовательская область видимости.

Первым шагом является реализация интерфейса 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() {
    }
}

Четыре Не говорите, что FactoryBean бесполезен

Говоря оFactoryBeanдолжен упомянутьBeanFactory, потому что интервьюеры любят спрашивать о разнице.

  • BeanFactory: интерфейс верхнего уровня контейнера Spring, который управляет фабрикой компонентов.
  • FactoryBean: это не обычный фабричный компонент, он скрывает детали создания экземпляров некоторых сложных компонентов и обеспечивает удобство для приложений верхнего уровня.

Если вы посмотрите на исходный код Spring, вы обнаружите, что он использует интерфейс FactoryBean более чем в 70 местах.

Приведенного выше изображения достаточно, чтобы проиллюстрировать важность этого интерфейса, пожалуйста, не игнорируйте его, хорошо?

Особое упоминание:mybatisизSqlSessionFactoryобъект черезSqlSessionFactoryBeanкласс создан.

Давайте вместе определим наш собственный FactoryBean:

@Component
public class MyFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        String data1 = buildData1();
        String data2 = buildData2();
        return buildData3(data1, data2);
    }

    private String buildData1() {
        return "data1";
    }

    private String buildData2() {
        return "data2";
    }

    private String buildData3(String data1, String data2) {
        return data1 + data2;
    }


    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

Получите объект экземпляра FactoryBean:

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

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

    public void test() {
        Object myFactoryBean = beanFactory.getBean("myFactoryBean");
        System.out.println(myFactoryBean);
        Object myFactoryBean1 = beanFactory.getBean("&myFactoryBean");
        System.out.println(myFactoryBean1);
    }
}
  • getBean("myFactoryBean");что получаетсяMyFactoryBeanServiceв классеgetObjectобъект, возвращаемый методом,

  • getBean("&myFactoryBean");что получаетMyFactoryBeanобъект.

Недавно я случайно получил заметку о чистке, написанную крупным производителем BAT, которая открыла мне сразу вторую линейку Ren и Du, и я все больше чувствую, что алгоритм не так сложен, как я себе представлял.Заметки о чистке, написанные боссом BAT, позвольте мне мягко получить предложение

5. Простое преобразование пользовательского типа

В настоящее время Spring поддерживает 3 преобразователя типов:

  • Converter: преобразование объекта типа S в объект типа T
  • ConverterFactory: преобразование объектов типа S в объекты типа R и подклассов.
  • GenericConverter: поддерживает преобразование нескольких исходных и целевых типов, а также предоставляет контекст исходных и целевых типов, что позволяет реализовать преобразование типов на основе аннотаций или информации об атрибутах.

Сценарии, используемые этими преобразователями трех типов, различны, мы используемConverter<S,T>Например. Предположим: В объекте сущности, который получает параметры в интерфейсе, есть поле типа Дата, но фактический параметр имеет строковый тип: 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";
    }
}

При запросе интерфейса поле registerDate в объекте User будет автоматически преобразовано в тип Date.

Шесть весенних перехватчиков mvc, раньше говорили хорошо

По сравнению с перехватчиком корневой пружины Spring MVC Interceptor, он может получитьHttpServletRequestиHttpServletResponseЭкземпляр веб-объекта и т. д.

Интерфейс верхнего уровня перехватчика Spring MVC:HandlerInterceptor, который содержит три метода:

  • preHandle выполняется до выполнения целевого метода
  • Выполнять после выполнения целевого метода postHandle
  • afterCompletion выполняется, когда запрос завершен

Для удобства мы обычно используемHandlerInterceptorКласс реализации интерфейсаHandlerInterceptorAdapterсвоего рода.

Если есть сценарии аутентификации авторизации, логи и статистика, то можно использовать этот перехватчик.

Первый шаг — наследовать класс 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(getAuthInterceptor());
    }
}

На третьем этапе Spring MVC может автоматически перехватывать интерфейс и проверять разрешения через перехватчик при запросе интерфейса.

На самом деле перехватчик относительно прост и может использоваться вDispatcherServletКатегорияdoDispatchСм. вызывающий процесс в методе:

Кстати, здесь я говорю только о перехватчике Spring MVC, а не о перехватчике Spring, потому что я немного эгоистичен и узнаю позже.

Переключатель Seven Enable действительно ароматный

Я не знаю, использовали ли вы егоEnableАннотации в начале, такие как: EnableAsync, EnableCaching, EnableAspectJAutoProxy и т. д., такие аннотации подобны переключателям, если@ConfigurationДобавление таких аннотаций к определенному классу конфигурации может активировать связанные функции.

Разве это не круто?

Давайте вместе реализуем собственный переключатель:

Первым шагом является определение LogFilter:

public class LogFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("记录请求日志");
        chain.doFilter(request, response);
        System.out.println("记录响应日志");
    }

    @Override
    public void destroy() {
        
    }
}

Второй шаг, зарегистрируйте LogFilter:

@ConditionalOnWebApplication
public class LogFilterWebConfig {

    @Bean
    public LogFilter timeFilter() {
        return new LogFilter();
    }
}

Обратите внимание, что здесь@ConditionalOnWebApplicationАннотация, не используется напрямую@Configurationаннотация.

Третий шаг — определить аннотацию переключателя @EnableLog:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LogFilterWebConfig.class)
public @interface EnableLog {

}

Четвертый шаг — простоspringbootСтартовый класс плюс@EnableLogАннотация позволяет LogFilter записывать журналы запросов и ответов.

Восемь перехватчиков RestTemplate для Spring

Мы используемRestTemplateПри вызове удаленного интерфейса иногда необходимоheaderинформация, такая как traceId, источник и т. д., удобна для подключения полной ссылки запроса при запросе журнала и быстрого обнаружения проблемы.

Этот бизнес-сценарий может пройтиClientHttpRequestInterceptorИнтерфейс реализован следующим образом:

Первым шагом является реализация интерфейса ClientHttpRequestInterceptor:

public class RestTemplateInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        request.getHeaders().set("traceId", MdcUtil.get());
        return execution.execute(request, body);
    }
}

Второй шаг — определить класс конфигурации:

@Configuration
public class RestTemplateConfiguration {

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(Collections.singletonList(restTemplateInterceptor()));
        return restTemplate;
    }

    @Bean
    public RestTemplateInterceptor restTemplateInterceptor() {
        return new RestTemplateInterceptor();
    }
}

Среди них MdcUtil фактически использует инструмент MDC дляThreadLocalХранить и получать traceId в

public class MdcUtil {

    private static final String TRACE_ID = "TRACE_ID";

    public static String get() {
        return MDC.get(TRACE_ID);
    }

    public static void add(String value) {
        MDC.put(TRACE_ID, value);
    }
}

Конечно, этот пример не демонстрирует конкретную настройку метода add класса MdcUtil, перед выполнением метода интерфейса в фильтре мы можем сгенерировать traceId, вызвать метод add класса MdcUtil, чтобы добавить его в MDC, а затем использовать его в другом месте того же запроса.TraceId можно получить с помощью метода get класса MdcUtil.

Девять унифицированная обработка исключений

В прошлом, когда мы разрабатывали интерфейс, если было исключение, чтобы дать пользователю более дружелюбную подсказку, например:

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

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

Если вы не выполняете какую-либо обработку и запрашиваете результат интерфейса добавления, об ошибке будет сообщено напрямую:

какие? Может ли пользователь увидеть сообщение об ошибке напрямую?

Такой способ взаимодействия дает пользователю очень плохой опыт, для решения этой проблемы мы обычно ловим исключения в интерфейсе:

@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Исключения обрабатываются в методе, который можно уверенно использовать в бизнес-интерфейсе, и нет необходимости ловить исключения (кто-то обрабатывает их единообразно). Очень круто.

Десять асинхронных тоже могут быть такими элегантными

В прошлом, когда мы использовали асинхронные функции, обычно было три способа:

  • Наследовать от класса Thread
  • Реализовать интерфейс Runable
  • Использовать пул потоков

Давайте рассмотрим вместе:

Наследовать от класса Thread

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("===call MyThread===");
    }

    public static void main(String[] args) {
        new MyThread().start();
    }
}

Реализовать интерфейс Runable

public class MyWork implements Runnable {
    @Override
    public void run() {
        System.out.println("===call MyWork===");
    }

    public static void main(String[] args) {
        new Thread(new MyWork()).start();
    }
}

Использовать пул потоков

public class MyThreadPool {

    private static ExecutorService executorService = new ThreadPoolExecutor(1, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(200));

    static class Work implements Runnable {

        @Override
        public void run() {
            System.out.println("===call work===");
        }
    }

    public static void main(String[] args) {
        try {
            executorService.submit(new MyThreadPool.Work());
        } finally {
            executorService.shutdown();
        }

    }
}

Эти три способа реализации асинхронности нельзя назвать плохими, но Spring уже извлек для нас некоторые общие места, и нам не нужно наследоватьThreadкласс или реализацияRunableинтерфейс, все готово.

Как активировать асинхронную функцию?

Первый шаг — добавить класс запуска проекта Springboot.@EnableAsyncаннотация.

@EnableAsync
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);
    }
}

Второй шаг — добавить аннотацию @Async к методу, который должен использовать асинхронность:

@Service
public class PersonService {

    @Async
    public String get() {
        System.out.println("===add==");
        return "data";
    }
}

Затем назовите его там, где он используется: personService.get(); имеет асинхронную функцию, разве это не удивительно?

По умолчанию spring создаст поток для выполнения нашего асинхронного метода, если метод вызывается очень много раз, необходимо создать большое количество потоков, что приведет к пустой трате ресурсов.

В это время мы можем определить пул потоков, и асинхронный метод будет автоматически отправлен в пул потоков для выполнения.

@Configuration
public class ThreadPoolConfig {

    @Value("${thread.pool.corePoolSize:5}")
    private int corePoolSize;

    @Value("${thread.pool.maxPoolSize:10}")
    private int maxPoolSize;

    @Value("${thread.pool.queueCapacity:200}")
    private int queueCapacity;

    @Value("${thread.pool.keepAliveSeconds:30}")
    private int keepAliveSeconds;

    @Value("${thread.pool.threadNamePrefix:ASYNC_}")
    private String threadNamePrefix;

    @Bean
    public Executor MessageExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix(threadNamePrefix);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

Основной метод весенней асинхронности:

В зависимости от возвращаемого значения ситуация обработки не одинакова, она делится на следующие ситуации:

Одиннадцать Я слышал, что кеш прост в использовании, но я не ожидал, что он будет настолько прост в использовании

Схема архитектуры кэша Spring:

В настоящее время он поддерживает несколько кешей:

Здесь мы возьмем в качестве примера кофеин, который официально рекомендован к весне.

Первый шаг - ввести соответствующую банку с кофеином.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.6.0</version>
</dependency>

Второй шаг – настройкаCacheManager, наEnableCaching

@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public CacheManager cacheManager(){
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        //Caffeine配置
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
                //最后一次写入后经过固定时间过期
                .expireAfterWrite(10, TimeUnit.SECONDS)
                //缓存的最大条数
                .maximumSize(1000);
        cacheManager.setCaffeine(caffeine);
        return cacheManager;
    }
}

Третий шаг, используйтеCacheableАннотация для получения данных

@Service
public class CategoryService {
   
   //category是缓存名称,#type是具体的key,可支持el表达式
   @Cacheable(value = "category", key = "#type")
   public CategoryModel getCategory(Integer type) {
       return getCategoryByType(type);
   }

   private CategoryModel getCategoryByType(Integer type) {
       System.out.println("根据不同的type:" + type + "获取不同的分类数据");
       CategoryModel categoryModel = new CategoryModel();
       categoryModel.setId(1L);
       categoryModel.setParentId(0L);
       categoryModel.setName("电器");
       categoryModel.setLevel(3);
       return categoryModel;
   }
}

При вызове метода categoryService.getCategory() начните сcaffineДанные берутся из кеша, если данные можно получить, то данные будут возвращены напрямую, без входа в тело метода. Если данные не могут быть получены, код в теле прямого метода получает данные и помещает их в caffine-кэш.

Недавно я случайно получил заметку о чистке, написанную крупным производителем BAT, которая открыла мне сразу вторую линейку Ren и Du, и я все больше чувствую, что алгоритм не так сложен, как я себе представлял.Заметки о чистке, написанные боссом BAT, позвольте мне мягко получить предложение

Последнее слово (пожалуйста, обратите внимание, не портите меня зря)

Если эта статья полезна или вдохновляет вас, пожалуйста, обратите внимание, ваша поддержка является самой большой мотивацией для меня, чтобы продолжать писать.

Попросите в один клик три ссылки: лайк, вперед и смотреть.

Следите за официальной учетной записью: [Су Сан сказал о технологии], ответьте в официальной учетной записи: интервью, артефакты кода, руководства по разработке, управление временем имеют большие преимущества для поклонников, и ответьте: присоединяйтесь к группе, вы можете общаться и учиться со многими старшими из BAT. производители .