问题描述
还是为了解决上次的Hibernate
拦截器问题,@Autowired
不管用了。
以下是部分代码,因本文主要解决手动从容器中获取对象的问题,所以将validateWebAppMenuRoute
方法的业务逻辑删除,我们只打印webAppMenuService
,来判断我们的注入是否成功。
public class WebAppMenuInterceptor extends EmptyInterceptor {
private WebAppMenuService webAppMenuService;
@Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
if (entity instanceof WebAppMenu) {
this.validateWebAppMenuRoute((WebAppMenu) entity);
}
return super.onSave(entity, id, state, propertyNames, types);
}
private void validateWebAppMenuRoute(WebAppMenu webAppMenu) {
System.out.println(webAppMenuService);
}
}
获取上下文
想要获取容器中的对象,我们想的就是先获取容器(上下文),然后再调用容器的getBean
方法获取其中某个对象。
@ComponentScan("com.mengyunzhi.spring")
public class Application {
public static void main(String []args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
TestService testService = context.getBean(TestService.class);
}
}
这是我们学习Spring IOC
时写的代码,我们手动根据我们的注解配置自己生成的上下文环境,所以我们可以任性地去操作我们的上下文。
然而,在Spring Boot
项目中。
@SpringBootApplication
public class ResourceApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class, args);
}
}
点开这行run
方法,会发现其返回值就是我们的context
,这是一种最简单的获取上下文的办法。但是一看到这么优雅的一行代码,实在不忍心破坏,永远保持main
只有这一行,这太美了。
实现接口,获取上下文
这是在Spring
揭秘一书中截下的一段话:
所以,我们只要实现上面的任一一个接口,就能获取到注入的上下文实例。
package com.mengyunzhi.measurement.context;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* 上下文,用于获取本项目的上下文/容器
* 在@Autowired失效时可使用上下文手动获取相关对象
*/
@Component
public class ResourceApplicationContext implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return context;
}
}
获取对象
对拦截器的代码修改如下,新建inject
方法,用于注入无法自动装配的Bean
。
public class WebAppMenuInterceptor extends EmptyInterceptor {
private WebAppMenuService webAppMenuService;
private void inject() {
if (null == this.webAppMenuService) {
this.webAppMenuService = ResourceApplicationContext.getApplicationContext().getBean(WebAppMenuService.class);
}
}
@Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
if (entity instanceof WebAppMenu) {
this.validateWebAppMenuRoute((WebAppMenu) entity);
}
return super.onSave(entity, id, state, propertyNames, types);
}
private void validateWebAppMenuRoute(WebAppMenu webAppMenu) {
this.inject();
System.out.println(webAppMenuService);
}
}
执行保存菜单的单元测试,结果如下:
注入成功,并且是Bean
默认的单例模式。
思考
为什么我们一直使用的@Autowired
注解失效了呢?
找了一晚上,终于找到失效的问题了,但是自己的问题没解决,却顺带把历史上遗留的一个问题解决了。
用户认证拦截器
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
Logger logger = Logger.getLogger(TokenInterceptor.class.getName());
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("在触发action前,先触发本方法");
logger.info("获取路径中的token值");
String tokenString = request.getParameter("token");
UserService userService = new UserServiceImpl();
logger.info("根据token获取对应的用户");
User user = userService.getUserByToken(tokenString);
if (user == null) {
throw new SecurityException("传入的token已过期");
}
logger.info("注册spring security,进行用户名密码认证");
Authentication auth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
SecurityContextHolder.getContext().setAuthentication(auth);
return true;
}
}
这是同一个包下的拦截器,该拦截器用于拦截请求,使用Spring Security
对用户进行认证。这里的UserService
并不是@Autowired
进来的,而是new
出来的,可能前辈在写这块的时候也遇到了注入失败的问题。
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
static private Logger logger = Logger.getLogger(WebConfig.class.getName());
/**
* 配置拦截器
* @param interceptorRegistry
*/
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
logger.info("添加拦截器/**/withToken/**, 所以包含/withToken字符串的url都要被TokenInterceptor拦截");
interceptorRegistry.addInterceptor(new TokenInterceptor()).addPathPatterns("/**/withToken/**");
}
}
可能第一次看这段添加拦截器的代码可能有些摸不着头绪,其实我们点开源码,可能就会理解得更深刻。
没错,这就是我们学习过的观察者模式。
其实很简单,调用interceptorRegistry
添加拦截器的方法,添加一个我们写的拦截器进去。这里是new
出来的拦截器。
new
出来的东西,Spring
是不管的,所以,如果我们是new
的拦截器,那@Autowired
注解标注的属性就是空。所以引发@Autowired
失效。
为了验证我们的猜想,测试一下。
1.我们先删除new UserServiceImpl()
,将其改为@Autowired
。
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
@Autowired
private UserService userService;
Logger logger = Logger.getLogger(TokenInterceptor.class.getName());
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
logger.info("在触发action前,先触发本方法");
logger.info("获取路径中的token值");
String tokenString = request.getParameter("token");
logger.info("根据token获取对应的用户");
User user = userService.getUserByToken(tokenString);
if (user == null) {
throw new SecurityException("传入的token已过期");
}
logger.info("注册spring security,进行用户名密码认证");
Authentication auth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
SecurityContextHolder.getContext().setAuthentication(auth);
return true;
}
}
单元测试,应该出错,因为我们是new
的拦截器,而在拦截器中@Autowired
,Spring
是不管里这个注入的。并且错误信息就是我们调用userService
的那行代码。
2.将new
的拦截器改为@Autowired
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
private TokenInterceptor tokenInterceptor;
static private Logger logger = Logger.getLogger(WebConfig.class.getName());
/**
* 配置拦截器
* @param interceptorRegistry
*/
@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
logger.info("添加拦截器/**/withToken/**, 所以包含/withToken字符串的url都要被TokenInterceptor拦截");
interceptorRegistry.addInterceptor(tokenInterceptor).addPathPatterns("/**/withToken/**");
}
}
单元测试,通过。
解决了这个问题,因为本问题是由我们在配置拦截器时一个new
的失误造成的。但是Hibernate
的拦截器我们并没有自己配置,扫描到就会调用,这里猜测问题应该出现在Hibernate
在实例化我自定义的拦截器时使用的是new
或某种其他方式,反正应该不是在容器中获取的,所以得到的拦截器对象的@Autowired
相关属性就为空。
总结
@Autowired
描述对象之间依赖的关系,这里的关系指的是Spring IOC
容器中相关的关系。
所以,其实这个问题并不是我们的注解失效了,其实如果我们从容器中拿一个WebAppMenuInterceptor
的拦截器对象,它其实是已经将WebAppMenuService
注入进来的,只不过Hibernate
调用我们的拦截器时,采用并非从容器中获取的方式,只是“看起来”,注解失效了。
配置的时候能@Autowired
就不用new
,如果第三方库中new
实例化我们的实现,我们需要使用手动从容器中获取的方法,就不能让Spring
为我们注入了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。