Изучение аннотаций Spring Boot @ServletComponentScan и расширение

Spring Boot

@ServletComponentScanServletРегистрация сервлетов через кодРегистрация сервлетов с аннотациямиFilterАннотировать с помощью @WebFilterНаследовать HttpFilterРеализовать интерфейс фильтраListener@WebListenerСуммировать

@ServletComponentScan

Исходный код выглядит следующим образом:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ServletComponentScanRegistrar.class})
public @interface ServletComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

Ключевым моментом в аннотации @ServletComponentScan является ссылка на класс ServletComponentScanRegistrar, который является классом реализации интерфейса ImportBeanDefinitionRegistrar и будет анализироваться контейнером Spring. ServletComponentScanRegistrar проанализирует аннотацию @ServletComponentScan внутри, а затем зарегистрирует ServletComponentRegisteringPostProcessor в контейнере Spring, который является BeanFactoryPostProcessor.Он проанализирует, имеет ли отсканированный класс три аннотации: @WebServlet, @WebListener и @WebFilter.Этот тип класса преобразуется в ServletRegistrationBean, FilterRegistrationBean или ServletListenerRegistrationBean, а затем позволяет контейнеру Spring разрешаться.

После использования аннотации @ServletComponentScan в SpringBootApplication Servlet, Filter и Listener могут быть автоматически зарегистрированы напрямую через аннотации @WebServlet, @WebFilter, @WebListener без дополнительного кода.

Вышеупомянутое содержимое является кратким изложением аннотации @ServletComponentScan.Далее мы в основном расскажем, как добавить сервлет, фильтр и прослушиватель в Spring Boot.

Три аннотации @WebServlet, @WebFilter и @WebListener, упомянутые только что, на самом деле являются новыми аннотациями в Servlet 3.0.

Сервлет 3.0 был выпущен со спецификацией Java EE 6 как часть стека спецификаций Java EE 6. Эта версия основана на предыдущей версии (Servlet 2.5) с несколькими новыми функциями, упрощающими разработку и развертывание веб-приложений. Внедрение некоторых из этих функций взволновало разработчиков и получило много похвал от сообщества Java:

  1. Поддержка асинхронной обработки: До Servlet3.0 поток сервлета должен блокироваться до завершения процесса, чтобы повторно экспортировать бизнес-ответ, и, наконец, конец потока сервлета. Servlet3.0 и, при получении запроса, поток сервлета может выполнять трудоемкую операцию, делегированную другому потоку, чтобы завершить их возврат в контейнер без генерации ответа. Для более трудоемких бизнес-процессов, которые значительно уменьшат объем ресурсов сервера и повысят скорость параллельной обработки.
  2. Добавлена ​​поддержка аннотаций: в этой версии добавлено несколько аннотаций для упрощения объявления сервлета, фильтра и прослушивателя, что делает файл описания развертывания web.xml более не нужным в этой версии.
  3. Поддержка подключаемости: Разработчики, знакомые со Struts2, наверняка помнят функции интеграции различных распространенных фреймворков, включая Spring, через плагины. Соответствующие подключаемые модули упаковываются в пакеты JAR и помещаются в путь к классам, а среда выполнения Struts2 может автоматически загружать эти подключаемые модули. Теперь Servlet 3.0 предоставляет аналогичные функции, и разработчики могут легко расширять функции существующих веб-приложений с помощью подключаемых модулей, не изменяя исходные приложения.

Для ознакомления с новыми функциями Servlet 3.0 см.:Подробное объяснение новых возможностей Servlet 3.0

Servlet

До Servlet3.0 мы могли настраивать сервлеты только из файла web.xml. web.xml — это файл в Java EE, который можно дополнительно использовать для описания развертывания приложений, чтобы контейнер сервлетов мог загружать и развертывать приложения.Этот файл можно использовать для объявления сервлетов, сопоставлений доступа к сервлетам, настройки прослушивателей и другой информации, а описать внешние ресурсы.

Например, когда Tomcat запускает и развертывает веб-приложение, он загружает файл web.xml на этапе инициализации, затем загружает сервлет и загружает отношения сопоставления между сервлетом и API и, наконец, предоставляет услуги внешнему миру. . На этом этапе каждый раз, когда мы разрабатываем новые функции и добавляем новые сервлеты, нам нужно модифицировать файл web.xml, который громоздок в настройке.

