admin管理员组

文章数量:1399006

I am upgrading the dependencies of a spring boot application with an embedded Tomcat server. When stepping through and upgrading from Spring 5.1 -> 5.2 and Spring Boot 2.1 -> 2.2 an issue arose where only POST and GET Http requests were being processed, while PATCH and DELETE were not. The Controller and JSP pages worked perfectly until I made this Spring upgrade.

For instance, when I breakpoint the below controller I can trace myself going to the Edit page. When I click Submit on that page it should then take me to the controller's update() method with RequestMethod.PATCH annotated. Since the upgrade however this instead takes me to the controller's create() method with RequestMethod.POST instead. It will then error because it's trying to add an existing item in the database, instead of updating an existing one. This is happening for multiple controllers.

I've confirmed that the normal use case of going to the Add page and clicking submit will hit the correct create() method in the controller and will create a new item.

I am required to use JAVA 8 and have pushed both Spring and Boot as far forward in versions as I can hoping this would resolve itself, but the issue remains. My current dependency versions are below.

I've attempted to breakpoint through the relevant Spring and Tomcat classes I can find to see where the error is occurring. I've confirmed that the startup building of Spring's RequestMappingInfoHandlerMapping is correctly mapping the controller's handler methods to the HTTP methods. Instead, it seems to be that the HttpServletRequest objects sent to Spring already have the incorrect HTTP methods. I've attempted to breakpoint through Tomcat classes to investigate, but am at a loss as to what is causing this, let alone how to resolve it.

Just in case I cleared out the .m2\repository folder and reloaded Maven, but to no avail.

Parent Pom.xml

