How to integrate angular with spring boot to see a

2019-06-13 18:08发布

问题:

I have backend spring boot app and front-end developed using angular4. Since these 2 operate on different ports(8080,4200) when deploying, it isn't showing any UI. In local, starting angular server using below works absolutely fine on localhost:4200 and shows web interface:

ng serve --proxy-config proxy.conf.json

where proxy.conf.json has contents:

{
  "*": {
    "target": "http://localhost:8080",
    "secure": false,
    "logLevel": "debug"
  }
}

But not when trying to integrate with springboot app(localhost:8080). Probably, it requires ng business logic(node/npm install etc.) to be baked before deployment.

So I used ng build which copied generated files to a output dir - src/main/resources/static and now it's in spring-boot app path. Starting tomcat still shows no UI on localhost:8080. I do see Angular symbol/icon on chrome tab but nothing shown on html page.

I also added a controller method to return index.html so that it can serve static files in path.

@GetMapping("/")
public String index() {
    return "forward:/index.html";
}

But doing this just displays "forward:/index.html" string instead of html content on webpage. Do I need to change something in this index.html so that it can route to ng components that I have created? Don't know if it matters to change selector in index.html. Since my main component is not app component(which is by default the root component) rather a login component so in index.html I changed <app-root></app-root> to login component's selector <app-login></app-login>; still nothing shows up in UI.

Seems like spring-boot isn't able to understand angular stuff and how to route to main ng components.

Below is index.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Hello App</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
<script type="text/javascript" src="runtime.js"></script><script type="text/javascript" src="polyfills.js"></script><script type="text/javascript" src="styles.js"></script><script type="text/javascript" src="vendor.js"></script><script type="text/javascript" src="main.js"></script></body>
</html>

Project structure:

-src
 -main
  -java
    - backend
      - AppController
      - AppService
      - Main.java
    - frontend
      - src
        - index.html
        - app
          -login
           - login.component.html
           - login.component.css
           - login.component.ts
          etc..
  - resources
    - static
      - index.html
      - application.properties
      - ...

How can I have the frontend work with backend server when deployment? Do I need to add any argument or configuration in application.properties for the app to serve static content from /resources on startup ? Do I add any resourceLocationHandler for serving it?

Any help , very much appreciated!

回答1:

You can do it by using maven and the frontend-maven-plugin

First od all I agree about the fact to have the frontend separated by the backend

So i would create this project structure:

parentDirectory
- frontend
  - angular src files
  - pom.xml
- backend
  - spring boot based backend
  - pom.xml
- pom.xml

The parent pom.xml would be:

<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>test</groupId>
    <artifactId>apringangular</artifactId>
    <packaging>pom</packaging>
    <name>Spring angular</name>
    <modules>
        <module>backend</module>
        <module>frontend</module>
    </modules>
</project>

The frontend pom would be:

<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>
    <parent>
        <groupId>test</groupId>
        <artifactId>springangular</artifactId>
        <version>1.0</version>
    </parent>
    <artifactId>frontend</artifactId>
    <packaging>jar</packaging>
    <name>frontend</name>
    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>properties-maven-plugin</artifactId>
                <version>1.0.0</version>
                <executions>
                    <execution>
                        <phase>initialize</phase>
                        <goals>
                            <goal>read-project-properties</goal>
                        </goals>
                        <configuration>
                            <files>
                                <file>frontend_project_properties.properties</file>
                            </files>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-clean-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <filesets>
                        <fileset>
                            <directory>dist</directory>
                            <includes>
                                <include>*</include>
                            </includes>
                        </fileset>
                    </filesets>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.github.eirslett</groupId>
                <artifactId>frontend-maven-plugin</artifactId>
                <version>1.6</version>
                <executions>
                    <execution>
                        <id>install node and npm</id>
                        <goals>
                            <goal>install-node-and-npm</goal>
                        </goals>
                        <configuration>
                            <nodeVersion>v8.11.3</nodeVersion>
                            <npmVersion>6.3.0</npmVersion>
                            <arguments>${http_proxy_config}</arguments>
                            <arguments>${https_proxy_config}</arguments>
                            <arguments>run build</arguments>
                            <npmInheritsProxyConfigFromMaven>false</npmInheritsProxyConfigFromMaven>
                        </configuration>
                    </execution>
                    <execution>
                        <id>npm install</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                        <phase>generate-resources</phase>
                        <configuration>
                            <arguments>install</arguments>
                        </configuration>
                    </execution>
                    <execution>
                        <id>npm run build</id>
                        <goals>
                            <goal>npm</goal>
                        </goals>
                        <configuration>
                            <arguments>${http_proxy_config}</arguments>
                            <arguments>${https_proxy_config}</arguments>
                            <arguments>run build</arguments>
                            <npmInheritsProxyConfigFromMaven>false</npmInheritsProxyConfigFromMaven>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Inside the file frontend_project_properties.propertiesI have my proxy configuration for node and npm. Something like this:

http_proxy_config=config set proxy http://USERNAME_PROXY:PASSWORD_PROXY@PROXY_HOST:PROXY_PORT
https_proxy_config=config set https-proxy http://USERNAME_PROXY:PASSWORD_PROXY@PROXY_HOST:PROXY_PORT

The backend pom is a classical spring boot backend. You must tell maven where the frontend is so maven is able in creating a unique webapplication. In the backend pom.xml you should add something like this:

<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>
    <parent>
        <groupId>it.eng.tz.area.vasta.mev</groupId>
        <artifactId>appmgr</artifactId>
        <version>1.0</version>
    </parent>
    <artifactId>appmgrbackend</artifactId>
    <packaging>war</packaging>
    <name>Application manager backend</name>
    <description>
        Lato backend del sistema di gestione
    </description>
    <dependencies>
         <!-- Your dependencies -->
    </dependencies>
    <build>
        <sourceDirectory>src/main/java</sourceDirectory>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>**/*.*</include>
                </includes>
            </resource>
        </resources>        
        <testResources>
            <testResource>
                <directory>src/test/resources</directory>
                <excludes>
                    <exclude>**/*.*</exclude>
                </excludes>
            </testResource>
        </testResources>
        <plugins>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <webResources>
                        <resource>
                            <directory>../frontend/dist</directory>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

In this way you tell the maven-war-plugin that the HTML and static code is locate in the dist directory of frontend project Note that during the development node.js serves resource on 4200 port while spring uses a different port. So you'll have a cross-site issue. By using spring security yuo can solve this issue bu configuring, in the backend side, spring security in this way:

@Configuration
@EnableWebSecurity
@Import(value= {AppMgrWebMvcConfig.class})
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled=true)
public class AppMgrWebSecConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    @Qualifier("oauthUsrDetailSvc")
    UserDetailsService userDetailsService;
    @Autowired
    @Qualifier("userPwdEnc")
    private PasswordEncoder pwdEncoder;
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.httpFirewall(this.allowUrlEncodedSlashHttpFirewall());
    }
    @Bean
    public HttpFirewall allowUrlEncodedSlashHttpFirewall()
    {
        StrictHttpFirewall firewall = new StrictHttpFirewall();
        firewall.setAllowUrlEncodedSlash(true);
        firewall.setAllowSemicolon(true);
        return firewall;
    } 
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
    {
        auth.userDetailsService(userDetailsService);
        auth.authenticationProvider(authenticationProvider());
    }
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(pwdEncoder);
        return authenticationProvider;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http
        .authorizeRequests()
        .antMatchers("/resources/**")
        .permitAll()
        .antMatchers("/rest/protected/**")
        .access("hasAnyRole('ADMIN','USER','SUPER_ADMIN')")
        .and()
        .authorizeRequests()
        .antMatchers("/rest/public/**")
        .permitAll()
        .and()
        .formLogin()
        .loginPage("/login")
        .permitAll()
        .usernameParameter("username")
        .passwordParameter("password")
        .defaultSuccessUrl("http://localhost:8100/", true)
        .failureUrl("/login?error")
        .loginProcessingUrl("/login")
        .and()
        .logout()
        .permitAll()
        .logoutSuccessUrl("/login?logout")
        .and()
        .csrf()
        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        .and()
        .cors().configurationSource(corsConfigurationSource())
        .and()
        .exceptionHandling()
        .accessDeniedPage("/pages/accessDenied");
    }
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200","http://localhost:8080"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("x-requested-with"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

This will let you to develope and compile all just only by using maven

Angelo



回答2:

Please open the jar file with an archive utility and see if the static files are available in it. If they are available, you need to tell Spring that the URLs you are entering into the address bar are actually Angular routes:

@Controller
public class Routing {

    @RequestMapping({ "", "/login", "/products/**" })
    public String gui() {
        return "forward:/index.html";
    }
}