仍是爲了解決上次的Hibernate
攔截器問題,@Autowired
無論用了。java
如下是部分代碼,因本文主要解決手動從容器中獲取對象的問題,因此將validateWebAppMenuRoute
方法的業務邏輯刪除,咱們只打印webAppMenuService
,來判斷咱們的注入是否成功。web
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
方法獲取其中某個對象。spring
@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
時寫的代碼,咱們手動根據咱們的註解配置本身生成的上下文環境,因此咱們能夠任性地去操做咱們的上下文。segmentfault
然而,在Spring Boot
項目中。app
@SpringBootApplication public class ResourceApplication { public static void main(String[] args) { SpringApplication.run(ResourceApplication.class, args); } }
點開這行run
方法,會發現其返回值就是咱們的context
,這是一種最簡單的獲取上下文的辦法。可是一看到這麼優雅的一行代碼,實在不忍心破壞,永遠保持main
只有這一行,這太美了。ide
這是在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); } }
執行保存菜單的單元測試,結果以下:this
注入成功,而且是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
爲咱們注入了。