<spring.version>5.3.39</spring.version>
<spring-boot.version>2.7.18</spring-boot.version>
<spring-security.version>5.8.16</spring-security.version>
<tomcat.version>9.0.102</tomcat.version>
    
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>${spring-boot.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-test</artifactId>
    <version>${spring-boot.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>.springframework.data</groupId>
    <artifactId>spring-data-commons</artifactId>
    <version>2.1.15.RELEASE</version>
</dependency>
<dependency>
    <groupId>.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>2.1.15.RELEASE</version>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring-security.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>${spring-security.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>
    <version>${spring-security.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>${spring-security.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${spring-security.version}</version>
</dependency>

Sub-Pom1.xml

<dependency>
    <groupId>.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>${tomcat.version}</version>
</dependency>

Sub-Pom2.xml

<dependency>
    <groupId>.apache.tomcat</groupId>
    <artifactId>tomcat-el-api</artifactId>
    <version>${tomcat.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>.apache.tomcat</groupId>
    <artifactId>tomcat-jasper-el</artifactId>
    <version>${tomcat.version}</version>
    <scope>test</scope>
</dependency>

Configuration.java

@Configuration
@Import(CoreConfiguration.class)
@EnableAutoConfiguration
@ComponentScan(".aurora.biorepository")
public class Configuration implements WebMvcConfigurer {
    public static void main(String[] args) throws Exception {
        System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "not_test");
        SpringApplication.run(ApplicationConfiguration.class, args);
    }


    @Bean
    public ServletWebServerFactory embeddedServletContainerFactory() {
        return new TomcatServletWebServerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                ((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
            }
        };
    }


    @Bean
    public FilterRegistrationBean getFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(getMethodConvertingFilter());
        registration.addUrlPatterns("/*");
        registration.setDispatcherTypes(DispatcherType.FORWARD);
        registration.setName("getMethodConvertingFilter");
        return registration;
    }

    @Bean
    public GetMethodConvertingFilter getMethodConvertingFilter() {
        return new GetMethodConvertingFilter();
    }

    @Bean
    @Autowired
    public DomainClassConverter domainClassConverter(@Qualifier("mvcConversionService") ConversionService cs) {
        return new DomainClassConverter(cs);
    }
}   

GetMethodConvertingFilter.java

public class GetMethodConvertingFilter implements Filter {

    @Override
    public void init(FilterConfig config) throws ServletException {
        // do nothing
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        chain.doFilter(wrapRequest((HttpServletRequest) request), response);
    }

    @Override
    public void destroy() {
        // do nothing
    }

    private static HttpServletRequestWrapper wrapRequest(HttpServletRequest request) {
        return new HttpServletRequestWrapper(request) {
            @Override
            public String getMethod() {
                return "GET";
            }
        };
    }
}

Controller.java

@Controller
@RequestMapping("/home")
public class Controller {

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String add(@ModelAttribute("modelAttribute") AddForm addForm, Errors errors, Model model, RedirectAttributes redirectAttributes) {
        ...doStuff();

        return "home/add";
    }
    
    @RequestMapping(method = RequestMethod.POST)
    public String create(@ModelAttribute("modelAttribute") @Valid AddForm addForm, Errors errors, Model model, RedirectAttributes redirectAttributes, javax.servlet.http.HttpServletRequest request) {
        ...doStuff();
    }
    
    @RequestMapping(value = "/{key}/edit", method = RequestMethod.GET)
    public String edit(ModelMap model, @PathVariable String key) {
        ...doStuff();
        
        return "home/edit";
    }
    
    @RequestMapping(method = RequestMethod.PATCH)
    public String update(@ModelAttribute("modelAttribute") @Valid AddForm addForm, Errors errors, Model model, RedirectAttributes redirectAttributes, HttpServletRequest request) throws ObjectNotFoundException {
        ...doStuff();
    }
}   

Add.jsp

<tiles:insertDefinition name="base">
    <tiles:putAttribute name="title"><fmt:message key=".home.Controller.add.title" /></tiles:putAttribute>
    <tiles:putAttribute name="content">
        <form:form action="/home" method="POST" modelAttribute="modelAttribute">
            <div class="half-page">             
                <%@ include file="_form.jsp" %>
                <div class="row">
                    <input type="submit" name="submit" value="<fmt:message key=".home.Controller.add.action.submit" />" class="button" />
                </div>
            </div>
        </form:form>
    </tiles:putAttribute>
    <tiles:putAttribute name="javascripts">
        <script type="text/javascript" src="<c:url value="/resources/js/home/_form.js" />"></script>
    </tiles:putAttribute>
</tiles:insertDefinition>

Edit.jsp

<tiles:insertDefinition name="base">
    <tiles:putAttribute name="title">
        <fmt:message key=".home.Controller.edit.title">
            <fmt:param value="${itemAddForm.item.name}" />
        </fmt:message>
    </tiles:putAttribute>
    <tiles:putAttribute name="sectionNavigation" type="template" value="/WEB-INF/views/home/" />
    <tiles:putAttribute name="content">
        <form:form action="/home" method="PATCH" modelAttribute="modelAttribute">
            <div class="half-page">
                <%@ include file="_form.jsp" %>
                <div class="row">
                    <input type="submit" name="submit" value="<fmt:message key=".home.Controller.edit.action.submit" />" class="button" />
                </div>
            </div>
        </form:form>
    </tiles:putAttribute>
    <tiles:putAttribute name="javascripts">
        <script type="text/javascript" src="<c:url value="/resources/js/home/_form.js" />"></script>
    </tiles:putAttribute>
</tiles:insertDefinition>

I am upgrading the dependencies of a spring boot application with an embedded Tomcat server. When stepping through and upgrading from Spring 5.1 -> 5.2 and Spring Boot 2.1 -> 2.2 an issue arose where only POST and GET Http requests were being processed, while PATCH and DELETE were not. The Controller and JSP pages worked perfectly until I made this Spring upgrade.

For instance, when I breakpoint the below controller I can trace myself going to the Edit page. When I click Submit on that page it should then take me to the controller's update() method with RequestMethod.PATCH annotated. Since the upgrade however this instead takes me to the controller's create() method with RequestMethod.POST instead. It will then error because it's trying to add an existing item in the database, instead of updating an existing one. This is happening for multiple controllers.

I've confirmed that the normal use case of going to the Add page and clicking submit will hit the correct create() method in the controller and will create a new item.

I am required to use JAVA 8 and have pushed both Spring and Boot as far forward in versions as I can hoping this would resolve itself, but the issue remains. My current dependency versions are below.

I've attempted to breakpoint through the relevant Spring and Tomcat classes I can find to see where the error is occurring. I've confirmed that the startup building of Spring's RequestMappingInfoHandlerMapping is correctly mapping the controller's handler methods to the HTTP methods. Instead, it seems to be that the HttpServletRequest objects sent to Spring already have the incorrect HTTP methods. I've attempted to breakpoint through Tomcat classes to investigate, but am at a loss as to what is causing this, let alone how to resolve it.

Just in case I cleared out the .m2\repository folder and reloaded Maven, but to no avail.

Parent Pom.xml

<spring.version>5.3.39</spring.version>
<spring-boot.version>2.7.18</spring-boot.version>
<spring-security.version>5.8.16</spring-security.version>
<tomcat.version>9.0.102</tomcat.version>
    
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>${spring-boot.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>${spring-boot.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-test</artifactId>
    <version>${spring-boot.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>.springframework.data</groupId>
    <artifactId>spring-data-commons</artifactId>
    <version>2.1.15.RELEASE</version>
</dependency>
<dependency>
    <groupId>.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>2.1.15.RELEASE</version>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>${spring-security.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
    <version>${spring-security.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>
    <version>${spring-security.version}</version>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>${spring-security.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>${spring-security.version}</version>
</dependency>

Sub-Pom1.xml

<dependency>
    <groupId>.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    <version>${tomcat.version}</version>
</dependency>

Sub-Pom2.xml

<dependency>
    <groupId>.apache.tomcat</groupId>
    <artifactId>tomcat-el-api</artifactId>
    <version>${tomcat.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>.apache.tomcat</groupId>
    <artifactId>tomcat-jasper-el</artifactId>
    <version>${tomcat.version}</version>
    <scope>test</scope>
</dependency>

Configuration.java

@Configuration
@Import(CoreConfiguration.class)
@EnableAutoConfiguration
@ComponentScan(".aurora.biorepository")
public class Configuration implements WebMvcConfigurer {
    public static void main(String[] args) throws Exception {
        System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "not_test");
        SpringApplication.run(ApplicationConfiguration.class, args);
    }


    @Bean
    public ServletWebServerFactory embeddedServletContainerFactory() {
        return new TomcatServletWebServerFactory() {
            @Override
            protected void postProcessContext(Context context) {
                ((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
            }
        };
    }


    @Bean
    public FilterRegistrationBean getFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(getMethodConvertingFilter());
        registration.addUrlPatterns("/*");
        registration.setDispatcherTypes(DispatcherType.FORWARD);
        registration.setName("getMethodConvertingFilter");
        return registration;
    }

    @Bean
    public GetMethodConvertingFilter getMethodConvertingFilter() {
        return new GetMethodConvertingFilter();
    }

    @Bean
    @Autowired
    public DomainClassConverter domainClassConverter(@Qualifier("mvcConversionService") ConversionService cs) {
        return new DomainClassConverter(cs);
    }
}   

GetMethodConvertingFilter.java

public class GetMethodConvertingFilter implements Filter {

    @Override
    public void init(FilterConfig config) throws ServletException {
        // do nothing
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        chain.doFilter(wrapRequest((HttpServletRequest) request), response);
    }

    @Override
    public void destroy() {
        // do nothing
    }

    private static HttpServletRequestWrapper wrapRequest(HttpServletRequest request) {
        return new HttpServletRequestWrapper(request) {
            @Override
            public String getMethod() {
                return "GET";
            }
        };
    }
}

Controller.java

@Controller
@RequestMapping("/home")
public class Controller {

    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String add(@ModelAttribute("modelAttribute") AddForm addForm, Errors errors, Model model, RedirectAttributes redirectAttributes) {
        ...doStuff();

        return "home/add";
    }
    
    @RequestMapping(method = RequestMethod.POST)
    public String create(@ModelAttribute("modelAttribute") @Valid AddForm addForm, Errors errors, Model model, RedirectAttributes redirectAttributes, javax.servlet.http.HttpServletRequest request) {
        ...doStuff();
    }
    
    @RequestMapping(value = "/{key}/edit", method = RequestMethod.GET)
    public String edit(ModelMap model, @PathVariable String key) {
        ...doStuff();
        
        return "home/edit";
    }
    
    @RequestMapping(method = RequestMethod.PATCH)
    public String update(@ModelAttribute("modelAttribute") @Valid AddForm addForm, Errors errors, Model model, RedirectAttributes redirectAttributes, HttpServletRequest request) throws ObjectNotFoundException {
        ...doStuff();
    }
}   

Add.jsp

<tiles:insertDefinition name="base">
    <tiles:putAttribute name="title"><fmt:message key=".home.Controller.add.title" /></tiles:putAttribute>
    <tiles:putAttribute name="content">
        <form:form action="/home" method="POST" modelAttribute="modelAttribute">
            <div class="half-page">             
                <%@ include file="_form.jsp" %>
                <div class="row">
                    <input type="submit" name="submit" value="<fmt:message key=".home.Controller.add.action.submit" />" class="button" />
                </div>
            </div>
        </form:form>
    </tiles:putAttribute>
    <tiles:putAttribute name="javascripts">
        <script type="text/javascript" src="<c:url value="/resources/js/home/_form.js" />"></script>
    </tiles:putAttribute>
</tiles:insertDefinition>

Edit.jsp

<tiles:insertDefinition name="base">
    <tiles:putAttribute name="title">
        <fmt:message key=".home.Controller.edit.title">
            <fmt:param value="${itemAddForm.item.name}" />
        </fmt:message>
    </tiles:putAttribute>
    <tiles:putAttribute name="sectionNavigation" type="template" value="/WEB-INF/views/home/" />
    <tiles:putAttribute name="content">
        <form:form action="/home" method="PATCH" modelAttribute="modelAttribute">
            <div class="half-page">
                <%@ include file="_form.jsp" %>
                <div class="row">
                    <input type="submit" name="submit" value="<fmt:message key=".home.Controller.edit.action.submit" />" class="button" />
                </div>
            </div>
        </form:form>
    </tiles:putAttribute>
    <tiles:putAttribute name="javascripts">
        <script type="text/javascript" src="<c:url value="/resources/js/home/_form.js" />"></script>
    </tiles:putAttribute>
</tiles:insertDefinition>
Share Improve this question asked Mar 26 at 19:42 MustyProgrammerMustyProgrammer 11 bronze badge 1
  • For starters stop trying to outsmart Spring Boot dependency management. You don't need the .springframework dependencies those are already pulled in by Spring Boot. The same for the .springframework.security and .springframework.data dependencies. You are fighting the dependency management. That being said you have a filter that makes everything a GET request. If you want to upgrade certain versions then do it in a proper way. – M. Deinum Commented Mar 27 at 7:07
Add a comment  | 

2 Answers 2

Reset to default 1

These are pretty old versions, and it might be an incompatibility between spring boot version, spring version, and other dependency versions.

I suggest to let spring-boot-starter-parent manage dependencies.

Add it as your parent:

<parent>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.18</version>
<parent>

or in your dependency management section.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.7.18</version>
            <type>pom</type>
            <scope>import</scope>
        <dependency>
    </dependencies>
</dependencyManagement>

Then remove all the versions you have in your <dependencies> section.

Also, a good ideea is to use the maven-enforcer-plugin to make sure you don't have dependency convergence errors.

For example: your version of spring-data-commons has a dependency on spring 5.1.13, while you use 5.3.39.

If you really want to increase the version of spring security, try to see which version of spring-boot has that spring security version.

Also, when upgrading versions outside of spring-boot, you need to be careful as if it's not a patch version increase, then you risk to break the application.

For starters I would suggest to use the proper way to override dependency versions. Which is by just specifying the right property and let Spring Boot (the plugin) handle the managing of the versions.

Don't do manual dependency management for dependencies managed by Spring Boot or its starters (see my previous point). With that you are making things hard for dependency management and will lead to conflicting versions.

You have a GetMethodConvertingFilter which turns every forward into a GET, when using Spring Security things might be an internal forward in which this filter kicks in and breaks the method. So I would suggest to remove this.

<parent>
  <groupId>.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.7.18</version>
<parent>

<properties>
  <spring-framework.version>5.3.39</spring-framework.version>
  <spring-security.version>5.8.16</spring-security.version>
  <tomcat.version>9.0.102</tomcat.version>
<properties>

<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
    <groupId>.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

Your own GetMethodConvertingFilter probably also plays a role especially when using Spring Security. So I would suggest to remove it or make it smarter to determine when to do the forward (although that would be tricky as it probably executes before the security chain).

本文标签: