130.【Spring注解_AOP】(五)

简介: 130.【Spring注解_AOP】

2.ServletContainerInitalizer

容器在启动应用的时候,会扫描当前应用每一个jar包里面的src/main/webapp/META-INF/services/javax.servlet.ServletContainerInitializer指定实现的类,启动并运行这个实现类的方法。

(1).创建我们的指定扫描的文件并编辑

(2).编写被扫描的文件信息
package com.jsxs.servlet;
import com.jsxs.services.HelloService;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.util.Set;
/**
 * @Author Jsxs
 * @Date 2023/8/27 10:41
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyServletContainerInitalizer
 * @Description: TODO
 * @Version 1.0
 */
// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口,抽象类)传递过来
@HandlesTypes(value = {HelloService.class})  // 传入我们感兴趣的类型并赋值给set 🚹
public class MyServletContainerInitializer implements ServletContainerInitializer {  // 1.继承接口并实现方法 ⭐
    // 2.应用启动的时候会调用这个方法
    /**
     * @param set : 负责接受我们感兴趣类型的所有子类型 即HelloService🚹
     * @param servletContext: 代表当前web应用的ServletContext,一个web应用相当于一个ServletContext
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型"+set);
        for (Class<?> aClass : set) {
            System.out.println(aClass);
        }
    }
}

1.抽象类

package com.jsxs.services;
/**
 * @Author Jsxs
 * @Date 2023/8/27 10:54
 * @PackageName:com.jsxs.services
 * @ClassName: HelloServiceAbart
 * @Description: TODO  父接口的抽象子类
 * @Version 1.0
 */
public abstract class HelloServiceAbart implements HelloService{
}

2.子接口: 接口继承接口

package com.jsxs.services;
/**
 * @Author Jsxs
 * @Date 2023/8/27 10:53
 * @PackageName:com.jsxs.services
 * @ClassName: HelloServiceExt
 * @Description: TODO  父接口的子接口
 * @Version 1.0
 */
public interface HelloServiceExt extends HelloService{
}

3.实现类

package com.jsxs.services;
/**
 * @Author Jsxs
 * @Date 2023/8/27 10:56
 * @PackageName:com.jsxs.services
 * @ClassName: HelloServiceImpi
 * @Description: TODO  普通类继承接口
 * @Version 1.0
 */
public class HelloServiceImpi implements HelloService{
}
(3).将要被扫描的类全限定名写入文件中
com.jsxs.servlet.MyServletContainerInitializer
• 1

(4).运行结果

我们发现不管是子类 子接口 还是抽象类 实现类都被打印出全限定名了。

3.ServletContext 注册三大组件

过滤器 + 监听器 + Servlet

1.要注册的过滤器

package com.jsxs.servlet;
import javax.servlet.*;
import java.io.IOException;
/**
 * @Author Jsxs
 * @Date 2023/8/27 12:28
 * @PackageName:com.jsxs.servlet
 * @ClassName: UserFilter
 * @Description: TODO  过滤器: 使用的是Servlet的包
 * @Version 1.0
 */
public class UserFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
        // 过滤请求,假如为false那么就拦截,true就放行
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 过滤请求
        System.out.println("UserFilter...");
        // 放行
        filterChain.doFilter(servletRequest,servletResponse);
    }
    @Override
    public void destroy() {
    }
}

2.要注册的监听器

package com.jsxs.servlet;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
 * @Author Jsxs
 * @Date 2023/8/27 12:34
 * @PackageName:com.jsxs.servlet
 * @ClassName: UserListener
 * @Description: TODO  监听项目的启动和停止
 * @Version 1.0
 */
public class UserListener implements ServletContextListener {
    // 监听项目的开始和初始化
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("UserListener---项目初始化成功....");
    }
    // 监听销毁
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("UserListener---项目关闭成功....");
    }
}

3. 要注册的Servlet

package com.jsxs.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * @Author Jsxs
 * @Date 2023/8/27 12:39
 * @PackageName:com.jsxs.servlet
 * @ClassName: UserServlet
 * @Description: TODO
 * @Version 1.0
 */
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("tomcate...");
    }
}