После Servlet3.0 создание сервлетов может иметь следующие методы:

  • Сначала пользовательский класс наследует HttpServlet, а затем регистрация кода контролируется ServletRegistrationBean. Его также можно реализовать напрямую, реализуя интерфейс ServletContextInitializer;

  • После использования аннотации @ServletComponentScan в SpringBootApplication сервлеты могут быть автоматически зарегистрированы непосредственно через аннотацию @WebServlet без дополнительного кода.

Регистрация сервлетов через код

ServletRegistrationBean объявляется в классе запуска Spring Boot.

класс сервлета:

public class MyServlet extends HttpServlet {
    private static final long serialVersionUID = -8685285401859800066L;

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(">>>>>>>>>>service()<<<<<<<<<<<");
        resp.getWriter().println("receive by MyServlet");
    }
}

Сначала определите сервлет и перепишите службу, чтобы реализовать собственную бизнес-логику.

Класс запуска Spring Boot:

@SpringBootApplication
public class SpbGuide3Application {

    @Bean
    public ServletRegistrationBean servletRegistrationBean(){
        return new ServletRegistrationBean(new MyServlet(),"/servlet/*");
    }

    public static void main(String[] args) {
        SpringApplication.run(SpbGuide3Application.class, args);
    }

}

Затем вставьте экземпляр компонента типа ServletRegistrationBean в контейнер Spring с помощью аннотации @Bean и создайте экземпляр пользовательского сервлета в качестве параметра, чтобы пользовательский сервлет был добавлен в Tomcat.

Запустите проект и откройте URL-адрес: http://localhost:8080/servlet.

Как упоминалось ранее, его также можно реализовать напрямую, реализуя интерфейс ServletContextInitializer следующим образом:

Класс сервлета:

package com.example.servlet;
public class MyServlet2 extends HttpServlet  {
    private static final long serialVersionUID = -8685285401859800066L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(">>>>>>>>>>doGet()<<<<<<<<<<<");
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(">>>>>>>>>>doPost()<<<<<<<<<<<");
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>MyServlet2 doPost()</h1>");
        out.println("</body>");
        out.println("</html>");
    }
}

В HttpServlet есть еще три важных метода: методы service(), doGet() и doPost().По умолчанию в сервлете, независимо от того, отправляете ли вы его с помощью get или post, он будет обработан методом service(), а затем для метода doGet или doPost. Когда пользовательский класс сервлета наследует HttpServlet, если вы не переопределяете метод службы, вам нужно только переопределить метод doGet или doPost.

Реализовать пользовательскую загрузку конфигурации ServletContainerInitializer

public class MyServletContainerInitializer implements ServletContextInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println("启动加载自定义的MyServletContainerInitializer");
        ServletRegistration.Dynamic registration = servletContext.addServlet("servlet2","com.example.servlet.MyServlet2");
        registration.setLoadOnStartup(1);
        registration.addMapping("/servlet2");
    }
}

Глядя на исходный код класса ServletRegistrationBean, мы видим, что этот класс наследует RegistrationBean, а RegistrationBean реализует интерфейс ServletContextInitializer и имеет метод onStartup, поэтому мы можем настроить класс реализации интерфейса ServletContextInitializer для замены ServletRegistrationBean.

