Spring MVC 异常处理- @ControllerAdvice, @ExceptionHandler, HandlerExceptionResolver

使用Spring MVC异常处理非常重要,以确保不将服务器异常发送到客户端。 今天我们将使用@ExceptionHandler、@ControllerAdvice和HandlerExceptionResolver来深入了解Spring异常处理。 任何Web应用程序都需要良好的异常处理设计,因为我们不希望在应用程序出现未处理异常时提供容器生成的页面。

春季异常处理

拥有一个明确定义的异常处理方法对于任何网页应用程序框架来说都是一个巨大的优势,也就是说,当涉及到网页应用程序中的异常和错误处理时,Spring MVC框架表现出色。Spring MVC框架提供以下方法来帮助我们实现健壮的异常处理。

    1. 基于控制器 – 我们可以在我们的控制器类中定义异常处理方法。我们只需要用@ExceptionHandler注释这些方法即可。该注释以Exception类作为参数。因此,如果我们为Exception类定义了其中之一,那么请求处理器方法抛出的异常将全部被处理。这些异常处理器方法与其他请求处理器方法一样,我们可以构建错误响应并响应不同的错误页面。我们还可以发送JSON错误响应,我们将在示例中学习。如果定义了多个异常处理器方法,则将使用离Exception类最近的处理器方法。例如,如果我们为IOException和Exception分别定义了两个处理器方法,并且我们的请求处理器方法引发了IOException,那么将执行用于IOException的处理器方法。

全局异常处理器 – 异常处理是一个横切关注点,应该在我们应用程序的所有切入点上处理。我们已经研究了Spring AOP,这就是为什么Spring提供@ControllerAdvice注释,我们可以与任何类一起使用来定义我们的全局异常处理器。全局控制器建议中的处理器方法与基于控制器的异常处理器方法相同,在控制器类无法处理异常时使用。

HandlerExceptionResolver – 对于通用异常,大多数情况下,我们提供静态页面服务。Spring框架提供了HandlerExceptionResolver接口,我们可以实现它来创建全局异常处理器。之所以有这种额外的定义全局异常处理器的方式,是因为Spring框架还提供了默认实现类,我们可以在Spring配置文件中定义,以获取Spring框架的异常处理优势。SimpleMappingExceptionResolver是默认的实现类,它允许我们配置exceptionMappings,我们可以指定要为特定异常使用的资源。我们还可以覆盖它以根据我们应用程序的特定更改创建自己的全局处理器,例如记录异常消息。

让我们创建一个Spring MVC项目,在其中我们将探讨基于控制器、基于AOP和基于异常解析器的异常和错误处理方法的实现。我们还将编写一个异常处理器方法,以返回JSON响应。如果您对Spring中的JSON不熟悉,请阅读Spring Restful JSON教程。我们最终的项目将如下图所示,我们将逐个查看应用程序的所有组件。

春季异常处理的Maven依赖项