4.进行注册的操作

package com.jsxs.servlet;
import com.jsxs.services.HelloService;
import javax.servlet.*;
import javax.servlet.annotation.HandlesTypes;
import java.util.EnumSet;
import java.util.Set;
/**
 * @Author Jsxs
 * @Date 2023/8/27 10:41
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyServletContainerInitalizer
 * @Description: TODO
 * @Version 1.0
 */
// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口)传递过来
@HandlesTypes(value = {HelloService.class})  // 传入我们感兴趣的类型并赋值给set 🚹
public class MyServletContainerInitializer implements ServletContainerInitializer {  // 1.继承接口并实现方法 ⭐
    // 2.应用启动的时候会调用这个方法
    /**
     * @param set : 负责接受我们感兴趣类型的所有子类型 即HelloService🚹
     * @param servletContext: 代表当前web应用的ServletContext,一个web应用相当于一个ServletContext
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型"+set);
        for (Class<?> aClass : set) {
            System.out.println(aClass);
        }
        // 这里我们向容器中注册监听组件和过滤组件 ⭐⭐
            // (1).添加Servlet组件 -> 具体的映射请求 ⭐⭐⭐
        ServletRegistration.Dynamic userServlet = servletContext.addServlet("UserServlet", new UserServlet());
            // 配置要映射请求的路径
        userServlet.addMapping("/user");
            // (2).过滤器⭐⭐⭐
        FilterRegistration.Dynamic userFilter = servletContext.addFilter("UserFilter", UserFilter.class);
            // 配置映射的信息
        userFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*"); // 请求方式 是否为true 映射路径
            // (3).添加监听器⭐⭐⭐
        servletContext.addListener(UserListener.class);
    }
}

4.SpringMVC整合分析

1.web容器在启动的时候,会扫描每个jar包下的src/main/webapp/META-INF/services/javax.servlet.ServletContainerInitializer的全限定名
2.根据全限定名加载这个文件指定的类
3.Spring的应用一启动会加载感兴趣的WebApplicationInitializer下的所有组件
4.并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)
  (1).AbstractContextLoaderInitializer 创建跟容器,createRootApplicationContext();
  (2).AbstractDispatcherServletInitializer 
    1.创建一个web的ioc容器 createServletApplicationContext()
    2.创建一个DispatchServlet  createDispatchServlet()
    3.将创建的DispatchServlet添加到ServletContext中
  (3). AbstractAnnotationConfigDispatcherServletInitializer注解方式配置的DispatcherServlet初始化器 
    1.创建更容器: createRootApplicationContext()
      getRootConfigClasses(); 传入一个配置类
    2.创建web的ioc容器 createServletApplicationContext()
总结:
  以注解的方式启动SpringMvc,继承AbstractAnnotationConfigDispatcherServletInitializer实现抽象发发指定DipatcherServlet的配置信息

5.Servlet 异步

(1).AsyncContext 实现异步处理

有时候我们在处理一个数据的时候,会有相对应的时间限制。在实际的用户体验中可能会产生很糟糕的效应。所以我们要使用异步的方式来减少时间差。

package com.jsxs.servlet;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * @Author Jsxs
 * @Date 2023/8/27 16:10
 * @PackageName:com.jsxs.servlet
 * @ClassName: HelloAsyncServlet
 * @Description: TODO
 * @Version 1.0
 */
@WebServlet(value = "/async",asyncSupported = true) // 设置映射的的路径和支持异步 ⭐
public class HelloAsyncServlet extends HttpServlet {
    @Override
    protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final AsyncContext asyncContext = req.startAsync(); // 2.开启异步模式 ⭐⭐
        System.out.println("主线程----------------------");
        // 3.通过使用线程的方式进行异步处理 ⭐⭐⭐
        asyncContext.start(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello-------------------------------------");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 4. 通知异步处理完毕 ⭐⭐⭐⭐
                asyncContext.complete();
                // 5.获取到异步的上下文 ⭐⭐⭐⭐⭐
                AsyncContext asyncContext1 = req.getAsyncContext();
                // 6.获取响应 ⭐⭐⭐⭐⭐⭐
                ServletResponse response = asyncContext1.getResponse();
                try {
                    response.getWriter().write("hello async...");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

(2).Callable 实现异步处理
package com.jsxs.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.concurrent.Callable;
/**
 * @Author Jsxs
 * @Date 2023/8/27 16:37
 * @PackageName:com.jsxs.controller
 * @ClassName: AsyncController
 * @Description: TODO
 * @Version 1.0
 */
@Controller
public class AsyncController {
    @RequestMapping("/async")
    @ResponseBody
    public Callable<String> async01(){
        System.out.println("主线程开始"+System.currentTimeMillis());
        Callable<String> callable=new Callable<String>(){
            @Override
            public String call() throws Exception {
                System.out.println("副线程开始A"+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("副线程结束B"+System.currentTimeMillis());
                return "Callabled";
            }
        };
        System.out.println("主线程结束"+System.currentTimeMillis());
        return callable;
    }
}
(3).DeferredResult 实现异步

我们以创建订单为例,创建订单的请求一进来,应用1就要启动一个线程,来帮我们处理这个请求。如果假设应用1并不能创建订单,创建订单需要应用2来完成,那么此时应该怎么办呢?应用1可以把创建订单的消息存放在消息中间件中,比如RabbitMQ、Kafka等等,而应用2就来负责监听这些消息中间件里面的消息,一旦它发现有创建订单这个消息,那么它就进行相应处理,然后将处理完成后的结果,比如订单的订单号等等,再次存放在消息中间件中,接着应用1再启动一个线程,例如线程2,来监听消息中间件中的返回结果,只要订单创建完毕,它就会拿到返回的结果(即订单的订单号),最后将其响应给客户端。

1.创建订单

package com.meimeixia.controller;
import java.util.concurrent.Callable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
@Controller
public class AsyncController {
  @ResponseBody
  @RequestMapping("/createOrder")
  public DeferredResult<Object> createOrder() {
      /*
       * 在创建DeferredResult对象时,可以像下面这样传入一些参数哟!
       * 
       * 第一个参数(timeout): 超时时间。限定(请求?)必须在该时间内执行完,如果超出时间限制,那么就会返回一段错误的提示信息(timeoutResult)
       * 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息
       */
    DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail..."); ⭐
    return deferredResult;
  }
  @ResponseBody
  @RequestMapping("/async01")
  public Callable<String> async01() {
    System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
    Callable<String> callable = new Callable<String>() {
      @Override
      public String call() throws Exception {
        System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
        Thread.sleep(2000); // 我们来睡上2秒 
        System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
        // 响应给客户端一串字符串,即"Callable<String> async01()"
        return "Callable<String> async01()";
      }
    };
    System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
    return callable;
  }
}

很明显这是超出时间限制了,应该是相当于创建订单失败了吧!那接下来,我们应该怎么办呢?真令人头疼😭

OK,我们不妨来模拟一个队列。首先新建一个类,例如DeferredResultQueue,如下所示。

2.模拟队列

package com.meimeixia.service;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.springframework.web.context.request.async.DeferredResult;
public class DeferredResultQueue {
  // DeferredResult对象临时保存的地方
  private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>(); ⭐
  // 临时保存DeferredResult对象的方法 ⭐⭐
  public static void save(DeferredResult<Object> deferredResult) {
    queue.add(deferredResult); 
  }
  // 获取DeferredResult对象的方法 ⭐⭐⭐
  public static DeferredResult<Object> get() {
    /*
     * poll():检索并且移除,移除的是队列头部的元素
     */
    return queue.poll();
  }
}

然后,修改一下AsyncController中的createOrder方法。上面我也已经说过了,该方法并不能真正地来处理创建订单的请求。即使如此,那也没关系,因为我们可以在该方法中先把new出来的DeferredResult对象临时保存起来。

3.将正在创建中的订单临时保存起来

package com.meimeixia.controller;
import java.util.concurrent.Callable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import com.meimeixia.service.DeferredResultQueue;
@Controller
public class AsyncController {
  @ResponseBody
  @RequestMapping("/createOrder")
  public DeferredResult<Object> createOrder() {
    /*
    * 在创建DeferredResult对象时,可以像下面这样传入一些参数哟!
    * 
    * 第一个参数(timeout): 超时时间。限定(请求?)必须在该时间内执行完,如果超出时间限制,那么就会返回一段错误的提示信息(timeoutResult)
    * 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息
    */
    DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail...");
    DeferredResultQueue.save(deferredResult); ⭐// 保存起来
    return deferredResult;
  }
  @ResponseBody
  @RequestMapping("/async01")
  public Callable<String> async01() {
    System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
    Callable<String> callable = new Callable<String>() {
      @Override
      public String call() throws Exception {
        System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
        Thread.sleep(2000); // 我们来睡上2秒 
        System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
        // 响应给客户端一串字符串,即"Callable<String> async01()"
        return "Callable<String> async01()";
      }
    };
    System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
    return callable;
  }
}

当然了,实际开发中应该有一个别的线程来专门监听这个事,啥事?我猜应该是在其他的地方临时保存DeferredResult对象这件事吧!不过,我们在这儿并不会这样做,而是再在AsyncController中编写一个方法,例如create,该方法才是真正来处理创建订单的请求的。

4.真正创建订单号(监听)

@ResponseBody
@RequestMapping("/create")
public String create() {
    // 在这模拟创建订单
    String order = UUID.randomUUID().toString();
    /*
     * 如果我们想在上一个请求(即createOrder)中使用订单,那么该怎么办呢?从临时保存DeferredResult对象的地方获取
     * 到刚才保存的DeferredResult对象,然后调用其setResult方法设置结果,例如设置订单的订单号
     */
    DeferredResult<Object> deferredResult = DeferredResultQueue.get();  // ⭐
    deferredResult.setResult(order); // 设置结果
    // 这儿给客户端直接响应"success===>订单号"这样的字符串,不要再跳转页面了
    return "success===>" + order;
}

至此,可以看到,只要客户端发送一个createOrder请求进来创建订单,那么服务端就会先将new出来的DeferredResult对象临时保存起来。等到create方法触发并把订单号设置进去之后,在createOrder方法中就会立即得到返回结果,即订单号。

最后,我们来重启项目进行测试。重启成功之后,先来访问createOrder请求,以便来创建订单,但是订单必须得在3秒内创建完,所以一旦访问了createOrder请求后,你必须立即访问create请求来真正创建订单,而且至少得在3秒内完成。

这时,你会看到什么结果呢?可以看到访问create请求之后,直接给浏览器页面响应了一个success===>4e7e3e4c-27d2-4989-87c1-00d545e05feb这样的字符串,其中4e7e3e4c-27d2-4989-87c1-00d545e05feb就是所创建订单的订单号,如下图所示。

切到访问createOrder请求的浏览器窗口之后,你也可以在浏览器页面中看到所创建订单的订单号,即4e7e3e4c-27d2-4989-87c1-00d545e05feb,如下图所示。

相关文章
|
10天前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
20天前
|
Java API 数据安全/隐私保护
(工作经验)优雅实现接口权限校验控制:基于自定义注解、AOP与@ConditionalOnProperty配置开关的通用解决方案
(工作经验)优雅实现接口权限校验控制:基于自定义注解、AOP与@ConditionalOnProperty配置开关的通用解决方案
50 1
|
13天前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
25 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
23天前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
57 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
18天前
|
XML Java 数据格式
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
本文介绍了如何使用Spring框架的注解方式实现AOP(面向切面编程)。当目标对象没有实现接口时,Spring会自动采用CGLIB库进行动态代理。文中详细解释了常用的AOP注解,如`@Aspect`、`@Pointcut`、`@Before`等,并提供了完整的示例代码,包括业务逻辑类`User`、配置类`SpringConfiguration`、切面类`LoggingAspect`以及测试类`TestAnnotationConfig`。通过这些示例,展示了如何在方法执行前后添加日志记录等切面逻辑。
48 2
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
|
1天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
13 2
|
23小时前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
13 1
|
15天前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
20天前
|
XML Java 数据库
Spring boot的最全注解
Spring boot的最全注解
|
21天前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。