ServletContextInitializerЭто интерфейс инициализации, предоставляемый при инициализации контейнера сервлета. Поэтому при инициализации контейнера сервлетов будет получен и запущен метод onStartup во всех экземплярах `FilterRegistrationBean, FilterRegistrationBean, ServletListenerRegistrationBean.

Класс запуска Spring Boot:

@SpringBootApplication
public class SpbGuide3Application {

    /**
     * 使用代码注册Servlet(不需要@ServletComponentScan注解)
     * @return
     */
    @Bean
    public MyServletContainerInitializer servletContainerInitializer(){
        return new MyServletContainerInitializer();
    }

    public static void main(String[] args) {
        SpringApplication.run(SpbGuide3Application.class, args);
    }

}

Запустите проект и откройте URL-адрес: http://localhost:8080/servlet2.

Регистрация сервлетов с аннотациями

Ниже приведен список атрибутов @WebServlet.

Имя свойства тип описывать
name String Указание атрибута имени сервлета эквивалентно, если не указано явно, значением сервлета является полное имя класса.
value String[] Это свойство эквивалентно свойству urlPatterns. Оба свойства нельзя использовать одновременно.
urlPatterns String[] Задает набор шаблонов сопоставления URL-адресов для сервлетов, эквивалентных тегам.
loadOnStartup int Указывает порядок загрузки сервлетов, эквивалентный тегам.
initParams WebInitParam[] Задает набор параметров инициализации сервлета, эквивалентных тегам.
asyncSupported boolean Объявляет, поддерживает ли сервлет асинхронный режим работы, эквивалентный тегу.
description String Информация описания сервлета, эквивалентная метке.
displayName String Отображаемое имя сервлета, обычно используемое с инструментами, эквивалентно метке.

Как видно из приведенной выше таблицы, атрибуты сервлета, которые можно настроить в файле web.xml, можно настроить и в @WebServlet.

В Spring Boot встроенный контейнер сервлетов регистрирует сервлеты, фильтры и всех прослушивателей спецификации сервлета (таких как прослушиватели HttpSessionListener) путем сканирования аннотаций.

Класс сервлета:

@WebServlet(urlPatterns = "/servlet/web",
description = "用注解声明Servlet",
initParams = {//以下都是druid数据源状态监控的参数
        @WebInitParam(name = "allow",value = ""),// IP白名单 (没有配置或者为空,则允许所有访问)
        @WebInitParam(name = "deny",value = ""),// IP黑名单 (存在共同时,deny优先于allow)
        @WebInitParam(name = "loginUsername",value = "hresh"),// 用户名
        @WebInitParam(name = "loginPassword",value = "123456"),// 密码
        @WebInitParam(name = "resetEnable",value = "false")//   禁用HTML页面上的“Reset All”功能
})
public class MyWebServlet extends HttpServlet {
    private static final long serialVersionUID = -8685285401859800066L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(">>>>>>>>>>MyWebServlet doGet()<<<<<<<<<<<");
        ServletConfig config = getServletConfig();
        System.out.println(config.getInitParameter("loginUsername"));
        System.out.println(config.getInitParameter("loginPassword"));
        doPost(req, resp);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(">>>>>>>>>>MyWebServlet doPost()<<<<<<<<<<<");
        resp.setContentType("text/html");
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>MyServlet2 doPost()</h1>");
        out.println("</body>");
        out.println("</html>");
    }
}

Класс запуска Spring Boot:

@SpringBootApplication
@ServletComponentScan
public class SpbGuide3Application {


    public static void main(String[] args) {
        SpringApplication.run(SpbGuide3Application.class, args);
    }

}

Запустите проект и откройте URL-адрес: http://localhost:8080/servlet/web.

Выходные данные loginUsername и loginPassword — это то, что мы установили в аннотации @WebServlet.

Filter

Аннотировать с помощью @WebFilter

Общие свойства @WebFilter

Имя свойства тип описывать
filterName String Указывает атрибут имени фильтра, который эквивалентен,
value String[] Это свойство эквивалентно свойству urlPatterns. Оба свойства нельзя использовать одновременно.
urlPatterns String[] Шаблоны сопоставления URL-адресов, определяющие набор фильтров, эквивалентных тегам.
dispatcherTypes DispatcherType Указывает режим пересылки фильтра. Конкретные значения включают: ASYNC, ERROR, FORWARD, INCLUDE, REQUEST.
initParams WebInitParam[] Задает набор параметров инициализации фильтра, эквивалентных меткам.
asyncSupported boolean Указывает, поддерживает ли фильтр асинхронный режим работы, эквивалентный тегу.
description String Описание фильтра, эквивалентное метке.
displayName String Отображаемое имя фильтра, обычно используемое с инструментами, эквивалентно метке.

При использовании аннотации @WebFilter обнаружено, что в аннотации нет параметра, который может управлять порядком выполнения.Практически установлено, что если вы хотите контролировать порядок выполнения фильтра, вы можетеКонтролируя имя файла фильтраконтролировать. Например: UserLoginFilter.java и ApiLog.java эти два файла фильтра, потому что эти два файла имеютПервая буква А стоит перед У, чтобы фильтр ApiLog выполнялся первым, а затем фильтр UserLoginFilter выполнялся каждый раз при его выполнении, поэтому теперь мы изменим имена двух файлов на Filter0_UserLogin.java и Filter1_ApiLog.java, а затем выполним Filter0_UserLogin, а затем Filter1_ApiLog.

Пример:

Класс фильтра:

@WebFilter(filterName = "MyFilterWithAnnotation",urlPatterns = "/api/*",
initParams = {//以下都是druid数据源状态监控的参数
        @WebInitParam(name = "allow", value = ""),// IP白名单 (没有配置或者为空,则允许所有访问)
        @WebInitParam(name = "deny", value = ""),// IP黑名单 (存在共同时,deny优先于allow)
        @WebInitParam(name = "loginUsername", value = "hresh"),// 用户名
        @WebInitParam(name = "loginPassword", value = "123456"),// 密码
        @WebInitParam(name = "resetEnable", value = "false")//   禁用HTML页面上的“Reset All”功能
})
public class MyFilterWithAnnotation implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(MyFilterWithAnnotation.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("初始化MyFilterWithAnnotation过滤器:", filterConfig.getFilterName());
        System.out.println(filterConfig.getInitParameter("loginUsername"));
        System.out.println(filterConfig.getInitParameter("loginPassword"));

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        logger.info("过滤器开始对请求进行预处理:");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String requestUri = request.getRequestURI();
        System.out.println("请求的接口为:" + requestUri);
        long startTime = System.currentTimeMillis();
        //通过 doFilter 方法实现过滤功能
        filterChain.doFilter(servletRequest,servletResponse);
        // 上面的 doFilter 方法执行结束后用户的请求已经返回
        long endTime = System.currentTimeMillis();
        System.out.println("该用户的请求已经处理完毕,请求花费的时间为:" + (endTime - startTime));
    }

    @Override
    public void destroy() {
        logger.info("销毁过滤器MyFilterWithAnnotation");
    }
}

Пользовательские фильтры проверки контроллера

@RestController
@RequestMapping("/api")
public class MyController {

    @GetMapping("/hello")
    public String getHello() throws InterruptedException {
        Thread.sleep(1000);
        return "hello";
    }
}

Класс запуска Spring Boot:

@SpringBootApplication
@ServletComponentScan
public class SpbGuide3Application {

    public static void main(String[] args) {
        SpringApplication.run(SpbGuide3Application.class, args);
    }

}

Запустите проект и откройте URL-адрес: http://localhost:8080/api/hello.

Веб-сервер отвечает за создание и уничтожение фильтра.Когда веб-приложение запускается, веб-сервер создает экземпляр объекта фильтра и вызывает его метод init для завершения функции инициализации объекта, чтобы подготовиться к перехват последующих пользовательских запросов.Объект фильтра будет создан только один раз, то есть метод init будет выполнен только один раз. С помощью параметров метода init можно получить объект FilterConfig, представляющий текущую информацию о конфигурации фильтра. Точно так же уничтожение объекта Filter выполняется методом destroy, тем самым высвобождая ресурсы, используемые фильтром.

Наследовать HttpFilter

В сочетании с приведенным выше случаем регистрации сервлетов мы не можем не думать, можем ли мы наследовать HttpFilter через пользовательский класс и переписать метод doFilter.Конкретный код выглядит следующим образом:

Класс фильтра:

@Component
public class MyFilter3 extends HttpFilter {
    private static final Logger logger = LoggerFactory.getLogger(MyFilterWithAnnotation.class);

    @Override
    public void init() throws ServletException {
        FilterConfig filterConfig = this.getFilterConfig();
        logger.info("初始化MyFilter3过滤器:", filterConfig.getFilterName());
    }

    @Override
    public void destroy() {
        logger.info("销毁过滤器MyFilterWithAnnotation");
    }

    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String requestUri = request.getRequestURI();
        System.out.println("进入MyFilter3 过滤器");
        System.out.println("请求的接口为:" + requestUri);
        long startTime = System.currentTimeMillis();
        //通过 doFilter 方法实现过滤功能
        super.doFilter(request, response, chain);
        // 上面的 doFilter 方法执行结束后用户的请求已经返回
        long endTime = System.currentTimeMillis();
        System.out.println("该用户的请求已经处理完毕,请求花费的时间为:" + (endTime - startTime));
    }

}

Класс запуска Spring Boot:

@SpringBootApplication
@ServletComponentScan
public class SpbGuide3Application {

    public static void main(String[] args) {
        SpringApplication.run(SpbGuide3Application.class, args);
    }

}

В отличие от сервлетов, нет необходимости добавлять FilterRegistrationBean в класс запуска Spring Boot.

Запустите проект и откройте URL-адрес: http://localhost:8080/api/hello.

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

Определите два класса Filter для реализации функции сортировки фильтра.

@Component
public class MyFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(MyFilter.class);
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("初始化MyFilter过滤器:", filterConfig.getFilterName());
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        logger.info("过滤器开始对请求进行预处理:");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String requestUri = request.getRequestURI();
        System.out.println("请求的接口为:" + requestUri);
        long startTime = System.currentTimeMillis();
        //通过 doFilter 方法实现过滤功能
        filterChain.doFilter(servletRequest,servletResponse);
        // 上面的 doFilter 方法执行结束后用户的请求已经返回
        long endTime = System.currentTimeMillis();
        System.out.println("该用户的请求已经处理完毕,请求花费的时间为:" + (endTime - startTime));
    }

    @Override
    public void destroy() {
        logger.info("销毁过滤器MyFilter");
    }
}

Файл MyFilter2 аналогичен приведенному выше коду, только назван по-другому.

Зарегистрируйте пользовательские фильтры в конфигурации

@Configuration
public class MyFilterConfig {
    @Autowired
    MyFilter myFilter;

    @Autowired
    MyFilter2 myFilter2;

    @Bean
    public FilterRegistrationBean<MyFilter> setUpMyFilter(){
        FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>();

        //setOrder 方法可以决定 Filter 的执行顺序
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.setFilter(myFilter);

        filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/*")));

        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean<MyFilter2> setUpMyFilter2(){
        FilterRegistrationBean<MyFilter2> filterRegistrationBean = new FilterRegistrationBean<>();

        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.setFilter(myFilter2);

        filterRegistrationBean.setUrlPatterns(new ArrayList<>(Arrays.asList("/api/* ")));

        return filterRegistrationBean;
    }
}

Зарегистрируйте пользовательский фильтр в конфигурации, и порядок выполнения фильтра можно определить с помощью метода setOrder объекта FilterRegistrationBean.

Класс запуска Spring Boot:

@SpringBootApplication
@ServletComponentScan
public class SpbGuide3Application {

    public static void main(String[] args) {
        SpringApplication.run(SpbGuide3Application.class, args);
    }

}

Запустите проект и откройте URL-адрес: http://localhost:8080/api/hello.

Listener

@WebListener

После сервлета 3.0 нам не нужно настраивать прослушиватель в web.xml, нам просто нужно добавить аннотацию @WebListener, чтобы добиться этого.

Эта аннотация используется для объявления класса слушателем. Класс, аннотированный @WebListener, должен реализовывать хотя бы один из следующих интерфейсов:

имя интерфейса эффект
ServletContextListener Используется для мониторинга запуска и завершения работы веб-приложений.
ServletContextAttributeListener Используется для прослушивания изменений атрибутов в области ServletContext (приложение).
ServletRequestListener Используется для прослушивания запросов пользователей
ServletRequestAttributeListener Используется для прослушивания изменений атрибутов в области ServletRequest (запрос).
HttpSessionListener Используется для отслеживания начала и окончания сеанса пользователя.
HttpSessionAttributeListener Используется для отслеживания изменений свойств в области HttpSession (сеанс).

Класс слушателя:

@WebListener
public class ContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("application started");
        System.out.println(sce.getServletContext().getServerInfo());
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("application stopped");
    }
}

Класс запуска Spring Boot:

@SpringBootApplication
@ServletComponentScan
public class SpbGuide3Application {

    public static void main(String[] args) {
        SpringApplication.run(SpbGuide3Application.class, args);
    }

}

Запуск проекта приводит к:

Суммировать

Вышеупомянутое содержание суммирует, как добавить Servlet, FIlter и Listener в Spring Boot.Servlet3.0 упоминается много раз.Для объяснения этой части мы обобщим соответствующий контент позже.