Spring: can't access controller 404 not found error

144 views Asked by At

I'm trying to access a http://localhost:8088/test_war_exploded/home/hello but get 404 error. I can't figure out why.

Directory structure:

root
  src
    main
      java
         test
           config
              DbConfig.java
              MyAppInitializer.java
              WebConfig.java
          HomeController.java

WebConfig.java:


@Configuration
@ComponentScan("test")
@EnableWebMvc
@EnableTransactionManagement
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableJpaRepositories(basePackages = "test",
        entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager")
 public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureDefaultServletHandling(
                DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver bean = new InternalResourceViewResolver();

        bean.setViewClass(JstlView.class);
        bean.setPrefix("/WEB-INF/view/");
bean.setSuffix(".html");


        return bean;
    }
}

MyAppInitializer.java:

public class MyAppInitializer extends
                AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    public void onStartup(final ServletContext sc) throws ServletException {
        System.out.println("onStartup!");

        AnnotationConfigWebApplicationContext root =
                new AnnotationConfigWebApplicationContext();

        root.register(WebConfig.class);
        root.setServletContext(sc);

        root.scan("test");
        //sc.addListener(new ContextLoaderListener(root));

        ServletRegistration.Dynamic appServlet =
                sc.addServlet("dispatcher", new DispatcherServlet(new GenericWebApplicationContext()));
        appServlet.setLoadOnStartup(1);
        appServlet.addMapping("/");
    }

        @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {SecurityConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

HomeController.java:

@RestController
@RequestMapping("/home")
public class HomeController {

    @GetMapping(value = "/hello")
    public String hello() {
        return "Hello";
    }
}

The app gets deployed with no errors in server / IDE log. During deployment onStartup gets printed in console, meaning the code in MyAppInitializer gets executed.

3

There are 3 answers

0
parsecer On BEST ANSWER

I figured it out. The problem was that I didn't have an initializer class. I added two classes: SecurityConfig and SecurityWebApplicationInitializer:

SecurityConfig.java:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter  {
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests().antMatchers("/").permitAll();
    }

    @Bean
    BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

SecurityWebApplicationInitializer.java:

public class SecurityWebApplicationInitializer extends
                    AbstractSecurityWebApplicationInitializer {
    public SecurityWebApplicationInitializer() {
        super(SecurityConfig.class, WebConfig.class);
    }
}

This is how my project looks now:

enter image description here Main.java is just for the maven-shade-plugin, never mind it, it only contains the empty main method:

Main.java:

public class Main {
    public static void main(String[] args) {

    }
}

My other classes (updated). I removed database config class for simplicity. If you don't need a database, you can just copypaste classes from this answer and it should work fine:

MyAppInitializer.java:

public class MyAppInitializer extends
                AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    public void onStartup(final ServletContext sc) throws ServletException {
        System.out.println("onStartup!");

        AnnotationConfigWebApplicationContext root =
                new AnnotationConfigWebApplicationContext();

        root.register(WebConfig.class);
        root.setServletContext(sc);

        root.scan("test");
        //sc.addListener(new ContextLoaderListener(root));

        ServletRegistration.Dynamic appServlet =
                sc.addServlet("dispatcher", new DispatcherServlet(new GenericWebApplicationContext()));
        appServlet.setLoadOnStartup(1);
        appServlet.addMapping("/");
    }

        @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] {SecurityConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

WebConfig.java:

@Configuration
@ComponentScan("test")
@EnableWebMvc
@EnableTransactionManagement
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableJpaRepositories(basePackages = "test",
        entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager")
 public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");
    }


    @Override
    public void configureDefaultServletHandling(
                DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

HomeController.java:

@RestController
public class HomeController {
    @GetMapping("/")
    public String getHome()  {
        return "home";
    }
}

TestController.java:

@RestController
public class TestController {
    @RequestMapping("/test")
    public String test()  {
        return "test";
    }
}

I removed view resolvers from config classes, because I intend to only use rest controllers as an API provider. Thanks for coming to my TED talk.

PS Here's how the pom.xml looks (it seems to be important because with different poms Intellij doesn't auto generate war-exploded artifact):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>bigpuzzle</artifactId>
    <version>1.0-SNAPSHOT</version>


    <packaging>war</packaging>

    <pluginRepositories>
        <pluginRepository>
            <id>pcentral</id>
            <name>pcentral</name>
            <url>https://repo1.maven.org/maven2</url>
        </pluginRepository>

        <pluginRepository>
            <id>central</id>
            <name>Central Repository</name>
            <url>https://repo.maven.apache.org/maven2</url>
            <layout>default</layout>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <repositories>


        <repository>
            <id>jcenter</id>
            <name>jcenter-bintray</name>
            <url>https://jcenter.bintray.com</url>
        </repository>

    </repositories>


    <dependencies>
        <!-- Jackson to convert Java object to Json -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.4</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.4</version>
        </dependency>




        <dependency>
            <groupId>net.sourceforge</groupId>
            <artifactId>jwbf</artifactId>
            <version>3.1.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>5.3.1.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>5.3.1.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.3.1.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-web -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>5.3.1.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.2.6.RELEASE</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring4</artifactId>
            <version>3.0.7.RELEASE</version>
        </dependency>


        <dependency>
            <groupId>org.apache.opennlp</groupId>
            <artifactId>opennlp-tools</artifactId>
            <version>1.9.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/net.dv8tion/JDA -->
        <dependency>
            <groupId>net.dv8tion</groupId>
            <artifactId>JDA</artifactId>
            <version>4.0.0_46</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.4.4.Final</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.197</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.4-1203-jdbc4</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.4.1.RELEASE</version>
        </dependency>

        <!--is needed for jwt-->
        <!-- https://mvnrepository.com/artifact/javax.xml/jaxb-api -->
        <dependency>
            <groupId>javax.xml</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.1</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/com.sun.xml.bind/jaxb-impl -->
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>3.0.0-M4</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/com.sun.xml.bind/jaxb-core -->
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>3.0.0-M4</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-jwt -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>1.1.0.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/com.jakewharton.fliptables/fliptables -->
        <dependency>
            <groupId>com.jakewharton.fliptables</groupId>
            <artifactId>fliptables</artifactId>
            <version>1.0.2</version>
        </dependency>




        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.4.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.json/json -->
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20190722</version>
        </dependency>



        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec -->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.14</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>

    </dependencies>

    <build>
        <finalName>bigpuzzle</finalName>


        <plugins>


            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <shadedArtifactAttached>true</shadedArtifactAttached>

                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>bigpuzzle.Main</mainClass>
                                    <manifestEntries>
                                        <Main-Class>bigpuzzle.Main</Main-Class>
                                    </manifestEntries>
                                </transformer>
                            </transformers>


                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>


            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals><goal>copy</goal></goals>
                        <configuration>

                            <artifactItems>

                                <artifactItem>
                                    <groupId>com.github.jsimone</groupId>
                                    <artifactId>webapp-runner</artifactId>
                                    <version>8.0.30.2</version>
                                    <destFileName>webapp-runner.jar</destFileName>
                                </artifactItem>

                            </artifactItems>

                        </configuration>
                    </execution>
                </executions>
            </plugin>


            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>

            </plugin>

        </plugins>
    </build>

</project>

Once you've configured Tomcat Local, you can change this pom.xml.

If you also want database support, add this file to config package:

package bigpuzzle.config;

import org.hibernate.SessionFactory;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;


@Configuration
 @EnableTransactionManagement
@EnableJpaRepositories(basePackages = "bigpuzzle", entityManagerFactoryRef = "entityManagerFactory")
 public class DbConfig {
    @Primary
    @Bean(name = "entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(getDatasource());
        entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);

        entityManagerFactoryBean.setJpaProperties(getHibernateProperties());

        entityManagerFactoryBean.setPackagesToScan("bigpuzzle");

        return entityManagerFactoryBean;
    }


    @Bean
    public DataSource getDatasource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("org.postgresql.Driver");
        dataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/bigpuzzle");
        dataSource.setUsername("postgres");
        dataSource.setPassword("123456");
        return dataSource;
    }

    @Primary
    @Bean
    public SessionFactory getSessionFactory() throws IOException {
        LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
        sessionFactoryBean.setPackagesToScan("bigpuzzle");
        //getHibernateProperties method is a private method

        sessionFactoryBean.setHibernateProperties(getHibernateProperties());
        sessionFactoryBean.setDataSource(getDatasource());
        sessionFactoryBean.afterPropertiesSet();

        return sessionFactoryBean.getObject();
    }

    @Bean(name = "transactionManager")
     public PlatformTransactionManager transactionManager() throws IOException {
        /*HibernateTransactionManager transactionManager = new HibernateTransactionManager();
        transactionManager.setSessionFactory(getSessionFactory());*/

        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }


    @Bean
    private static Properties getHibernateProperties() {
        Properties hibernateProperties = new Properties();  //PostgreSQLDialect
        //hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
        hibernateProperties.put("hibernate.connection.driver_class", "org.postgresql.Driver");
        hibernateProperties.put("hibernate.show_sql", true);
        hibernateProperties.put("hibernate.allow_update_outside_transaction",  false);
    //    hibernateProperties.put("spring.jpa.hibernate.ddl-auto", "create");

        hibernateProperties.put( "hibernate.hbm2ddl.auto", "create-drop");  //create-drop or update
         /*hibernateProperties.setProperty(
                "hibernate.dialect", "org.hibernate.dialect.H2Dialect");
*/
        System.out.println();

        hibernateProperties.put("spring.jpa.properties.hibernate.show_sql", "false");
        hibernateProperties.put("spring.jpa.properties.hibernate.use_sql_comments", "true");
        hibernateProperties.put("spring.jpa.properties.hibernate.format_sql", "true");
        hibernateProperties.put("spring.jpa.properties.hibernate.type", "trace");
        hibernateProperties.put("spring.jpa.show-sql", "true");
        hibernateProperties.put("logging.level.org.hibernate", "TRACE");


        // other properties
        return hibernateProperties;
    }
}

PS If you also use Idea Intellij, follow this video to deploy the application on Tomcat.

0
Valerij Dobler On

The HomeController provides a mapping for /home. For accessing the mentioned endpoint you could add the first part to the RequestMapping Annotation as such: @RequestMapping("test_war_exploded/home")

And you try to access via port 8088. The default port is 8080, if not set in the application.properties otherwise.

1
Studiedlist On

HomeController bean is not defined and not initialized.

@Component
@RestController
@RequestMapping("/home")
public class HomeController {

    @GetMapping(value = "/hello")
    public String hello() {
        return "Hello";
    }
}