公司用的图片验证码模块用的是很多年前的Servlet,我的需求需要在Servlet中调用一个Spring的bean对象,但Servlet和Sping bean的生命周期不同、context也不同:
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));
}
}
因为在@WebServlet参数中指定了url,所以这里不用再指定
@Configuration
public class MyConfig{
@Bean
public ServletRegistrationBean registration(){
ServletRegistrationBean servletRegistration = new ServletRegistrationBean
(new MyServlet());//因为在@WebServlet参数中指定了url,所以这里不用再指定
return servletRegistration;
}
}
此时的Servlet并没有作为bean加入到IOC,只是SpringMVC添加了这个Servlet写的接口。
如果想在MyServlet中使用@Autowired,则有如下两种方法
见名知意,这是用来解析@Autowired的工具类,源码我看了下,用于解析@Autowired和@Value,但并不局限于Bean初始化的BeanPostProcessor阶段,可以对任意普通类进行增强,本质就是从IOC中拿到这个指针
为了不影响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);
}
}
其实上面那个解析@Autowired也仅仅是从IOC中拿到bean的指针,与Spring的启动过程无关
同时我们知道Spring的三级缓存机制,那么就可以在Spring启动的第2个阶段(BeanFactoryPostProcessor
)直接把待使用的bean指针给到Servlet,即便此时待使用的bean还未初始化完毕
此时可以去掉@Autowired
和SpringBeanAutowiringSupport.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));
}
}
@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;
}
}
上面的第三点,Servlet注册 和 所调用的Bean 都在同一个Config文件,因此可以自由引用,那如果所调用的Bean是通过@Component注册的呢?
@Component
public class MyBean {
private String username = "111";
private String password = "222";
}
这里也是因为大家都是在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;
}
}
这个接口是给Bean使用的,用于获取指定的bean对象,言下之意实现这个接口,想要生效(获取到applicationContext)自己本身也必须是一个bean
通过这个工具类可以在任何一个类中使用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);
}
}
@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));
}
}