• Servlet使用ioc中的bean


    公司用的图片验证码模块用的是很多年前的Servlet,我的需求需要在Servlet中调用一个Spring的bean对象,但Servlet和Sping bean的生命周期不同、context也不同:

    • 无法直接在Servlet中使用@Autowired来获取bean对象(不在IOC中,BeanPostProcessor解析不了
    • Spring启动过程中不会主动发现Servlet接口(即便是配置了如@WebServlet),通过ServletRegistrationBean配置只能让Spring发现这个Servlet接口,但不会让Servlet添加到IOC作为bean(因此还是不能使用@Autowired)
    • 2、3、4、5都是“在Servlet使用bean”的解决方法,Filter也可以用相同的逻辑,我在这个需求中使用的是第4个方法(因为本身就已经有了注册Servlet接口的配置,只需通过已有的构造器添加指针即可)

    1.Servlet部分

    1.1Servlet接口

    Servlet通过@WebServlet表明这是一个接口,但Spring启动过程中不能直接发现@WebServlet,所以需要通过ServletRegistrationBean在Spring启动过程中注册接口

    @AllArgsConstructor
    @NoArgsConstructor
    @WebServlet("hello")
    public class MyServlet extends HttpServlet {
    
      @Autowired
      MyBean myBean;
    
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("此时mybean是否为空" + (myBean == null));
      }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    1.2ServletRegistrationBean注册接口

    因为在@WebServlet参数中指定了url,所以这里不用再指定

    @Configuration
    public class MyConfig{
      @Bean
      public ServletRegistrationBean registration(){
        ServletRegistrationBean servletRegistration = new ServletRegistrationBean
        (new MyServlet());//因为在@WebServlet参数中指定了url,所以这里不用再指定
        return servletRegistration;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.3注意点

    此时的Servlet并没有作为bean加入到IOC,只是SpringMVC添加了这个Servlet写的接口。
    如果想在MyServlet中使用@Autowired,则有如下两种方法

    2.SpringBeanAutowiringSupport工具类

    见名知意,这是用来解析@Autowired的工具类,源码我看了下,用于解析@Autowired和@Value,但并不局限于Bean初始化的BeanPostProcessor阶段,可以对任意普通类进行增强,本质就是从IOC中拿到这个指针

    2.1processInjectionBasedOnServletContext()

    为了不影响Servlet正常使用,我选择将其放在init()方法中

    重写init()方法,调用作用于BeanPostProcessor的工具类SpringBeanAutowiringSupport.processInjectionBasedOnServletContext()
    当请求第一次过来时就能在Servlet(非Spring的Bean中)解析@Autowired了

    @AllArgsConstructor
    @NoArgsConstructor
    @WebServlet("hello")
    public class MyServlet extends HttpServlet {
    
      @Autowired
      MyBean myBean;
    
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("此时mybean是否为空" + (myBean == null));
      }
    
      //第一次请求这个Servlet接口时会触发一次
      @Override
      public void init(ServletConfig config) throws ServletException {
        System.out.println("解析mybena之前:" + (myBean == null));
        //允许Servlet上下文解析@Autowired
        ServletContext servletContext = config.getServletContext();
        SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, servletContext);
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    3.注册Servlet接口时直接通过构造器获取指针

    其实上面那个解析@Autowired也仅仅是从IOC中拿到bean的指针,与Spring的启动过程无关
    同时我们知道Spring的三级缓存机制,那么就可以在Spring启动的第2个阶段(BeanFactoryPostProcessor直接把待使用的bean指针给到Servlet即便此时待使用的bean还未初始化完毕

    3.1Servlet结构修改

    此时可以去掉@AutowiredSpringBeanAutowiringSupport.processInjectionBasedOnServletContext()

    @AllArgsConstructor
    @NoArgsConstructor
    @WebServlet("hello")
    public class MyServlet extends HttpServlet {
    //不用加@Autowired
      MyBean myBean;
    
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("此时mybean是否为空" + (myBean == null));
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.2通过构造器获取指针

    @Configuration
    public class MyConfig implements ApplicationContextAware {
    
      @Bean
      public MyBean myBean() {
        MyBean myBean = new MyBean("张三");
        return myBean;
      }
    
      @Bean("myServlet2")
      public ServletRegistrationBean registration(MyBean myBean) {
        ServletRegistrationBean servletRegistration = new ServletRegistrationBean(new MyServlet(myBean));
        return servletRegistration;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    4.所用的bean是通过@Component配的,而不是@Configuration+@Bean配置的

    上面的第三点,Servlet注册所调用的Bean 都在同一个Config文件,因此可以自由引用,那如果所调用的Bean是通过@Component注册的呢?

    4.1@Component注册

    @Component
    public class MyBean {
    
      private String username = "111";
      private String password = "222";
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.2配置类

    这里也是因为大家都是在Spring启动过程中完成的,有三级缓存的机制下,也是可以通过这种方式让Servlet拿到其他包下的bean对象

    @Configuration
    public class MyConfig  {
    
      @Autowired
      MyBean mybean;
    
      @Bean("myServlet2")
      public ServletRegistrationBean registration(MyBean myBean) {
        ServletRegistrationBean servletRegistration = new ServletRegistrationBean(new MyServlet(myBean));
        return servletRegistration;
      }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    5.ApplicationContextAware接口

    这个接口是给Bean使用的,用于获取指定的bean对象,言下之意实现这个接口,想要生效(获取到applicationContext)自己本身也必须是一个bean

    5.1工具类

    通过这个工具类可以在任何一个类中使用Bean对象了(获取Bean的指针)

    @Component
    public class SpringContextUtils implements ApplicationContextAware {
      private static ApplicationContext applicationContext;
    
      @Override
      public void setApplicationContext(ApplicationContext context) throws BeansException {
        applicationContext = context;
      }
    
      //静态加载applicationContext
      public static ApplicationContext getApplicationContext() {
        return applicationContext;
      }
      //通过反射获取Bean
      public static  T getBean(Class requiredType){
        return getApplicationContext().getBean(requiredType);
      }
      //通过id名获取bean
      public static  T getBean(String name){
        return (T) getApplicationContext().getBean(name);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    5.2使用示例

    @AllArgsConstructor
    @NoArgsConstructor
    @WebServlet("hello")
    public class MyServlet extends HttpServlet {
    
      MyBean myBean;
    
      @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("此时mybean是否为空" + (myBean == null));
      }
      
      @Override
      public void init() throws ServletException {
        System.out.println("在通过ApplicationContextAware获取Bean之前:" + (myBean == null));
        MyBean myBean = SpringContextUtils.getBean(MyBean.class);
        this.myBean = myBean;
        System.out.println("在通过ApplicationContextAware获取Bean之后:" + (this.myBean == null));
    
      }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    为什么需要对jvm进行优化,jvm运行参数之标准参数
    NOI / 1.2编程基础之变量定义、赋值及转换
    ffmpeg 枚举decoders, encoders 分析
    Win10电脑任务栏没有蓝牙图标的简单解决方法
    【python】len()、str()、int()和float()函数
    多线程-线程与进程、线程的实现方式(第十八天)
    SpringBoot2 +vue2 + shiro 集成山东通 auth2 方式单点登陆
    设计模式- 模板方法模式(Template Method Pattern) 结构|原理|优缺点|场景|示例
    第十三届蓝桥杯大赛软件赛决赛(Java 大学A组)
    游戏数据分析实战 | 学习笔记
  • 原文地址:https://blog.csdn.net/m0_56079407/article/details/127595073