Spring Boot集成测试中无法注入自定义Filter的问题
问题:Spring集成测试中无法实例化自定义Filter导致测试失败
我在Spring控制器中定义了一个POST方法,尝试用TestRestTemplate的exchange方法做集成测试,但运行测试时无法实例化自定义Filter,导致测试失败。
控制器代码(Controller.java)
@RequestMapping(path = "/rest/projects") @RestController public class Controller { public static final Logger LOGGER = LoggerFactory.getLogger(Controller.class); private JwtTokenProvider jwtTokenProvider; private MailsService mailsService; @Autowired public Controller(MailsService mailsService, JwtTokenProvider jwtTokenProvider) { this.mailsService = mailsService; this.jwtTokenProvider = jwtTokenProvider; } @PostMapping(value = "/{projectId}/mails/filter/{type}") public ListDto getMails(@PathVariable("type") String mailType, @PathVariable("projectId") String projectId, @RequestParam(value = "page_number", required = false) String pageNumber, @RequestParam(value = "page_size", required = false) String pageSize, @RequestParam(value = "search_query", required = false) String searchKeyword, @RequestParam(value = "sort", required = false) String sortBy, @RequestParam(value = "sort_direction", required = false) String sortDirection, @RequestBody MailSearchModel mailSearchModel) throws BackendError { //调用服务 } }
测试类代码(ControllerTest.java)
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ControllerTest { @Autowired private MockMvc mock; @Autowired private WebApplicationContext wac; @Autowired private RequestHeaderFilter requestHeaderFilter; @LocalServerPort private int port; TestRestTemplate restTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); @Before public void setUp() { mock = MockMvcBuilders.webAppContextSetup(wac) .addFilter(requestHeaderFilter).build(); } @Test public void singleFieldProjectFields() throws Exception { ObjectMapper mapper = new ObjectMapper(); MailSearchModel mailSearchModel = mapper.readValue(new File("./src/test/resources/SingleValueSchema.json"), MailSearchModel.class); LinkedMultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>(); requestParams.add("sort_direction", "DESC"); requestParams.add("page_number", "1"); requestParams.add("sort", "sentdate"); requestParams.add("page_size", "100"); HttpEntity<MailSearchModel> entity = new HttpEntity<MailSearchModel>(mailSearchModel, headers); ResponseEntity<String> response = restTemplate.exchange( createURLWithPort("/rest/projects/{projectId}/mails/filter/{type}"), HttpMethod.POST, entity, String.class,"1879053221","inbox",requestParams); String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); assertTrue(actual.contains("/mails")); } }
自定义Filter代码(RequestHeaderFilter.java)
@WebFilter(urlPatterns = "/rest/*") @Order(1) public class RequestHeaderFilter implements Filter { private LocalizationHelper localizationHelper; private ObjectMapper jacksonObjectMapper; private JwtTokenProvider jwtTokenProvider; private BabylonCookieProvider babylonCookieProvider; private BabylonSessionService babylonSessionService; @Autowired public RequestHeaderFilter(LocalizationHelper localizationHelper, ObjectMapper jacksonObjectMapper, JwtTokenProvider jwtTokenProvider, BabylonCookieProvider babylonCookieProvider, BabylonSessionService babylonSessionService) { this.localizationHelper = localizationHelper; this.jacksonObjectMapper = jacksonObjectMapper; this.jwtTokenProvider = jwtTokenProvider; this.babylonCookieProvider = babylonCookieProvider; this.babylonSessionService = babylonSessionService; } @Override public void init(FilterConfig filterConfig) { // 无操作 } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 在请求作用域Bean中添加RequestId addRequestIdInSessionScope(httpRequest); // 检查请求认证 String auth = httpRequest.getHeader("Authorization"); if (Utility.isNullOrEmpty(auth)) { ErrorDto errorDto = getErrorDto(HttpStatus.BAD_REQUEST, JwtConstants.TOKEN_MISSING); postErrorResponse(httpResponse, errorDto); } else { auth = auth.replace(JwtConstants.TOKEN_TYPE_BEARER, "").trim(); String tokenStatus = parseJweToken(auth); afterValidation(request, response, chain, auth, tokenStatus); } } }
主类代码(MobileApplication.java)
@SpringBootApplication @ServletComponentScan public class MobileApplication { public static void main(String[] args) { SpringApplication.run(MobileApplication.class, args); } }
错误日志
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.oracle.cegbu.aconex.filter.RequestHeaderFilter': Cannot create inner bean '(inner bean)#6221ac7d' of type [com.oracle.cegbu.aconex.filter.RequestHeaderFilter] while setting bean property 'filter'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name '(inner bean)#6221ac7d' defined in file [/Users/sachinhr/Aconex/ora-acx-spring-bff/build/classes/java/main/com/oracle/cegbu/aconex/filter/RequestHeaderFilter.class]: Unsatisfied dependency expressed through constructor parameter 4; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'babylonSessionService' defined in file [/Users/sachinhr/Aconex/ora-acx-spring-bff/build/classes/java/main/com/oracle/cegbu/aconex/service/auth/BabylonSessionService.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bffRestTemplate' defined in file [/Users/sachinhr/Aconex/ora-acx-spring-bff/build/classes/java/main/com/oracle/cegbu/aconex/common/delegate/BffRestTemplate.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'baseUrlProvider': Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Circular placeholder reference 'instance' in property definitions at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:389) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:134) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1707) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1452) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:213) ~[spring-beans-5.3.15.jar:5.3.15] at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:212) ~[spring-boot-2.5.9.jar:2.5.9] at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:203) ~[spring-boot-2.5.9.jar:2.5.9] at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addServletContextInitializerBeans(ServletContextInitializerBeans.java:97) ~[spring-boot-2.5.9.jar:2.5.9] at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:86) ~[spring-boot-2.5.9.jar:2.5.9] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:260) ~[spring-boot-2.5.9.jar:2.5.9] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:234) ~[spring-boot-2.5.9.jar:2.5.9] at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:53) ~[spring-boot-2.5.9.jar:2.5.9] at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5219) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1396) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1386) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_312] at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) ~[na:1.8.0_312] at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:919) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:835) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1396) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1386) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_312] at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134) ~[na:1.8.0_312] at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:919) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:263) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.core.StandardService.startInternal(StandardService.java:432) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:927) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.apache.catalina.startup.Tomcat.start(Tomcat.java:486) ~[tomcat-embed-core-9.0.58.jar:9.0.58] at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:123) ~[spring-boot-2.5.9.jar:2.5.9] ... 63 common frames omitted
解决方案
从错误日志根因java.lang.IllegalArgumentException: Circular placeholder reference 'instance' in property definitions可知,问题出在配置文件的属性占位符循环引用,并非Filter本身实例化问题。
步骤1:修复配置文件循环占位符
检查项目配置文件(如application.properties/application.yml),找到包含instance的配置项,修正循环引用。例如:
# 错误示例:循环引用 instance=${instance} # 修正:直接赋值或通过外部配置传入 instance=test-instance
步骤2:简化测试类Filter加载逻辑
集成测试中使用TestRestTemplate会启动真实Servlet容器,@ServletComponentScan已自动注册Filter,无需手动在MockMvc中添加。同时需给请求添加Authorization头避免Filter拦截报错:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ControllerTest { @LocalServerPort private int port; TestRestTemplate restTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); @Test public void singleFieldProjectFields() throws Exception { // 添加Authorization头,绕过Filter的token校验 headers.setBearerAuth("mock-valid-token"); ObjectMapper mapper = new ObjectMapper(); MailSearchModel mailSearchModel = mapper.readValue(new File("./src/test/resources/SingleValueSchema.json"), MailSearchModel.class); LinkedMultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>(); requestParams.add("sort_direction", "DESC"); requestParams.add("page_number", "1"); requestParams.add("sort", "sentdate"); requestParams.add("page_size", "100"); // 构造带参数的请求URL UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(createURLWithPort("/rest/projects/{projectId}/mails/filter/{type}")) .queryParams(requestParams); HttpEntity<MailSearchModel> entity = new HttpEntity<>(mailSearchModel, headers); ResponseEntity<String> response = restTemplate.exchange( builder.buildAndExpand("1879053221", "inbox").toUriString(), HttpMethod.POST, entity, String.class); String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); assertTrue(actual.contains("/mails")); } private String createURLWithPort(String uri) { return "http://localhost:" + port + uri;
相关产品推荐
相关产品推荐