除了标准的Spring MVC依赖,我们还需要Jackson JSON依赖以支持JSON。我们最终的pom.xml文件如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.Olivia.spring</groupId>
	<artifactId>SpringExceptionHandling</artifactId>
	<name>SpringExceptionHandling</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	<properties>
		<java-version>1.6</java-version>
		<org.springframework-version>4.0.2.RELEASE</org.springframework-version>
		<org.aspectj-version>1.7.4</org.aspectj-version>
		<org.slf4j-version>1.7.5</org.slf4j-version>
		<jackson.databind-version>2.2.3</jackson.databind-version>
	</properties>
	<dependencies>
		<!-- Jackson -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>${jackson.databind-version}</version>
		</dependency>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
			<exclusions>
				<!-- Exclude Commons Logging in favor of SLF4j -->
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>

		<!-- AspectJ -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version>
		</dependency>

		<!-- Logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${org.slf4j-version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>${org.slf4j-version}</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.15</version>
			<exclusions>
				<exclusion>
					<groupId>javax.mail</groupId>
					<artifactId>mail</artifactId>
				</exclusion>
				<exclusion>
					<groupId>javax.jms</groupId>
					<artifactId>jms</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jdmk</groupId>
					<artifactId>jmxtools</artifactId>
				</exclusion>
				<exclusion>
					<groupId>com.sun.jmx</groupId>
					<artifactId>jmxri</artifactId>
				</exclusion>
			</exclusions>
			<scope>runtime</scope>
		</dependency>

		<!-- @Inject -->
		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>

		<!-- Servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>2.5</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>

		<!-- Test -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.7</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-eclipse-plugin</artifactId>
				<version>2.9</version>
				<configuration>
					<additionalProjectnatures>
						<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
					</additionalProjectnatures>
					<additionalBuildcommands>
						<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
					</additionalBuildcommands>
					<downloadSources>true</downloadSources>
					<downloadJavadocs>true</downloadJavadocs>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.5.1</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
					<compilerArgument>-Xlint:all</compilerArgument>
					<showWarnings>true</showWarnings>
					<showDeprecation>true</showDeprecation>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.codehaus.mojo</groupId>
				<artifactId>exec-maven-plugin</artifactId>
				<version>1.2.1</version>
				<configuration>
					<mainClass>org.test.int1.Main</mainClass>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

我已经更新Spring Framework、AspectJ、Jackson和slf4j的版本,使用了最新版本。

Spring MVC 异常处理部署描述符

我们的web.xml文件如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="https://java.sun.com/xml/ns/javaee"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/root-context.xml</param-value>
	</context-param>
	
	<!-- Creates the Spring Container shared by all Servlets and Filters -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/spring.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

	<error-page>
		<error-code>404</error-code>
		<location>/resources/404.jsp</location>
	</error-page>
</web-app>

除了为我们的Web应用程序插入Spring框架的大部分部分外,还定义了404错误的错误页。因此,当我们的应用程序出现404错误时,将使用该页面作为响应。此配置是由容器在我们的Spring Web应用程序抛出404错误码时使用的。

春季异常处理 – 模型类

我已将Employee bean定义为模型类,然而在我们的应用程序中,我们只会在特定场景中使用它来返回有效的响应。在大多数情况下,我们会故意抛出不同类型的异常。

package com.Olivia.spring.model;

public class Employee {

	private String name;
	private int id;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}
}

由于我们还将返回JSON响应,让我们创建一个包含异常详细信息的Java bean,作为响应发送出去。

package com.Olivia.spring.model;

public class ExceptionJSONInfo {

	private String url;
	private String message;
	
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
}

自定义异常类 – 春季异常处理

让我们创建一个自定义异常类,供我们的应用程序使用。

package com.Olivia.spring.exceptions;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Employee Not Found") //404
public class EmployeeNotFoundException extends Exception {

	private static final long serialVersionUID = -3332292346834265371L;

	public EmployeeNotFoundException(int id){
		super("EmployeeNotFoundException with id="+id);
	}
}

请注意,我们可以使用@ResponseStatus注解与异常类一起使用,以定义应用程序在抛出此类型异常并由我们的异常处理实现处理时发送的HTTP代码。正如您可以看到的,我将HTTP状态设置为404,并为此定义了错误页,因此如果我们没有返回任何视图,我们的应用程序应该使用此类型异常的错误页。我们还可以在异常处理器方法中覆盖状态码,将其视为默认的HTTP状态码,当异常处理器方法没有返回任何视图页作为响应时使用。

Spring MVC 异常处理控制器类异常处理程序

让我们来看看我们的控制器类,我们将抛出不同类型的异常。

package com.Olivia.spring.controllers;

import java.io.IOException;
import java.sql.SQLException;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import com.Olivia.spring.exceptions.EmployeeNotFoundException;
import com.Olivia.spring.model.Employee;
import com.Olivia.spring.model.ExceptionJSONInfo;

@Controller
public class EmployeeController {
	
	private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
	
	@RequestMapping(value="/emp/{id}", method=RequestMethod.GET)
	public String getEmployee(@PathVariable("id") int id, Model model) throws Exception{
		//deliberately throwing different types of exception
		if(id==1){
			throw new EmployeeNotFoundException(id);
		}else if(id==2){
			throw new SQLException("SQLException, id="+id);
		}else if(id==3){
			throw new IOException("IOException, id="+id);
		}else if(id==10){
			Employee emp = new Employee();
			emp.setName("Pankaj");
			emp.setId(id);
			model.addAttribute("employee", emp);
			return "home";
		}else {
			throw new Exception("Generic Exception, id="+id);
		}
		
	}
	
	@ExceptionHandler(EmployeeNotFoundException.class)
	public ModelAndView handleEmployeeNotFoundException(HttpServletRequest request, Exception ex){
		logger.error("Requested URL="+request.getRequestURL());
		logger.error("Exception Raised="+ex);
		
		ModelAndView modelAndView = new ModelAndView();
	    modelAndView.addObject("exception", ex);
	    modelAndView.addObject("url", request.getRequestURL());
	    
	    modelAndView.setViewName("error");
	    return modelAndView;
	}	
}

请注意,对于EmployeeNotFoundException的处理程序,我返回的是ModelAndView,因此http状态码将被发送为OK(200)。如果返回值为void,则http状态码将被发送为404。我们将在全局异常处理程序中研究这种实现类型。由于我只处理控制器中的EmployeeNotFoundException,所以由我们的控制器抛出的所有其他异常都将由全局异常处理程序处理。

@ControllerAdvice和@ExceptionHandler

这是我们的全局异常处理器控制类。请注意,该类使用@ControllerAdvice注解进行标注。同时,方法也使用@ExceptionHandler注解进行标注。

package com.Olivia.spring.controllers;

import java.io.IOException;
import java.sql.SQLException;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
public class GlobalExceptionHandler {

	private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
	
	@ExceptionHandler(SQLException.class)
	public String handleSQLException(HttpServletRequest request, Exception ex){
		logger.info("SQLException Occured:: URL="+request.getRequestURL());
		return "database_error";
	}
	
	@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="IOException occured")
	@ExceptionHandler(IOException.class)
	public void handleIOException(){
		logger.error("IOException handler executed");
		//returning 404 error code
	}
}

请注意,对于SQLException,我将以200的http状态码返回database_error.jsp作为响应页面。对于IOException,我们以404的状态码返回void,因此在这种情况下将使用我们的error-page。正如你所看到的,我在这里没有处理其他类型的异常,我将这部分留给HandlerExceptionResolver实现。

异常处理解析器

我们只是扩展SimpleMappingExceptionResolver并重写其中的一个方法,但我们可以重写它最重要的方法resolveException来进行日志记录并发送不同类型的视图页面。但这与使用ControllerAdvice实现的方式相同,所以我放弃了这种方法。我们将使用它来配置所有其他未经我们处理的异常的视图页面,通过响应通用错误页面。

春季异常处理的配置文件

我们的spring bean配置文件如下所示。spring.xml代码:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="https://www.springframework.org/schema/beans"
	xmlns:context="https://www.springframework.org/schema/context"
	xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>
	
	
	<beans:bean id="simpleMappingExceptionResolver" class="com.Olivia.spring.resolver.MySimpleMappingExceptionResolver">
		<beans:property name="exceptionMappings">
			<beans:map>
				<beans:entry key="Exception" value="generic_error"></beans:entry>
			</beans:map>
		</beans:property>
		<beans:property name="defaultErrorView" value="generic_error"/>
	</beans:bean>
	
	<!-- Configure to plugin JSON as request and response in method handler -->
	<beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
		<beans:property name="messageConverters">
			<beans:list>
				<beans:ref bean="jsonMessageConverter"/>
			</beans:list>
		</beans:property>
	</beans:bean>
	
	<!-- Configure bean to convert JSON to POJO and vice versa -->
	<beans:bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
	</beans:bean>
	
	<context:component-scan base-package="com.Olivia.spring" />
	
</beans:beans>

请注意我们网页应用程序中用于支持JSON的beans配置。与异常处理有关的唯一部分是simpleMappingExceptionResolver bean的定义,我们将generic_error.jsp定义为Exception类的视图页面。这确保了我们的应用程序未处理的任何异常不会作为响应发送服务器生成的错误页面。

Spring MVC 异常处理 JSP 视图页面

是时候来看一下我们应用程序的最后一部分,也就是我们应用程序中将要使用的视图页面。home.jsp的代码:

<%@ taglib uri="https://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
	<title>Home</title>
</head>
<body>
	<h3>Hello ${employee.name}!</h3><br>
	<h4>Your ID is ${employee.id}</h4>  
</body>
</html>

当客户端请求中的id参数为10时,home.jsp用于响应有效数据。404.jsp的代码:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>404 Error Page</title>
</head>
<body>

<h2>Resource Not Found Error Occured, please contact support.</h2>

</body>
</html>

当我们收到客户端请求中的id为3时,我们使用404.jsp生成404 http状态码的视图,这应该是我们实现中的响应。error.jsp代码:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<%@ taglib uri="https://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Error Page</title>
</head>
<body>
<h2>Application Error, please contact support.</h2>

<h3>Debug Information:</h3>

Requested URL= ${url}<br><br>

Exception= ${exception.message}<br><br>

<strong>Exception Stack Trace</strong><br>
<c:forEach items="${exception.stackTrace}" var="ste">
	${ste}
</c:forEach>

</body>
</html>

error.jsp在我们的控制器类请求处理方法抛出EmployeeNotFoundException时使用。当客户端请求中的id值为1时,我们应该在响应中获得该页面。database_error.jsp的代码:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Database Error Page</title>
</head>
<body>

<h2>Database Error, please contact support.</h2>

</body>
</html>

在GlobalExceptionHandler类中配置了当我们的应用程序抛出SQLException时使用database_error.jsp。当客户端请求中的id值为2时,我们应该得到这个页面作为响应。generic_error.jsp的代码:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Generic Error Page</title>
</head>
<body>

<h2>Unknown Error Occured, please contact support.</h2>

</body>
</html>

只有在我们的应用代码未处理的任何异常发生时,才会出现这个页面作为响应,并且simpleMappingExceptionResolver bean负责处理。当客户端请求中的id值不是1、2、3或10时,我们应该得到这个页面作为响应。

运行Spring MVC异常处理应用程序

只需在您正在使用的servlet容器中部署应用程序,本示例使用Apache Tomcat 7。下面的图片显示了我们的应用程序根据id值返回的不同响应页面。ID=10,有效的响应。ID=1,使用控制器异常处理程序。ID=2,使用全局异常处理程序并将视图作为响应。ID=3,使用404错误页面。ID=4,使用simpleMappingExceptionResolver作为响应视图。正如您所看到的,我们在所有情况下都得到了预期的响应。

春季异常处理的JSON响应

我们的教程即将完成,只剩下最后一部分,即我将解释如何在异常处理方法中发送JSON响应。我们的应用程序具有所有的JSON依赖项,并配置了jsonMessageConverter,我们只需要实现异常处理程序方法即可。为了简单起见,我将重新编写EmployeeController的handleEmployeeNotFoundException()方法,以返回JSON响应。只需更新EmployeeController的异常处理程序方法为以下代码,并重新部署应用程序。

	@ExceptionHandler(EmployeeNotFoundException.class)
	public @ResponseBody ExceptionJSONInfo handleEmployeeNotFoundException(HttpServletRequest request, Exception ex){
		
		ExceptionJSONInfo response = new ExceptionJSONInfo();
		response.setUrl(request.getRequestURL().toString());
		response.setMessage(ex.getMessage());
		
		return response;
	}

现在当我们在客户端请求中使用id为1时,我们会得到下面图片中显示的JSON响应。这就是Spring异常处理和Spring MVC异常处理的全部内容,请从下面的URL下载应用程序并尽情使用以获取更多的学习经验。

下载Spring异常处理项目

发表回复 0

Your email address will not be published. Required fields are marked *


广告
将在 10 秒后关闭
bannerAds