Mkyong-中文博客翻译-九-

Mkyong 中文博客翻译(九)

原文:Mkyong

协议:CC BY-NC-SA 4.0

从资源文件夹中读取文件

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring/spring-read-file-from-resources-folder/

在 Spring 中,我们可以使用ClassPathResourceResourceLoader轻松地从类路径中获取文件。

用弹簧 5.1.4 测试 P.S .释放

1.资源中心/主要/资源/

例如,src/main/resources/文件夹中的图像文件

resources

2.ClassPathResource

 import org.springframework.core.io.Resource;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.InputStream;

	Resource resource = new ClassPathResource("android.png");

	InputStream input = resource.getInputStream();

	File file = resource.getFile(); 

3.资源加载器

 import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.File;
import java.io.InputStream;

	@Autowired
    ResourceLoader resourceLoader;

	Resource resource = resourceLoader.getResource("classpath:android.png");

	InputStream input = resource.getInputStream();

	File file = resource.getFile(); 

4.资源工具

请不要使用这个ResourceUtils即使它有效,这个类主要是在框架内部使用。阅读 ResourceUtils JavaDocs

 import org.springframework.util.ResourceUtils;

	File file = ResourceUtils.getFile("classpath:android.png"); 

参考

带有 ResourceBundleMessageSource 示例的 Spring 资源包

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring/spring-resource-bundle-with-resourcebundlemessagesource-example/

在 Spring 中,您可以使用ResourceBundleMessageSource来解析来自属性文件的文本消息,基于所选择的地区。请参见以下示例:

1.目录结构

查看此示例的目录结构。

directory structure of this example

2.属性文件

创建两个属性文件,一个用于英文字符(messages_en_US.properties),另一个用于中文字符(messages_zh_CN.properties)。将其放入项目类路径中(见上图)。

文件:messages_en_US.properties

 customer.name=Yong Mook Kim, age : {0}, URL : {1} 

文件:messages_zh_CN.properties

 customer.name=\ufeff\u6768\u6728\u91d1, age : {0}, URL : {1} 

'\ u eff \ u 6768 \ u 6728 \ u91d 1'为中文 Unicode 字符。

Note
To display the Chinese characters correctly, you have to use “native2ascii” tool to convert the Chinese characters into Unicode characters.

3.Bean 配置文件

将属性文件包含到 bean 配置文件中。“messages _ en _ us . properties”和“messages _ zh _ cn . properties”在 Spring 中都被认为是一个文件,你只需要包含文件名一次,Spring 就会自动找到正确的区域设置。

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

	<bean id="messageSource"
		class="org.springframework.context.support.ResourceBundleMessageSource">
		<property name="basename">
			<value>locale\customer\messages</value>
		</property>
	</bean>

</beans> 

假设这两个文件都位于“资源\区域\客户”文件夹。

4.运行它

 package com.mkyong.common;

import java.util.Locale;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

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

		ApplicationContext context 
			= new ClassPathXmlApplicationContext("locale.xml");

		String name = context.getMessage("customer.name", 
				new Object[] { 28,"http://www.mkyong.com" }, Locale.US);

		System.out.println("Customer name (English) : " + name);

		String namechinese = context.getMessage("customer.name", 
				new Object[] {28, "http://www.mkyong.com" }, 
                                        Locale.SIMPLIFIED_CHINESE);

		System.out.println("Customer name (Chinese) : " + namechinese);

	}
} 

输出

outputNote
Make sure your Eclipse is able to display Chinese output.

说明

1.在context.getMessage()中,第二个参数是消息参数,你必须将其作为对象数组传递。如果没有可用的参数值,可以只传递一个 null。

 context.getMessage("customer.name",null, Locale.US); 

2.地点。US 将从'messages _ en _ US . properties'中检索消息。简体中文将从“messages _ zh _ cn . properties中检索消息。

More …
Read this article to know how to access the MessageSource inside a bean.

下载源代码

Download it – Spring-MessageSource-Example.zipTags : resource bundle spring

带有 getResource()示例的 Spring 资源加载器

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring/spring-resource-loader-with-getresource-example/

Spring 的资源加载器提供了一个非常通用的 getResource() 方法来从文件系统、类路径或 URL 获取资源,比如(文本文件、媒体文件、图像文件……)。您可以从应用程序上下文中获取 getResource() 方法。

下面的例子展示了如何使用 getResource()

1。文件系统

 Resource resource = appContext.getResource("file:c:\\testing.txt"); 

2。URL 路径

 Resource resource = 
          appContext.getResource("url:http://www.yourdomain.com/testing.txt"); 

3。类别路径

 Resource resource = 
          appContext.getResource("classpath:com/mkyong/common/testing.txt"); 

您只需要指定资源位置,Spring 会处理剩下的工作并返回一个资源对象。

使用getResource()方法的完整示例。

 package com.mkyong.common;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
public class App 
{
    public static void main( String[] args )
    {
    	ApplicationContext appContext = 
    	   new ClassPathXmlApplicationContext(new String[] {"If-you-have-any.xml"});

    	Resource resource = 
           appContext.getResource("classpath:com/mkyong/common/testing.txt");

    try{
     	  InputStream is = resource.getInputStream();
          BufferedReader br = new BufferedReader(new InputStreamReader(is));

          String line;
          while ((line = br.readLine()) != null) {
             System.out.println(line);
       	  } 
          br.close();

    	}catch(IOException e){
    		e.printStackTrace();
    	}

    }
} 

Bean 资源加载器(ResourceLoaderAware)

既然 bean 没有应用程序上下文访问权限,那么 bean 如何访问资源呢?解决方法是实现resource loader ware接口,并为 ResourceLoader 对象创建 setter 方法。Spring 会将资源加载器插入到 bean 中。

 package com.mkyong.customer.services;

import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

public class CustomerService implements ResourceLoaderAware
{
	private ResourceLoader resourceLoader;

	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	public Resource getResource(String location){
		return resourceLoader.getResource(location);
	}
} 

Bean 配置文件

 <beans 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

   <bean id="customerService" 
           class="com.mkyong.customer.services.CustomerService" />

</beans> 

运行它

 package com.mkyong.common;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;

import com.mkyong.customer.services.CustomerService;
public class App 
{
    public static void main( String[] args )
    {
    	ApplicationContext appContext = 
    	   new ClassPathXmlApplicationContext(new String[] {"Spring-Customer.xml"});

    	CustomerService cust = 
           (CustomerService)appContext.getBean("customerService");

    	Resource resource = 
            cust.getResource("classpath:com/mkyong/common/testing.txt");

    try{
          InputStream is = resource.getInputStream();
          BufferedReader br = new BufferedReader(new InputStreamReader(is));

          String line;
          while ((line = br.readLine()) != null) {
     	       System.out.println(line);
          } 
          br.close();

    	}catch(IOException e){
    		e.printStackTrace();
    	}

    }
} 

现在,您可以从 bean 中获取资源。

结论

如果没有这个 getResource()方法,您将需要用不同的解决方案处理不同的资源,比如文件系统资源的文件对象,URL 资源的 URL 对象。Spring 确实用这个超级通用的 getResource() 方法做得很好,它确实节省了我们处理资源的时间。

下载源代码

Download it – Spring-getResource-Example.zipspring

弹簧座错误处理示例

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring-boot/spring-rest-error-handling-example/

在本文中,我们将向您展示 Spring Boot REST 应用程序中的错误处理。

使用的技术:

  • Spring Boot 2.1.2 .版本
  • 弹簧 5.1.4 释放
  • maven3
  • Java 8

1./错误

1.1 默认情况下,Spring Boot 为处理所有错误的/error映射提供了一个BasicErrorController控制器,并为生成包含错误细节、HTTP 状态和异常消息的 JSON 响应提供了一个getErrorAttributes

 {	
	"timestamp":"2019-02-27T04:03:52.398+0000",
	"status":500,
	"error":"Internal Server Error",
	"message":"...",
	"path":"/path"
} 

BasicErrorController.java

 package org.springframework.boot.autoconfigure.web.servlet.error;

//...

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

	//...

	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		Map<String, Object> body = getErrorAttributes(request,
				isIncludeStackTrace(request, MediaType.ALL));
		HttpStatus status = getStatus(request);
		return new ResponseEntity<>(body, status);
	} 

在 IDE 中,在这个方法中放置一个断点,你将理解 Spring Boot 如何生成默认的 JSON 错误响应。

2.自定义异常

在 Spring Boot,我们可以使用@ControllerAdvice来处理自定义异常。

2.1 自定义异常。

BookNotFoundException.java

 package com.mkyong.error;

public class BookNotFoundException extends RuntimeException {

    public BookNotFoundException(Long id) {
        super("Book id not found : " + id);
    }

} 

如果没有找到图书 id,控制器抛出上述BookNotFoundException

BookController.java

 package com.mkyong;

//...

@RestController
public class BookController {

    @Autowired
    private BookRepository repository;

    // Find
    @GetMapping("/books/{id}")
    Book findOne(@PathVariable Long id) {
        return repository.findById(id)
                .orElseThrow(() -> new BookNotFoundException(id));
    }

	//...
} 

默认情况下,Spring Boot 生成以下 JSON 错误响应,http 500 error。

Terminal

 curl localhost:8080/books/5

{
	"timestamp":"2019-02-27T04:03:52.398+0000",
	"status":500,
	"error":"Internal Server Error",
	"message":"Book id not found : 5",
	"path":"/books/5"
} 

2.2 如果没有找到图书 id,它应该返回 404 错误而不是 500,我们可以像这样覆盖状态代码:

CustomGlobalExceptionHandler.java

 package com.mkyong.error;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // Let Spring BasicErrorController handle the exception, we just override the status code
    @ExceptionHandler(BookNotFoundException.class)
    public void springHandleNotFound(HttpServletResponse response) throws IOException {
        response.sendError(HttpStatus.NOT_FOUND.value());
    }

	//...
} 

2.3 它现在返回一个 404。

Terminal

 curl localhost:8080/books/5

{
	"timestamp":"2019-02-27T04:21:17.740+0000",
	"status":404,
	"error":"Not Found",
	"message":"Book id not found : 5",
	"path":"/books/5"
} 

2.4 此外,我们可以定制整个 JSON 错误响应:

CustomErrorResponse.java

 package com.mkyong.error;

import com.fasterxml.jackson.annotation.JsonFormat;

import java.time.LocalDateTime;

public class CustomErrorResponse {

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime timestamp;
    private int status;
    private String error;

    //...getters setters
} 

CustomGlobalExceptionHandler.java

 package com.mkyong.error;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.time.LocalDateTime;

@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(BookNotFoundException.class)
    public ResponseEntity<CustomErrorResponse> customHandleNotFound(Exception ex, WebRequest request) {

        CustomErrorResponse errors = new CustomErrorResponse();
        errors.setTimestamp(LocalDateTime.now());
        errors.setError(ex.getMessage());
        errors.setStatus(HttpStatus.NOT_FOUND.value());

        return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);

    }

	//...
} 

Terminal

 curl localhost:8080/books/5
{
	"timestamp":"2019-02-27 12:40:45",
	"status":404,
	"error":"Book id not found : 5"
} 

3.JSR 303 验证错误

3.1 对于 Spring @valid验证错误,会抛出handleMethodArgumentNotValid

CustomGlobalExceptionHandler.java

 package com.mkyong.error;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {

    //...

    // @Validate For Validating Path Variables and Request Parameters
    @ExceptionHandler(ConstraintViolationException.class)
    public void constraintViolationException(HttpServletResponse response) throws IOException {
        response.sendError(HttpStatus.BAD_REQUEST.value());
    }

    // error handle for @Valid
    @Override
    protected ResponseEntity<Object>
    handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                 HttpHeaders headers,
                                 HttpStatus status, WebRequest request) {

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("timestamp", new Date());
        body.put("status", status.value());

        //Get all fields errors
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(x -> x.getDefaultMessage())
                .collect(Collectors.toList());

        body.put("errors", errors);

        return new ResponseEntity<>(body, headers, status);

    }

} 

4.ResponseEntityExceptionHandler

4.1 如果我们不确定 Spring Boot 抛出了什么异常,就在这个方法中放一个断点进行调试。

ResponseEntityExceptionHandler.java

 package org.springframework.web.servlet.mvc.method.annotation;

//...
public abstract class ResponseEntityExceptionHandler {

	@ExceptionHandler({
			HttpRequestMethodNotSupportedException.class,
			HttpMediaTypeNotSupportedException.class,
			HttpMediaTypeNotAcceptableException.class,
			MissingPathVariableException.class,
			MissingServletRequestParameterException.class,
			ServletRequestBindingException.class,
			ConversionNotSupportedException.class,
			TypeMismatchException.class,
			HttpMessageNotReadableException.class,
			HttpMessageNotWritableException.class,
			MethodArgumentNotValidException.class,
			MissingServletRequestPartException.class,
			BindException.class,
			NoHandlerFoundException.class,
			AsyncRequestTimeoutException.class
		})
	@Nullable
	public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
		HttpHeaders headers = new HttpHeaders();

		if (ex instanceof HttpRequestMethodNotSupportedException) {
			HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
			return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
		}
		else if (ex instanceof HttpMediaTypeNotSupportedException) {
			HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
			return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
		}
		//...
	}

	//...

} 

5.DefaultErrorAttributes

5.1 为了覆盖所有异常的默认 JSON 错误响应,创建一个 bean 并扩展DefaultErrorAttributes

CustomErrorAttributes.java

 package com.mkyong.error;

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

@Component
public class CustomErrorAttributes extends DefaultErrorAttributes {

    private static final DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {

        // Let Spring handle the error first, we will modify later :)
        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);

        // format & update timestamp
        Object timestamp = errorAttributes.get("timestamp");
        if (timestamp == null) {
            errorAttributes.put("timestamp", dateFormat.format(new Date()));
        } else {
            errorAttributes.put("timestamp", dateFormat.format((Date) timestamp));
        }

        // insert a new key
        errorAttributes.put("version", "1.2");

        return errorAttributes;

    }

} 

现在,日期时间被格式化,一个新字段–version被添加到 JSON 错误响应中。

 curl localhost:8080/books/5

{
	"timestamp":"2019/02/27 13:34:24",
	"status":404,
	"error":"Not Found",
	"message":"Book id not found : 5",
	"path":"/books/5",
	"version":"1.2"
}

curl localhost:8080/abc

{
	"timestamp":"2019/02/27 13:35:10",
	"status":404,
	"error":"Not Found",
	"message":"No message available",
	"path":"/abc",
	"version":"1.2"
} 

完成了。

下载源代码

$ git clone https://github.com/mkyong/spring-boot.git
$ cd spring-rest-error-handling
$ mvn spring-boot:run

参考

春季休息你好世界示例

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring-boot/spring-rest-hello-world-example/

Spring REST hello world

在本文中,我们将向您展示如何开发一个 Spring Boot REST 风格的 web 服务来处理来自 H2 内存数据库的 CRUD 操作。

使用的技术:

  • Spring Boot 2.1.2 .版本
  • 弹簧 5.1.4 释放
  • 春季数据 JPA 2.1.4.RELEASE
  • H2 内存数据库 1.4.197
  • Tomcat Embed 9.0.14
  • JUnit 4.12
  • maven3
  • Java 8

1.项目目录

project directory

2.专家

包括用于 Spring MVC 和 REST 结构的spring-boot-starter-web,用于 CRUD 库的spring-boot-starter-data-jpa

pom.xml

 <?xml version="1.0" encoding="UTF-8"?>
<project 
         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>

    <artifactId>spring-rest-hello-world</artifactId>
    <packaging>jar</packaging>
    <name>Spring Boot REST API Example</name>
    <version>1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
    </parent>

    <!-- Java 8 -->
    <properties>
        <java.version>1.8</java.version>
        <downloadSources>true</downloadSources>
        <downloadJavadocs>true</downloadJavadocs>
    </properties>

    <dependencies>

        <!-- spring mvc, rest -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- jpa, crud repository -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- in-memory database  -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>

        <!-- unit test rest -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- test patch operation need this -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.7</version>
            <scope>test</scope>
        </dependency>

        <!-- hot swapping, disable cache for template, enable live reload -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>

    <build>
        <plugins>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <addResources>true</addResources>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.0</version>
            </plugin>

        </plugins>

    </build>
</project> 

项目依赖关系。

 $ mvn dependency:tree

[INFO] org.springframework.boot:spring-rest-hello-world:jar:1.0
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.1.2.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.2.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.1:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.11.1:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.25:compile
[INFO] |  |  +- javax.annotation:javax.annotation-api:jar:1.3.2:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.23:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-json:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.9.8:compile
[INFO] |  |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.9.0:compile
[INFO] |  |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.9.8:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.9.8:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.8:compile
[INFO] |  |  \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.9.8:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.14:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.14:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.14:compile
[INFO] |  +- org.hibernate.validator:hibernate-validator:jar:6.0.14.Final:compile
[INFO] |  |  +- javax.validation:validation-api:jar:2.0.1.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.3.2.Final:compile
[INFO] |  |  \- com.fasterxml:classmate:jar:1.4.0:compile
[INFO] |  +- org.springframework:spring-web:jar:5.1.4.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-beans:jar:5.1.4.RELEASE:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:5.1.4.RELEASE:compile
[INFO] |     +- org.springframework:spring-aop:jar:5.1.4.RELEASE:compile
[INFO] |     +- org.springframework:spring-context:jar:5.1.4.RELEASE:compile
[INFO] |     \- org.springframework:spring-expression:jar:5.1.4.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.1.2.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-aop:jar:2.1.2.RELEASE:compile
[INFO] |  |  \- org.aspectj:aspectjweaver:jar:1.9.2:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- com.zaxxer:HikariCP:jar:3.2.0:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:5.1.4.RELEASE:compile
[INFO] |  +- javax.transaction:javax.transaction-api:jar:1.3:compile
[INFO] |  +- javax.xml.bind:jaxb-api:jar:2.3.1:compile
[INFO] |  |  \- javax.activation:javax.activation-api:jar:1.2.0:compile
[INFO] |  +- org.hibernate:hibernate-core:jar:5.3.7.Final:compile
[INFO] |  |  +- javax.persistence:javax.persistence-api:jar:2.2:compile
[INFO] |  |  +- org.javassist:javassist:jar:3.23.1-GA:compile
[INFO] |  |  +- net.bytebuddy:byte-buddy:jar:1.9.7:compile
[INFO] |  |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  |  +- org.jboss:jandex:jar:2.0.5.Final:compile
[INFO] |  |  +- org.dom4j:dom4j:jar:2.1.1:compile
[INFO] |  |  \- org.hibernate.common:hibernate-commons-annotations:jar:5.0.4.Final:compile
[INFO] |  +- org.springframework.data:spring-data-jpa:jar:2.1.4.RELEASE:compile
[INFO] |  |  +- org.springframework.data:spring-data-commons:jar:2.1.4.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-orm:jar:5.1.4.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-tx:jar:5.1.4.RELEASE:compile
[INFO] |  |  \- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] |  \- org.springframework:spring-aspects:jar:5.1.4.RELEASE:compile
[INFO] +- com.h2database:h2:jar:1.4.197:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.1.2.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test:jar:2.1.2.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.1.2.RELEASE:test
[INFO] |  +- com.jayway.jsonpath:json-path:jar:2.4.0:test
[INFO] |  |  \- net.minidev:json-smart:jar:2.3:test
[INFO] |  |     \- net.minidev:accessors-smart:jar:1.2:test
[INFO] |  |        \- org.ow2.asm:asm:jar:5.0.4:test
[INFO] |  +- junit:junit:jar:4.12:test
[INFO] |  +- org.assertj:assertj-core:jar:3.11.1:test
[INFO] |  +- org.mockito:mockito-core:jar:2.23.4:test
[INFO] |  |  +- net.bytebuddy:byte-buddy-agent:jar:1.9.7:test
[INFO] |  |  \- org.objenesis:objenesis:jar:2.6:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  +- org.skyscreamer:jsonassert:jar:1.5.0:test
[INFO] |  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] |  +- org.springframework:spring-core:jar:5.1.4.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-jcl:jar:5.1.4.RELEASE:compile
[INFO] |  +- org.springframework:spring-test:jar:5.1.4.RELEASE:test
[INFO] |  \- org.xmlunit:xmlunit-core:jar:2.6.2:test
[INFO] +- org.apache.httpcomponents:httpclient:jar:4.5.7:test
[INFO] |  +- org.apache.httpcomponents:httpcore:jar:4.4.10:test
[INFO] |  \- commons-codec:commons-codec:jar:1.11:test
[INFO] \- org.springframework.boot:spring-boot-devtools:jar:2.1.2.RELEASE:compile (optional)
[INFO]    +- org.springframework.boot:spring-boot:jar:2.1.2.RELEASE:compile
[INFO]    \- org.springframework.boot:spring-boot-autoconfigure:jar:2.1.2.RELEASE:compile 

3.弹簧支架

3.1 一个 REST 控制器创建以下 REST API 端点:

HTTP 方法 上呼吸道感染 描述
得到 /书籍 列出所有书籍。
邮政 /书籍 存一本书。
得到 /books/ 找到一本 id = {:id}的书。
/books/ 在 id = {:id}处更新或保存图书。
修补 /books/ 更新单个字段,其中 id = {:id}。
删除 /books/ 删除 id = {:id}的图书。

BookController.java

 package com.mkyong;

import com.mkyong.error.BookNotFoundException;
import com.mkyong.error.BookUnSupportedFieldPatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
public class BookController {

    @Autowired
    private BookRepository repository;

    // Find
    @GetMapping("/books")
    List<Book> findAll() {
        return repository.findAll();
    }

    // Save
    //return 201 instead of 200
    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping("/books")
    Book newBook(@RequestBody Book newBook) {
        return repository.save(newBook);
    }

    // Find
    @GetMapping("/books/{id}")
    Book findOne(@PathVariable Long id) {
        return repository.findById(id)
                .orElseThrow(() -> new BookNotFoundException(id));
    }

    // Save or update
    @PutMapping("/books/{id}")
    Book saveOrUpdate(@RequestBody Book newBook, @PathVariable Long id) {

        return repository.findById(id)
                .map(x -> {
                    x.setName(newBook.getName());
                    x.setAuthor(newBook.getAuthor());
                    x.setPrice(newBook.getPrice());
                    return repository.save(x);
                })
                .orElseGet(() -> {
                    newBook.setId(id);
                    return repository.save(newBook);
                });
    }

    // update author only
    @PatchMapping("/books/{id}")
    Book patch(@RequestBody Map<String, String> update, @PathVariable Long id) {

        return repository.findById(id)
                .map(x -> {

                    String author = update.get("author");
                    if (!StringUtils.isEmpty(author)) {
                        x.setAuthor(author);

                        // better create a custom method to update a value = :newValue where id = :id
                        return repository.save(x);
                    } else {
                        throw new BookUnSupportedFieldPatchException(update.keySet());
                    }

                })
                .orElseGet(() -> {
                    throw new BookNotFoundException(id);
                });

    }

    @DeleteMapping("/books/{id}")
    void deleteBook(@PathVariable Long id) {
        repository.deleteById(id);
    }

} 

4.春季数据 JPA

4.1 用 JPA 注释标注的模型。

Book.java

 package com.mkyong;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.math.BigDecimal;

@Entity
public class Book {

    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String author;
    private BigDecimal price;

    //setters, getters, constructors...
} 

4.2 创建一个类并扩展JpaRepository,它包含所有的 CRUD 操作。

BookRepository.java

 package com.mkyong;

import org.springframework.data.jpa.repository.JpaRepository;

// Spring Data magic :)
public interface BookRepository extends JpaRepository<Book, Long> {
} 

5.基本错误处理

创建一个@ControllerAdvice来处理控制器抛出的定制异常。

CustomGlobalExceptionHandler

 package com.mkyong.error;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // Let Spring handle the exception, we just override the status code
    @ExceptionHandler(BookNotFoundException.class)
    public void springHandleNotFound(HttpServletResponse response) throws IOException {
        response.sendError(HttpStatus.NOT_FOUND.value());
    }

    @ExceptionHandler(BookUnSupportedFieldPatchException.class)
    public void springUnSupportedFieldPatch(HttpServletResponse response) throws IOException {
        response.sendError(HttpStatus.METHOD_NOT_ALLOWED.value());
    }

} 

BookNotFoundException.java

 package com.mkyong.error;

public class BookNotFoundException extends RuntimeException {

    public BookNotFoundException(Long id) {
        super("Book id not found : " + id);
    }

} 

BookUnSupportedFieldPatchException.java

 package com.mkyong.error;

import java.util.Set;

public class BookUnSupportedFieldPatchException extends RuntimeException {

    public BookUnSupportedFieldPatchException(Set<String> keys) {
        super("Field " + keys.toString() + " update is not allow.");
    }

} 

6.Spring Boot

6.1 创建一个@SpringBootApplication类来启动 REST web 应用程序,并将 3 个预定义的 book 对象插入到 H2 内存数据库中进行演示。

StartBookApplication.java

 package com.mkyong;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.math.BigDecimal;

@SpringBootApplication
public class StartBookApplication {

    public static void main(String[] args) {
        SpringApplication.run(StartBookApplication.class, args);
    }

	// init bean to insert 3 books into h2 database.
    @Bean
    CommandLineRunner initDatabase(BookRepository repository) {
        return args -> {
            repository.save(new Book("A Guide to the Bodhisattva Way of Life", "Santideva", new BigDecimal("15.41")));
            repository.save(new Book("The Life-Changing Magic of Tidying Up", "Marie Kondo", new BigDecimal("9.69")));
            repository.save(new Book("Refactoring: Improving the Design of Existing Code", "Martin Fowler", new BigDecimal("47.99")));
        };
    }
} 

7.演示

启动 Spring Boot 应用程序,用curl命令测试 REST API 端点。

 $ mvn spring-boot:run 

7.1 查找全部—GET /books

 > curl -v localhost:8080/books

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /books HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
>
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 06:33:57 GMT
<

[
	{"id":1,"name":"A Guide to the Bodhisattva Way of Life","author":"Santideva","price":15.41},
	{"id":2,"name":"The Life-Changing Magic of Tidying Up","author":"Marie Kondo","price":9.69},
	{"id":3,"name":"Refactoring: Improving the Design of Existing Code","author":"Martin Fowler","price":47.99}
] 

7.2 找到一个—GET /books/1

 > curl -v localhost:8080/books/1

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /books/1 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
>
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 06:37:26 GMT
<

{
	"id":1,
	"name":"A Guide to the Bodhisattva Way of Life",
	"author":"Santideva",
	"price":15.41
} 

7.3 试验 404-GET /books/5

 > curl -v localhost:8080/books/5

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /books/5 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
>
< HTTP/1.1 404
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 06:38:45 GMT
<

{
	"timestamp":"2019-02-19T06:38:45.743+0000",
	"status":404,
	"error":"Not Found",
	"message":"Book id not found : 5",
	"path":"/books/5"
} 

7.4 测试保存—POST /books -d {json}

 #Run this on Windows, need \"

> curl -v -X POST localhost:8080/books -H "Content-type:application/json" -d "{\"name\":\"Spring REST tutorials\",\"author\":\"mkyong\",\"price\":\"9.99\"}"

Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /books HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
> Content-type:application/json
> Content-Length: 65
>
* upload completely sent off: 65 out of 65 bytes
< HTTP/1.1 201
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 07:33:01 GMT
<
{
	"id":4,
	"name":
	"Spring REST tutorials",
	"author":"mkyong",
	"price":9.99
} 

7.5 测试更新—PUT /books/4 -d {json}

 > curl -v -X PUT localhost:8080/books/4 -H "Content-type:application/json" -d "{\"name\":\"Spring Forever\",\"author\":\"pivotal\",\"price\":\"9.99\"}"

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> PUT /books/4 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
> Content-type:application/json
> Content-Length: 59
>
* upload completely sent off: 59 out of 59 bytes
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 07:36:49 GMT
<
{
	"id":4,
	"name":"Spring Forever",
	"author":"pivotal",
	"price":9.99
}

> curl localhost:8080/books/4
{"id":4,"name":"Spring Forever","author":"pivotal","price":9.99} 

7.6 测试更新“作者”字段—PATCH /books/4 -d {json}

 > curl -v -X PATCH localhost:8080/books/4 -H "Content-type:application/json" -d "{\"author\":\"oracle\"}"
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> PATCH /books/4 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
> Content-type:application/json
> Content-Length: 19
>
* upload completely sent off: 19 out of 19 bytes
< HTTP/1.1 200
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 07:39:53 GMT

{	
	"id":4,
	"name":"Spring Forever",
	"author":"oracle",
	"price":9.99
} 

7.6.1 测试更新“名称”字段—PATCH /books/4 -d {json}

 > curl -v -X PATCH localhost:8080/books/4 -H "Content-type:application/json" -d "{\"name\":\"New Spring REST\"}"
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> PATCH /books/4 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
> Content-type:application/json
> Content-Length: 26
>
* upload completely sent off: 26 out of 26 bytes
< HTTP/1.1 405
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Tue, 19 Feb 2019 07:40:47 GMT
<
{	
	"timestamp":"2019-02-19T07:40:47.740+0000",
	"status":405,
	"error":"Method Not Allowed",
	"message":"Field [name] update is not allow.",
	"path":"/books/4"
} 

7.7 测试删除-DELETE /books/4

 > curl -v -X DELETE localhost:8080/books/4

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> DELETE /books/4 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: <strong>/</strong>
>
< HTTP/1.1 200
< Content-Length: 0
< Date: Tue, 19 Feb 2019 07:44:24 GMT
<

> curl localhost:8080/books/4
{
	"timestamp":"2019-02-19T07:44:39.432+0000",
	"status":404,
	"error":"Not Found",
	"message":"Book id not found : 4",
	"path":"/books/4"
}

> curl localhost:8080/books
[
	{"id":1,"name":"A Guide to the Bodhisattva Way of Life","author":"Santideva","price":15.41},
	{"id":2,"name":"The Life-Changing Magic of Tidying Up","author":"Marie Kondo","price":9.69},
	{"id":3,"name":"Refactoring: Improving the Design of Existing Code","author":"Martin Fowler","price":47.99}
] 

下载源代码

$ git clone https://github.com/mkyong/spring-boot.git
$ cd spring-rest-hello-world
$ mvn spring-boot:run

参考

弹簧座集成测试示例

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring-boot/spring-rest-integration-test-example/

spring boot test

在本文中,我们将向您展示如何测试 Spring Boot REST 应用程序。通常,我们使用MockMvcTestRestTemplate进行集成测试。

使用的技术:

  • Spring Boot 2.1.2 .版本
  • 弹簧 5.1.4 释放
  • maven3
  • Java 8

pom.xml

 <!-- spring integration test -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>

	<!-- spring integration test for security-->
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-test</artifactId>
		<scope>test</scope>
	</dependency> 

1. MockMvc

 @RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class BookControllerTest {

    @Autowired
    private MockMvc mockMvc; 

1.1 本 Spring REST Hello World 示例的 CRUD 测试

 package com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static org.hamcrest.Matchers.is;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

//@WebMvcTest(BookController.class)
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {

    private static final ObjectMapper om = new ObjectMapper();

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookRepository mockRepository;

    @Before
    public void init() {
        Book book = new Book(1L, "Book Name", "Mkyong", new BigDecimal("9.99"));
        when(mockRepository.findById(1L)).thenReturn(Optional.of(book));
    }

    @Test
    public void find_bookId_OK() throws Exception {

        mockMvc.perform(get("/books/1"))
                /*.andDo(print())*/
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.name", is("Book Name")))
                .andExpect(jsonPath("$.author", is("Mkyong")))
                .andExpect(jsonPath("$.price", is(9.99)));

        verify(mockRepository, times(1)).findById(1L);

    }

    @Test
    public void find_allBook_OK() throws Exception {

        List<Book> books = Arrays.asList(
                new Book(1L, "Book A", "Ah Pig", new BigDecimal("1.99")),
                new Book(2L, "Book B", "Ah Dog", new BigDecimal("2.99")));

        when(mockRepository.findAll()).thenReturn(books);

        mockMvc.perform(get("/books"))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].id", is(1)))
                .andExpect(jsonPath("$[0].name", is("Book A")))
                .andExpect(jsonPath("$[0].author", is("Ah Pig")))
                .andExpect(jsonPath("$[0].price", is(1.99)))
                .andExpect(jsonPath("$[1].id", is(2)))
                .andExpect(jsonPath("$[1].name", is("Book B")))
                .andExpect(jsonPath("$[1].author", is("Ah Dog")))
                .andExpect(jsonPath("$[1].price", is(2.99)));

        verify(mockRepository, times(1)).findAll();
    }

    @Test
    public void find_bookIdNotFound_404() throws Exception {
        mockMvc.perform(get("/books/5")).andExpect(status().isNotFound());
    }

    @Test
    public void save_book_OK() throws Exception {

        Book newBook = new Book(1L, "Spring Boot Guide", "mkyong", new BigDecimal("2.99"));
        when(mockRepository.save(any(Book.class))).thenReturn(newBook);

        mockMvc.perform(post("/books")
                .content(om.writeValueAsString(newBook))
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                /*.andDo(print())*/
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.name", is("Spring Boot Guide")))
                .andExpect(jsonPath("$.author", is("mkyong")))
                .andExpect(jsonPath("$.price", is(2.99)));

        verify(mockRepository, times(1)).save(any(Book.class));

    }

    @Test
    public void update_book_OK() throws Exception {

        Book updateBook = new Book(1L, "ABC", "mkyong", new BigDecimal("19.99"));
        when(mockRepository.save(any(Book.class))).thenReturn(updateBook);

        mockMvc.perform(put("/books/1")
                .content(om.writeValueAsString(updateBook))
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.name", is("ABC")))
                .andExpect(jsonPath("$.author", is("mkyong")))
                .andExpect(jsonPath("$.price", is(19.99)));

    }

    @Test
    public void patch_bookAuthor_OK() throws Exception {

        when(mockRepository.save(any(Book.class))).thenReturn(new Book());
        String patchInJson = "{\"author\":\"ultraman\"}";

        mockMvc.perform(patch("/books/1")
                .content(patchInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk());

        verify(mockRepository, times(1)).findById(1L);
        verify(mockRepository, times(1)).save(any(Book.class));

    }

    @Test
    public void patch_bookPrice_405() throws Exception {

        String patchInJson = "{\"price\":\"99.99\"}";

        mockMvc.perform(patch("/books/1")
                .content(patchInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andExpect(status().isMethodNotAllowed());

        verify(mockRepository, times(1)).findById(1L);
        verify(mockRepository, times(0)).save(any(Book.class));
    }

    @Test
    public void delete_book_OK() throws Exception {

        doNothing().when(mockRepository).deleteById(1L);

        mockMvc.perform(delete("/books/1"))
                /*.andDo(print())*/
                .andExpect(status().isOk());

        verify(mockRepository, times(1)).deleteById(1L);
    }

    private static void printJSON(Object object) {
        String result;
        try {
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            System.out.println(result);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

} 

1.2 此弹簧座验证的测试

 package com.mkyong;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {

    private static final ObjectMapper om = new ObjectMapper();

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookRepository mockRepository;

    /*
        {
            "timestamp":"2019-03-05T09:34:13.280+0000",
            "status":400,
            "errors":["Author is not allowed.","Please provide a price","Please provide a author"]
        }
     */
    @Test
    public void save_emptyAuthor_emptyPrice_400() throws Exception {

        String bookInJson = "{\"name\":\"ABC\"}";

        mockMvc.perform(post("/books")
                .content(bookInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.timestamp", is(notNullValue())))
                .andExpect(jsonPath("$.status", is(400)))
                .andExpect(jsonPath("$.errors").isArray())
                .andExpect(jsonPath("$.errors", hasSize(3)))
                .andExpect(jsonPath("$.errors", hasItem("Author is not allowed.")))
                .andExpect(jsonPath("$.errors", hasItem("Please provide a author")))
                .andExpect(jsonPath("$.errors", hasItem("Please provide a price")));

        verify(mockRepository, times(0)).save(any(Book.class));

    }

    /*
        {
            "timestamp":"2019-03-05T09:34:13.207+0000",
            "status":400,
            "errors":["Author is not allowed."]
        }
     */
    @Test
    public void save_invalidAuthor_400() throws Exception {

        String bookInJson = "{\"name\":\" Spring REST tutorials\", \"author\":\"abc\",\"price\":\"9.99\"}";

        mockMvc.perform(post("/books")
                .content(bookInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.timestamp", is(notNullValue())))
                .andExpect(jsonPath("$.status", is(400)))
                .andExpect(jsonPath("$.errors").isArray())
                .andExpect(jsonPath("$.errors", hasSize(1)))
                .andExpect(jsonPath("$.errors", hasItem("Author is not allowed.")));

        verify(mockRepository, times(0)).save(any(Book.class));

    }

} 

1.3 用@WithMockUser对该弹簧座安全进行测试

 package com.mkyong;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.math.BigDecimal;
import java.util.Optional;

import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookRepository mockRepository;

    @Before
    public void init() {
        Book book = new Book(1L, "A Guide to the Bodhisattva Way of Life", "Santideva", new BigDecimal("15.41"));
        when(mockRepository.findById(1L)).thenReturn(Optional.of(book));
    }

    //@WithMockUser(username = "USER")
    @WithMockUser("USER")
    @Test
    public void find_login_ok() throws Exception {

        mockMvc.perform(get("/books/1"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.name", is("A Guide to the Bodhisattva Way of Life")))
                .andExpect(jsonPath("$.author", is("Santideva")))
                .andExpect(jsonPath("$.price", is(15.41)));
    }

    @Test
    public void find_nologin_401() throws Exception {
        mockMvc.perform(get("/books/1"))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

} 

2.TestRestTemplate

 @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // for restTemplate
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {

    @Autowired
    private TestRestTemplate restTemplate; 

2.1 本 Spring REST Hello World 示例的 CRUD 测试

 package com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // for restTemplate
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {

    private static final ObjectMapper om = new ObjectMapper();

    @Autowired
    private TestRestTemplate restTemplate;

    @MockBean
    private BookRepository mockRepository;

    @Before
    public void init() {
        Book book = new Book(1L, "Book Name", "Mkyong", new BigDecimal("9.99"));
        when(mockRepository.findById(1L)).thenReturn(Optional.of(book));
    }

    @Test
    public void find_bookId_OK() throws JSONException {

        String expected = "{id:1,name:\"Book Name\",author:\"Mkyong\",price:9.99}";

        ResponseEntity<String> response = restTemplate.getForEntity("/books/1", String.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertEquals(MediaType.APPLICATION_JSON_UTF8, response.getHeaders().getContentType());

        JSONAssert.assertEquals(expected, response.getBody(), false);

        verify(mockRepository, times(1)).findById(1L);

    }

    @Test
    public void find_allBook_OK() throws Exception {

        List<Book> books = Arrays.asList(
                new Book(1L, "Book A", "Ah Pig", new BigDecimal("1.99")),
                new Book(2L, "Book B", "Ah Dog", new BigDecimal("2.99")));

        when(mockRepository.findAll()).thenReturn(books);

        String expected = om.writeValueAsString(books);

        ResponseEntity<String> response = restTemplate.getForEntity("/books", String.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());
        JSONAssert.assertEquals(expected, response.getBody(), false);

        verify(mockRepository, times(1)).findAll();
    }

    @Test
    public void find_bookIdNotFound_404() throws Exception {

        String expected = "{status:404,error:\"Not Found\",message:\"Book id not found : 5\",path:\"/books/5\"}";

        ResponseEntity<String> response = restTemplate.getForEntity("/books/5", String.class);

        assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
        JSONAssert.assertEquals(expected, response.getBody(), false);

    }

    @Test
    public void save_book_OK() throws Exception {

        Book newBook = new Book(1L, "Spring Boot Guide", "mkyong", new BigDecimal("2.99"));
        when(mockRepository.save(any(Book.class))).thenReturn(newBook);

        String expected = om.writeValueAsString(newBook);

        ResponseEntity<String> response = restTemplate.postForEntity("/books", newBook, String.class);

        assertEquals(HttpStatus.CREATED, response.getStatusCode());
        JSONAssert.assertEquals(expected, response.getBody(), false);

        verify(mockRepository, times(1)).save(any(Book.class));

    }

    @Test
    public void update_book_OK() throws Exception {

        Book updateBook = new Book(1L, "ABC", "mkyong", new BigDecimal("19.99"));
        when(mockRepository.save(any(Book.class))).thenReturn(updateBook);

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(om.writeValueAsString(updateBook), headers);

        ResponseEntity<String> response = restTemplate.exchange("/books/1", HttpMethod.PUT, entity, String.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());
        JSONAssert.assertEquals(om.writeValueAsString(updateBook), response.getBody(), false);

        verify(mockRepository, times(1)).findById(1L);
        verify(mockRepository, times(1)).save(any(Book.class));

    }

    @Test
    public void patch_bookAuthor_OK() {

        when(mockRepository.save(any(Book.class))).thenReturn(new Book());
        String patchInJson = "{\"author\":\"ultraman\"}";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(patchInJson, headers);

        ResponseEntity<String> response = restTemplate.exchange("/books/1", HttpMethod.PUT, entity, String.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());

        verify(mockRepository, times(1)).findById(1L);
        verify(mockRepository, times(1)).save(any(Book.class));

    }

    @Test
    public void patch_bookPrice_405() throws JSONException {

        String expected = "{status:405,error:\"Method Not Allowed\",message:\"Field [price] update is not allow.\"}";

        String patchInJson = "{\"price\":\"99.99\"}";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(patchInJson, headers);

        ResponseEntity<String> response = restTemplate.exchange("/books/1", HttpMethod.PATCH, entity, String.class);

        assertEquals(HttpStatus.METHOD_NOT_ALLOWED, response.getStatusCode());
        JSONAssert.assertEquals(expected, response.getBody(), false);

        verify(mockRepository, times(1)).findById(1L);
        verify(mockRepository, times(0)).save(any(Book.class));
    }

    @Test
    public void delete_book_OK() {

        doNothing().when(mockRepository).deleteById(1L);

        HttpEntity<String> entity = new HttpEntity<>(null, new HttpHeaders());
        ResponseEntity<String> response = restTemplate.exchange("/books/1", HttpMethod.DELETE, entity, String.class);

        assertEquals(HttpStatus.OK, response.getStatusCode());

        verify(mockRepository, times(1)).deleteById(1L);
    }

    private static void printJSON(Object object) {
        String result;
        try {
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            System.out.println(result);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

} 

2.2 此弹簧座验证的测试

 package com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // for restTemplate
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {

    private static final ObjectMapper om = new ObjectMapper();

    @Autowired
    private TestRestTemplate restTemplate;

    @MockBean
    private BookRepository mockRepository;

    /*
        {
            "timestamp":"2019-03-05T09:34:13.280+0000",
            "status":400,
            "errors":["Author is not allowed.","Please provide a price","Please provide a author"]
        }
     */
    @Test
    public void save_emptyAuthor_emptyPrice_400() throws JSONException {

        String bookInJson = "{\"name\":\"ABC\"}";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(bookInJson, headers);

        // send json with POST
        ResponseEntity<String> response = restTemplate.postForEntity("/books", entity, String.class);
        //printJSON(response);

        String expectedJson = "{\"status\":400,\"errors\":[\"Author is not allowed.\",\"Please provide a price\",\"Please provide a author\"]}";
        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
        JSONAssert.assertEquals(expectedJson, response.getBody(), false);

        verify(mockRepository, times(0)).save(any(Book.class));

    }

    /*
        {
            "timestamp":"2019-03-05T09:34:13.207+0000",
            "status":400,
            "errors":["Author is not allowed."]
        }
     */
    @Test
    public void save_invalidAuthor_400() throws JSONException {

        String bookInJson = "{\"name\":\" Spring REST tutorials\", \"author\":\"abc\",\"price\":\"9.99\"}";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(bookInJson, headers);

        //Try exchange
        ResponseEntity<String> response = restTemplate.exchange("/books", HttpMethod.POST, entity, String.class);

        String expectedJson = "{\"status\":400,\"errors\":[\"Author is not allowed.\"]}";
        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
        JSONAssert.assertEquals(expectedJson, response.getBody(), false);

        verify(mockRepository, times(0)).save(any(Book.class));

    }

    private static void printJSON(Object object) {
        String result;
        try {
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            System.out.println(result);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

} 

2.3 本弹簧座安全性测试

 package com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {

    private static final ObjectMapper om = new ObjectMapper();

    //@WithMockUser is not working with TestRestTemplate
    @Autowired
    private TestRestTemplate restTemplate;

    @MockBean
    private BookRepository mockRepository;

    @Before
    public void init() {
        Book book = new Book(1L, "A Guide to the Bodhisattva Way of Life", "Santideva", new BigDecimal("15.41"));
        when(mockRepository.findById(1L)).thenReturn(Optional.of(book));
    }

    @Test
    public void find_login_ok() throws Exception {

        String expected = "{id:1,name:\"A Guide to the Bodhisattva Way of Life\",author:\"Santideva\",price:15.41}";

        ResponseEntity<String> response = restTemplate
                .withBasicAuth("user", "password")
                .getForEntity("/books/1", String.class);

        printJSON(response);

        assertEquals(MediaType.APPLICATION_JSON_UTF8, response.getHeaders().getContentType());
        assertEquals(HttpStatus.OK, response.getStatusCode());

        JSONAssert.assertEquals(expected, response.getBody(), false);

    }

    @Test
    public void find_nologin_401() throws Exception {

        String expected = "{\"status\":401,\"error\":\"Unauthorized\",\"message\":\"Unauthorized\",\"path\":\"/books/1\"}";

        ResponseEntity<String> response = restTemplate
                .getForEntity("/books/1", String.class);

        printJSON(response);

        assertEquals(MediaType.APPLICATION_JSON_UTF8, response.getHeaders().getContentType());
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());

        JSONAssert.assertEquals(expected, response.getBody(), false);

    }

    private static void printJSON(Object object) {
        String result;
        try {
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            System.out.println(result);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

} 

下载源代码

$ git clone https://github.com/mkyong/spring-boot.git

$ CD spring-rest-hello-world
$ mvn 测试

$ cd 弹簧支架-验证
$ mvn 测试

$ cd 弹簧-支架-安全
$ mvn 测试

参考

spring boot spring test unit test

弹簧座+弹簧安全示例

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring-boot/spring-rest-spring-security-example/

logo

在本文中,我们将增强前面的 Spring REST 验证示例,通过添加 Spring Security 为请求的 URL(REST API 端点)执行认证和授权

使用的技术:

  • Spring Boot 2.1.2 .版本
  • 弹簧 5.1.4 释放
  • Spring Security 5.1.3 .发布
  • 春季数据 JPA 2.1.4.RELEASE
  • H2 内存数据库 1.4.197
  • Tomcat Embed 9.0.14
  • JUnit 4.12
  • maven3
  • Java 8

1.项目目录

project directory

2.专家

包括弹簧安全的spring-boot-starter-security和弹簧安全集成测试的spring-security-test

pom.xml

 <?xml version="1.0" encoding="UTF-8"?>
<project 
         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>

    <artifactId>spring-rest-security</artifactId>
    <packaging>jar</packaging>
    <name>Spring Boot REST API Example</name>
    <version>1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
    </parent>

    <!-- Java 8 -->
    <properties>
        <java.version>1.8</java.version>
        <downloadSources>true</downloadSources>
        <downloadJavadocs>true</downloadJavadocs>
    </properties>

    <dependencies>

        <!-- spring mvc, rest -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- spring security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

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

        <!-- jpa, crud repository -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!-- in-memory database  -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>

        <!-- unit test rest -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- test patch operation need this -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.7</version>
            <scope>test</scope>
        </dependency>

        <!-- hot swapping, disable cache for template, enable live reload -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>

    <build>
        <plugins>

            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <addResources>true</addResources>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.0</version>
            </plugin>

        </plugins>

    </build>
</project> 

项目依赖关系:

 > mvn dependency:tree

[INFO] --- maven-dependency-plugin:3.1.1:tree (default-cli) @ spring-rest-security ---
[INFO] org.springframework.boot:spring-rest-security:jar:1.0
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.1.2.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.2.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.1:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.11.1:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.25:compile
[INFO] |  |  +- javax.annotation:javax.annotation-api:jar:1.3.2:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.23:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-json:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.9.8:compile
[INFO] |  |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.9.0:compile
[INFO] |  |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.9.8:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.9.8:compile
[INFO] |  |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.8:compile
[INFO] |  |  \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.9.8:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.14:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.14:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.14:compile
[INFO] |  +- org.hibernate.validator:hibernate-validator:jar:6.0.14.Final:compile
[INFO] |  |  +- javax.validation:validation-api:jar:2.0.1.Final:compile
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.3.2.Final:compile
[INFO] |  |  \- com.fasterxml:classmate:jar:1.4.0:compile
[INFO] |  +- org.springframework:spring-web:jar:5.1.4.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-beans:jar:5.1.4.RELEASE:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:5.1.4.RELEASE:compile
[INFO] |     +- org.springframework:spring-context:jar:5.1.4.RELEASE:compile
[INFO] |     \- org.springframework:spring-expression:jar:5.1.4.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-security:jar:2.1.2.RELEASE:compile
[INFO] |  +- org.springframework:spring-aop:jar:5.1.4.RELEASE:compile
[INFO] |  +- org.springframework.security:spring-security-config:jar:5.1.3.RELEASE:compile
[INFO] |  \- org.springframework.security:spring-security-web:jar:5.1.3.RELEASE:compile
[INFO] +- org.springframework.security:spring-security-test:jar:5.1.3.RELEASE:test
[INFO] |  +- org.springframework.security:spring-security-core:jar:5.1.3.RELEASE:compile
[INFO] |  +- org.springframework:spring-core:jar:5.1.4.RELEASE:compile
[INFO] |  |  \- org.springframework:spring-jcl:jar:5.1.4.RELEASE:compile
[INFO] |  \- org.springframework:spring-test:jar:5.1.4.RELEASE:test
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.1.2.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-aop:jar:2.1.2.RELEASE:compile
[INFO] |  |  \- org.aspectj:aspectjweaver:jar:1.9.2:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.1.2.RELEASE:compile
[INFO] |  |  +- com.zaxxer:HikariCP:jar:3.2.0:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:5.1.4.RELEASE:compile
[INFO] |  +- javax.transaction:javax.transaction-api:jar:1.3:compile
[INFO] |  +- javax.xml.bind:jaxb-api:jar:2.3.1:compile
[INFO] |  |  \- javax.activation:javax.activation-api:jar:1.2.0:compile
[INFO] |  +- org.hibernate:hibernate-core:jar:5.3.7.Final:compile
[INFO] |  |  +- javax.persistence:javax.persistence-api:jar:2.2:compile
[INFO] |  |  +- org.javassist:javassist:jar:3.23.1-GA:compile
[INFO] |  |  +- net.bytebuddy:byte-buddy:jar:1.9.7:compile
[INFO] |  |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  |  +- org.jboss:jandex:jar:2.0.5.Final:compile
[INFO] |  |  +- org.dom4j:dom4j:jar:2.1.1:compile
[INFO] |  |  \- org.hibernate.common:hibernate-commons-annotations:jar:5.0.4.Final:compile
[INFO] |  +- org.springframework.data:spring-data-jpa:jar:2.1.4.RELEASE:compile
[INFO] |  |  +- org.springframework.data:spring-data-commons:jar:2.1.4.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-orm:jar:5.1.4.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-tx:jar:5.1.4.RELEASE:compile
[INFO] |  |  \- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] |  \- org.springframework:spring-aspects:jar:5.1.4.RELEASE:compile
[INFO] +- com.h2database:h2:jar:1.4.197:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.1.2.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test:jar:2.1.2.RELEASE:test
[INFO] |  +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.1.2.RELEASE:test
[INFO] |  +- com.jayway.jsonpath:json-path:jar:2.4.0:test
[INFO] |  |  \- net.minidev:json-smart:jar:2.3:test
[INFO] |  |     \- net.minidev:accessors-smart:jar:1.2:test
[INFO] |  |        \- org.ow2.asm:asm:jar:5.0.4:test
[INFO] |  +- junit:junit:jar:4.12:test
[INFO] |  +- org.assertj:assertj-core:jar:3.11.1:test
[INFO] |  +- org.mockito:mockito-core:jar:2.23.4:test
[INFO] |  |  +- net.bytebuddy:byte-buddy-agent:jar:1.9.7:test
[INFO] |  |  \- org.objenesis:objenesis:jar:2.6:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  +- org.skyscreamer:jsonassert:jar:1.5.0:test
[INFO] |  |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] |  \- org.xmlunit:xmlunit-core:jar:2.6.2:test
[INFO] +- org.apache.httpcomponents:httpclient:jar:4.5.7:test
[INFO] |  +- org.apache.httpcomponents:httpcore:jar:4.4.10:test
[INFO] |  \- commons-codec:commons-codec:jar:1.11:test
[INFO] \- org.springframework.boot:spring-boot-devtools:jar:2.1.2.RELEASE:compile (optional)
[INFO]    +- org.springframework.boot:spring-boot:jar:2.1.2.RELEASE:compile
[INFO]    \- org.springframework.boot:spring-boot-autoconfigure:jar:2.1.2.RELEASE:compile 

3.弹簧控制器

再次回顾一下 Book 控制器,稍后我们将集成 Spring Security 来保护 REST 端点。

BookController.java

 package com.mkyong;

import com.mkyong.error.BookNotFoundException;
import com.mkyong.error.BookUnSupportedFieldPatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.List;
import java.util.Map;

@RestController
@Validated
public class BookController {

    @Autowired
    private BookRepository repository;

    // Find
    @GetMapping("/books")
    List<Book> findAll() {
        return repository.findAll();
    }

    // Save
    @PostMapping("/books")
	@ResponseStatus(HttpStatus.CREATED)
    Book newBook(@Valid @RequestBody Book newBook) {
        return repository.save(newBook);
    }

    // Find
    @GetMapping("/books/{id}")
    Book findOne(@PathVariable @Min(1) Long id) {
        return repository.findById(id)
                .orElseThrow(() -> new BookNotFoundException(id));
    }

    // Save or update
    @PutMapping("/books/{id}")
    Book saveOrUpdate(@RequestBody Book newBook, @PathVariable Long id) {

        return repository.findById(id)
                .map(x -> {
                    x.setName(newBook.getName());
                    x.setAuthor(newBook.getAuthor());
                    x.setPrice(newBook.getPrice());
                    return repository.save(x);
                })
                .orElseGet(() -> {
                    newBook.setId(id);
                    return repository.save(newBook);
                });
    }

    // update author only
    @PatchMapping("/books/{id}")
    Book patch(@RequestBody Map<String, String> update, @PathVariable Long id) {

        return repository.findById(id)
                .map(x -> {

                    String author = update.get("author");
                    if (!StringUtils.isEmpty(author)) {
                        x.setAuthor(author);

                        // better create a custom method to update a value = :newValue where id = :id
                        return repository.save(x);
                    } else {
                        throw new BookUnSupportedFieldPatchException(update.keySet());
                    }

                })
                .orElseGet(() -> {
                    throw new BookNotFoundException(id);
                });

    }

    @DeleteMapping("/books/{id}")
    void deleteBook(@PathVariable Long id) {
        repository.deleteById(id);
    }

} 

P.S 此处未列出其他组件或库,请参考前面的弹簧座验证示例

4.春天安全

4.1 创建一个新的@Configuration类并扩展WebSecurityConfigurerAdapter。在下面的例子中,我们将使用 HTTP 基本认证来保护其余的端点。阅读注释,了解自我解释。

SpringSecurityConfig.java

 package com.mkyong.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    // Create 2 users for demo
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                .withUser("user").password("{noop}password").roles("USER")
                .and()
                .withUser("admin").password("{noop}password").roles("USER", "ADMIN");

    }

    // Secure the endpoins with HTTP Basic authentication
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                //HTTP Basic authentication
                .httpBasic()
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/books/**").hasRole("USER")
                .antMatchers(HttpMethod.POST, "/books").hasRole("ADMIN")
                .antMatchers(HttpMethod.PUT, "/books/**").hasRole("ADMIN")
                .antMatchers(HttpMethod.PATCH, "/books/**").hasRole("ADMIN")
                .antMatchers(HttpMethod.DELETE, "/books/**").hasRole("ADMIN")
                .and()
                .csrf().disable()
                .formLogin().disable();
    }

    /*@Bean
    public UserDetailsService userDetailsService() {
        //ok for demo
        User.UserBuilder users = User.withDefaultPasswordEncoder();

        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(users.username("user").password("password").roles("USER").build());
        manager.createUser(users.username("admin").password("password").roles("USER", "ADMIN").build());
        return manager;
    }*/

} 

4.2 完成后,上述 Spring REST API 端点受到 Spring Security 的保护🙂

阅读更多信息:

5.Spring Boot

正常的 Spring Boot 应用程序启动其余的端点,并插入 3 本书到 H2 数据库进行演示。

StartBookApplication

 package com.mkyong;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.math.BigDecimal;

@SpringBootApplication
public class StartBookApplication {

    // start everything
    public static void main(String[] args) {
        SpringApplication.run(StartBookApplication.class, args);
    }

    @Bean
    CommandLineRunner initDatabase(BookRepository repository) {
        return args -> {
            repository.save(new Book("A Guide to the Bodhisattva Way of Life", "Santideva", new BigDecimal("15.41")));
            repository.save(new Book("The Life-Changing Magic of Tidying Up", "Marie Kondo", new BigDecimal("9.69")));
            repository.save(new Book("Refactoring: Improving the Design of Existing Code", "Martin Fowler", new BigDecimal("47.99")));
        };
    }
} 

6.演示

6.1 启动 Spring Boot 应用程序。

 mvn spring-boot:run 

6.2 一个正常的GETPOST会返回一个401,所有端点都被保护,需要认证。

 > curl localhost:8080/books

{	
	"timestamp":"2019-02-25T04:05:14.709+0000",
	"status":401,
	"error":"Unauthorized",
	"message":"Unauthorized",
	"path":"/books"
}

> curl -X POST localhost:8080/books -H "Content-type:application/json" 
	-d {\"name\":\"ABC\",\"author\":\"mkyong\",\"price\":\"8.88\"}

{
	"timestamp":"2019-02-25T04:11:17.150+0000",
	"status":401,
	"error":"Unauthorized",
	"message":"Unauthorized",
	"path":"/books"
} 

6.3 发送一个GET请求和user登录。

 > curl localhost:8080/books -u user:password
[
	{"id":1,"name":"A Guide to the Bodhisattva Way of Life","author":"Santideva","price":15.41},
	{"id":2,"name":"The Life-Changing Magic of Tidying Up","author":"Marie Kondo","price":9.69},
	{"id":3,"name":"Refactoring: Improving the Design of Existing Code","author":"Martin Fowler","price":47.99}
]

> curl localhost:8080/books/1 -u admin:password

{
	"id":1,
	"name":"A Guide to the Bodhisattva Way of Life",
	"author":"Santideva",
	"price":15.41
} 

6.4 尝试用“用户”登录发送一个POST请求,它将返回403, Forbidden错误。这是因为user无权发送POST请求。

 > curl -X POST localhost:8080/books -H "Content-type:application/json" 
	-d {\"name\":\"ABC\",\"author\":\"mkyong\",\"price\":\"8.88\"} -u user:password

{
	"timestamp":"2019-02-25T04:16:58.702+0000",
	"status":403,
	"error":"Forbidden",
	"message":"Forbidden",
	"path":"/books"
} 

再次检查 Spring 安全配置。要发送POST,PUT,PATCH or DELETE请求,我们需要admin

SpringSecurityConfig.java

 @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                //HTTP Basic authentication
                .httpBasic()
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/books/**").hasRole("USER")
                .antMatchers(HttpMethod.POST, "/books").hasRole("ADMIN")
                .antMatchers(HttpMethod.PUT, "/books/**").hasRole("ADMIN")
                .antMatchers(HttpMethod.PATCH, "/books/**").hasRole("ADMIN")
                .antMatchers(HttpMethod.DELETE, "/books/**").hasRole("ADMIN")
                .and()
                .csrf().disable()
                .formLogin().disable();
    }

} 

6.5 尝试使用admin登录发送POST请求

 > curl -X POST localhost:8080/books -H "Content-type:application/json" 
	-d {\"name\":\"ABC\",\"author\":\"mkyong\",\"price\":\"8.88\"} -u admin:password

{
	"id":4,
	"name":"ABC",
	"author":"mkyong",
	"price":8.88
}

> curl localhost:8080/books -u user:password

[
	{"id":1,"name":"A Guide to the Bodhisattva Way of Life","author":"Santideva","price":15.41},
	{"id":2,"name":"The Life-Changing Magic of Tidying Up","author":"Marie Kondo","price":9.69},
	{"id":3,"name":"Refactoring: Improving the Design of Existing Code","author":"Martin Fowler","price":47.99},
	{"id":4,"name":"ABC","author":"mkyong","price":8.88}
] 

7.春季安全集成测试

7.1 用@WithMockUserMockMvc进行测试

BookControllerTest.java

 package com.mkyong;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.math.BigDecimal;
import java.util.Optional;

import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookRepository mockRepository;

    @Before
    public void init() {
        Book book = new Book(1L, "A Guide to the Bodhisattva Way of Life", "Santideva", new BigDecimal("15.41"));
        when(mockRepository.findById(1L)).thenReturn(Optional.of(book));
    }

    //@WithMockUser(username = "USER")
    @WithMockUser("USER")
    @Test
    public void find_login_ok() throws Exception {

        mockMvc.perform(get("/books/1"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id", is(1)))
                .andExpect(jsonPath("$.name", is("A Guide to the Bodhisattva Way of Life")))
                .andExpect(jsonPath("$.author", is("Santideva")))
                .andExpect(jsonPath("$.price", is(15.41)));
    }

    @Test
    public void find_nologin_401() throws Exception {
        mockMvc.perform(get("/books/1"))
                .andDo(print())
                .andExpect(status().isUnauthorized());
    }

} 

7.2 用TestRestTemplate进行测试

BookControllerRestTemplateTest.java

 package com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;
import java.util.Optional;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {

    private static final ObjectMapper om = new ObjectMapper();

    //@WithMockUser is not working with TestRestTemplate
    @Autowired
    private TestRestTemplate restTemplate;

    @MockBean
    private BookRepository mockRepository;

    @Before
    public void init() {
        Book book = new Book(1L, "A Guide to the Bodhisattva Way of Life", "Santideva", new BigDecimal("15.41"));
        when(mockRepository.findById(1L)).thenReturn(Optional.of(book));
    }

    @Test
    public void find_login_ok() throws Exception {

        String expected = "{id:1,name:\"A Guide to the Bodhisattva Way of Life\",author:\"Santideva\",price:15.41}";

        ResponseEntity<String> response = restTemplate
                .withBasicAuth("user", "password")
                .getForEntity("/books/1", String.class);

        printJSON(response);

        assertEquals(MediaType.APPLICATION_JSON_UTF8, response.getHeaders().getContentType());
        assertEquals(HttpStatus.OK, response.getStatusCode());

        JSONAssert.assertEquals(expected, response.getBody(), false);

    }

    @Test
    public void find_nologin_401() throws Exception {

        String expected = "{\"status\":401,\"error\":\"Unauthorized\",\"message\":\"Unauthorized\",\"path\":\"/books/1\"}";

        ResponseEntity<String> response = restTemplate
                .getForEntity("/books/1", String.class);

        printJSON(response);

        assertEquals(MediaType.APPLICATION_JSON_UTF8, response.getHeaders().getContentType());
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());

        JSONAssert.assertEquals(expected, response.getBody(), false);

    }

    private static void printJSON(Object object) {
        String result;
        try {
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            System.out.println(result);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

} 

附注:@WithMockUser不支持TestRestTemplate,我们需要使用 . withBasicAuth 验证用户

下载源代码

$ git clone https://github.com/mkyong/spring-boot.git
$ cd spring-rest-security
$ mvn spring-boot:run

参考

弹簧座验证示例

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring-boot/spring-rest-validation-example/

在本文中,我们将通过添加 bean 验证和自定义验证器来增强前面的 Spring REST Hello World 示例

使用的技术:

  • Spring Boot 2.1.2 .版本
  • 弹簧 5.1.4 释放
  • maven3
  • Java 8

1.控制器

再次查看之前的 REST 控制器:

BookController.java

 package com.mkyong;

import com.mkyong.error.BookNotFoundException;
import com.mkyong.error.BookUnSupportedFieldPatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
public class BookController {

    @Autowired
    private BookRepository repository;

    // Find
    @GetMapping("/books")
    List<Book> findAll() {
        return repository.findAll();
    }

    // Save
    @PostMapping("/books")
	@ResponseStatus(HttpStatus.CREATED)
    Book newBook(@RequestBody Book newBook) {
        return repository.save(newBook);
    }

	 // Find
    @GetMapping("/books/{id}")
    Book findOne(@PathVariable Long id) {
        return repository.findById(id)
                .orElseThrow(() -> new BookNotFoundException(id));
    }

	//...
} 

2.Bean 验证(Hibernate 验证器)

2.1 如果任何 JSR-303 实现(如 Hibernate Validator)在类路径上可用,bean 验证将被自动启用。默认情况下,Spring Boot 会自动获取并下载 Hibernate 验证程序。

2.2 下面的 POST 请求将被通过,我们需要在book对象上实现 bean 验证,以确保像nameauthorprice这样的字段不为空。

 @PostMapping("/books")
    Book newBook(@RequestBody Book newBook) {
        return repository.save(newBook);
    } 
 curl -v -X POST localhost:8080/books -H "Content-type:application/json" -d "{\"name\":\"ABC\"}" 

2.3 用[javax.validation.constraints.*](http://web.archive.org/web/20220904221328/https://docs.jboss.org/hibernate/beanvalidation/spec/2.0/api/javax/validation/constraints/package-summary.html)注释对 bean 进行注释。

Book.java

 package com.mkyong;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;

@Entity
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NotEmpty(message = "Please provide a name")
    private String name;

    @NotEmpty(message = "Please provide a author")
    private String author;

    @NotNull(message = "Please provide a price")
    @DecimalMin("1.00")
    private BigDecimal price;

    //...
} 

2.4 在@RequestBody中增加@Valid。完成,现在启用 bean 验证。

BookController.java

 import javax.validation.Valid;

@RestController
public class BookController {

    @PostMapping("/books")
    Book newBook(@Valid @RequestBody Book newBook) {
        return repository.save(newBook);
    }
	//...
} 

2.5 尝试再次向 REST 端点发送 POST 请求。如果 bean 验证失败,将触发一个MethodArgumentNotValidException。默认情况下,Spring 会发回一个 HTTP 状态 400 错误请求,但是没有错误细节。

 curl -v -X POST localhost:8080/books -H "Content-type:application/json" -d "{\"name\":\"ABC\"}"

Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /books HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
> Content-type:application/json
> Content-Length: 32
>
* upload completely sent off: 32 out of 32 bytes
< HTTP/1.1 400
< Content-Length: 0
< Date: Wed, 20 Feb 2019 13:02:30 GMT
< Connection: close
< 

2.6 上面的错误响应不友好,我们可以捕捉MethodArgumentNotValidException并像这样覆盖响应:

CustomGlobalExceptionHandler.java

 package com.mkyong.error;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // error handle for @Valid
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                  HttpHeaders headers,
                                                                  HttpStatus status, WebRequest request) {

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("timestamp", new Date());
        body.put("status", status.value());

        //Get all errors
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(x -> x.getDefaultMessage())
                .collect(Collectors.toList());

        body.put("errors", errors);

        return new ResponseEntity<>(body, headers, status);

    }

} 

2.7 再试一次。完成了。

 curl -v -X POST localhost:8080/books -H "Content-type:application/json" -d "{\"name\":\"ABC\"}"

{
	"timestamp":"2019-02-20T13:21:27.653+0000",
	"status":400,
	"errors":[
		"Please provide a author",
		"Please provide a price"
	]
} 

3.路径变量验证

3.1 我们也可以直接在路径变量甚至请求参数上应用javax.validation.constraints.*注释。

3.2 在类级别应用@Validated,并在路径变量上添加javax.validation.constraints.*注释,如下所示:

BookController.java

 import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Min;

@RestController
@Validated // class level
public class BookController {

    @GetMapping("/books/{id}")
    Book findOne(@PathVariable @Min(1) Long id) { //jsr 303 annotations
        return repository.findById(id)
                .orElseThrow(() -> new BookNotFoundException(id));
    }

	//...
} 

3.3 默认的错误信息是好的,只是错误代码 500 不合适。

 curl -v localhost:8080/books/0

{
	"timestamp":"2019-02-20T13:27:43.638+0000",
	"status":500,
	"error":"Internal Server Error",
	"message":"findOne.id: must be greater than or equal to 1",
	"path":"/books/0"
} 

3.4 如果@Validated失败,将触发一个ConstraintViolationException,我们可以这样覆盖错误代码:

CustomGlobalExceptionHandler.java

 package com.mkyong.error;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;

@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ConstraintViolationException.class)
    public void constraintViolationException(HttpServletResponse response) throws IOException {
        response.sendError(HttpStatus.BAD_REQUEST.value());
    }

	//..
} 
 curl -v localhost:8080/books/0

{
	"timestamp":"2019-02-20T13:35:59.808+0000",
	"status":400,
	"error":"Bad Request",
	"message":"findOne.id: must be greater than or equal to 1",
	"path":"/books/0"
} 

4.自定义验证程序

4.1 我们将为author字段创建一个自定义验证器,只允许 4 个作者保存到数据库中。

Author.java

 package com.mkyong.error.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = AuthorValidator.class)
@Documented
public @interface Author {

    String message() default "Author is not allowed.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

} 

AuthorValidator.java

 package com.mkyong.error.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;

public class AuthorValidator implements ConstraintValidator<Author, String> {

    List<String> authors = Arrays.asList("Santideva", "Marie Kondo", "Martin Fowler", "mkyong");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {

        return authors.contains(value);

    }
} 

Book.java

 package com.mkyong;

import com.mkyong.error.validator.Author;

import javax.persistence.Entity;
import javax.validation.constraints.NotEmpty;
//...

@Entity
public class Book {

    @Author
    @NotEmpty(message = "Please provide a author")
    private String author;

	//... 

4.2 测试一下。如果自定义验证器失败,它将触发一个MethodArgumentNotValidException

 curl -v -X POST localhost:8080/books 
	-H "Content-type:application/json" 
	-d "{\"name\":\"Spring REST tutorials\", \"author\":\"abc\",\"price\":\"9.99\"}"

{
	"timestamp":"2019-02-20T13:49:59.971+0000",
	"status":400,
	"errors":["Author is not allowed."]
} 

5.弹簧综合测试

5.1 用MockMvc进行测试

BookControllerTest.java

 package com.mkyong;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {

    private static final ObjectMapper om = new ObjectMapper();

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookRepository mockRepository;

    /*
        {
            "timestamp":"2019-03-05T09:34:13.280+0000",
            "status":400,
            "errors":["Author is not allowed.","Please provide a price","Please provide a author"]
        }
     */
    @Test
    public void save_emptyAuthor_emptyPrice_400() throws Exception {

        String bookInJson = "{\"name\":\"ABC\"}";

        mockMvc.perform(post("/books")
                .content(bookInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.timestamp", is(notNullValue())))
                .andExpect(jsonPath("$.status", is(400)))
                .andExpect(jsonPath("$.errors").isArray())
                .andExpect(jsonPath("$.errors", hasSize(3)))
                .andExpect(jsonPath("$.errors", hasItem("Author is not allowed.")))
                .andExpect(jsonPath("$.errors", hasItem("Please provide a author")))
                .andExpect(jsonPath("$.errors", hasItem("Please provide a price")));

        verify(mockRepository, times(0)).save(any(Book.class));

    }

    /*
        {
            "timestamp":"2019-03-05T09:34:13.207+0000",
            "status":400,
            "errors":["Author is not allowed."]
        }
     */
    @Test
    public void save_invalidAuthor_400() throws Exception {

        String bookInJson = "{\"name\":\" Spring REST tutorials\", \"author\":\"abc\",\"price\":\"9.99\"}";

        mockMvc.perform(post("/books")
                .content(bookInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.timestamp", is(notNullValue())))
                .andExpect(jsonPath("$.status", is(400)))
                .andExpect(jsonPath("$.errors").isArray())
                .andExpect(jsonPath("$.errors", hasSize(1)))
                .andExpect(jsonPath("$.errors", hasItem("Author is not allowed.")));

        verify(mockRepository, times(0)).save(any(Book.class));

    }

} 

5.2 用TestRestTemplate进行测试

BookControllerRestTemplateTest.java

 package com.mkyong;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // for restTemplate
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {

    private static final ObjectMapper om = new ObjectMapper();

    @Autowired
    private TestRestTemplate restTemplate;

    @MockBean
    private BookRepository mockRepository;

    /*
        {
            "timestamp":"2019-03-05T09:34:13.280+0000",
            "status":400,
            "errors":["Author is not allowed.","Please provide a price","Please provide a author"]
        }
     */
    @Test
    public void save_emptyAuthor_emptyPrice_400() throws JSONException {

        String bookInJson = "{\"name\":\"ABC\"}";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(bookInJson, headers);

        // send json with POST
        ResponseEntity<String> response = restTemplate.postForEntity("/books", entity, String.class);
        //printJSON(response);

        String expectedJson = "{\"status\":400,\"errors\":[\"Author is not allowed.\",\"Please provide a price\",\"Please provide a author\"]}";
        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
        JSONAssert.assertEquals(expectedJson, response.getBody(), false);

        verify(mockRepository, times(0)).save(any(Book.class));

    }

    /*
        {
            "timestamp":"2019-03-05T09:34:13.207+0000",
            "status":400,
            "errors":["Author is not allowed."]
        }
     */
    @Test
    public void save_invalidAuthor_400() throws JSONException {

        String bookInJson = "{\"name\":\" Spring REST tutorials\", \"author\":\"abc\",\"price\":\"9.99\"}";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(bookInJson, headers);

        //Try exchange
        ResponseEntity<String> response = restTemplate.exchange("/books", HttpMethod.POST, entity, String.class);

        String expectedJson = "{\"status\":400,\"errors\":[\"Author is not allowed.\"]}";
        assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
        JSONAssert.assertEquals(expectedJson, response.getBody(), false);

        verify(mockRepository, times(0)).save(any(Book.class));

    }

    private static void printJSON(Object object) {
        String result;
        try {
            result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            System.out.println(result);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

} 

下载源代码

$ git clone https://github.com/mkyong/spring-boot.git
$ cd spring-rest-validation
$ mvn spring-boot:run

参考

Spring 安全访问控制示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-access-control-example/

在 Spring Security 中,访问控制或授权很容易实现。请参见以下代码片段:

 <http auto-config="true">
	<intercept-url pattern="/admin*" access="ROLE_ADMIN" />
  </http> 

也就是说,只有权限为“ ROLE_ADMIN 的用户才能访问 URI /admin* 。如果非授权用户试图访问它,将显示“ http 403 访问被拒绝页面”。

Spring EL + Access Control
See equivalent version in Spring EL. It is more flexible and contains many useful ready made functions like “hasIpAddress“, make sure check all available el functions in this official Spring el access control documentation.

 <http auto-config="true" use-expressions="true">
	<intercept-url pattern="/admin*" access="hasRole('ROLE_ADMIN')" />
  </http> 

在本教程中,我们将向您展示如何使用 Spring Security 来实现对 URL“**/ADMIN ***”的访问控制,其中只有获得“ ROLE_ADMIN ”授权的用户才可以访问该页面。

1.项目相关性

访问控制包含在核心 Spring 安全 jar 中。请参考这个 Spring Security hello world 示例以获得所需依赖项的列表。

2.Spring MVC

Spring MVC 控制器并返回一个“hello”视图,这应该是不言自明的。

文件:WelcomeController.java

 package com.mkyong.common.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class WelcomeController {

	@RequestMapping(value = "/admin", method = RequestMethod.GET)
	public String welcomeAdmin(ModelMap model) {

		model.addAttribute("message", "Spring Security - ROLE_ADMIN");
		return "hello";

	}

} 

文件:hello.jsp

 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<body>
	<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script><h2>Message : ${message}</h2>	

	<a href="<c:url value="j_spring_security_logout" />" > Logout</a>
</body>
</html> 

3.春天安全

完整的 Spring 安全配置,只允许用户" eclipse "访问" /admin "页面。

 <beans:beans 
	xmlns:beans="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/security
	http://www.springframework.org/schema/security/spring-security-3.0.3.xsd">

	<http auto-config="true">
		<intercept-url pattern="/admin*" access="ROLE_ADMIN" />
		<logout logout-success-url="/admin" />
	</http>

	<authentication-manager>
	  <authentication-provider>
	   <user-service>
		<user name="mkyong" password="password" authorities="ROLE_USER" />
		<user name="eclipse" password="password" authorities="ROLE_ADMIN" />
	   </user-service>
	  </authentication-provider>
	</authentication-manager>

</beans:beans> 

4.演示

网址:http://localhost:8080/spring MVC/admin

1.将显示默认登录表单。

demo page - access control

2.如果用户“ mkyong ”登录,会显示“ http 403 被拒绝访问页面,因为“ mkyong ”是“ ROLE_USER ”。

demo page - access denied

3.如果用户“ eclipse ”登录,会显示“【hello.jsp】T2,因为“ eclipse ”是“ ROLE_ADMIN ”。

demo page - successCustomize 403 page
Default 403 page is ugly, read this example – How to customize http 403 access denied page in spring security.

下载源代码

Download it – Spring-Security-Access-Control-Example.zip (10 KB)

参考

  1. Spring 安全授权文档
  2. Spring 安全+ el 访问控制文档

access control spring security (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190215001531/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Spring Security:检查用户是否来自“记住我”cookie

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-check-if-user-is-from-remember-me-cookie/

这个 Spring 安全示例向您展示了如何检查一个用户是否从“remember me”cookie 登录。

 private boolean isRememberMeAuthenticated() {

	Authentication authentication = 
		SecurityContextHolder.getContext().getAuthentication();
	if (authentication == null) {
		return false;
	}

    return RememberMeAuthenticationToken.class.isAssignableFrom(authentication.getClass());
  }

  @RequestMapping(value = "/admin/update**", method = RequestMethod.GET)
  public ModelAndView updatePage() {

	ModelAndView model = new ModelAndView();

	if (isRememberMeAuthenticated()) {	
		model.setViewName("/login");	
	} else {
		model.setViewName("update");
	}

	return model;

  } 

在 Spring 安全标记中,您可以这样编码:

 <%@taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<%@page session="true"%>
<html>
<body>

	<sec:authorize access="isRememberMe()">
		<h2># This user is login by "Remember Me Cookies".</h2>
	</sec:authorize>

	<sec:authorize access="isFullyAuthenticated()">
		<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

<script>
     (adsbygoogle = window.adsbygoogle || []).push({});
</script><h2># This user is login by username / password.</h2>
	</sec:authorize>

</body>
</html> 

Note
isRememberMe() – Returns true if the current principal is a remember-me user
isFullyAuthenticated() – Returns true if the user is not an anonymous or a remember-me user ## 参考

  1. Spring Security,Spring EL for expression 概述
  2. AuthenticationTrustResolverImpl JavaDoc
  3. 春安记得我的例子

spring security (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190304003401/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Spring 安全自定义登录表单注释示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-custom-login-form-annotation-example/

在本教程中,我们将把之前的 Spring Security 定制登录表单(XML) 项目转换成一个纯粹的基于注释的项目。

使用的技术:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布
  3. Eclipse 4.2
  4. JDK 1.6
  5. maven3
  6. Tomcat 7 (Servlet 3.x)

Note
In this example, last Spring Security hello world annotation example will be reused, enhance it to support a custom login form.

1.项目演示

//web.archive.org/web/20190303104611if_/http://www.youtube.com/embed/Th5keN9Cqx4

2.目录结构

查看本教程的最终目录结构。

spring-security-custom-login-annotation-directory ## 3.Spring 安全配置

通过注释进行 Spring 安全配置。

SecurityConfig.java

 package com.mkyong.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
                   .withUser("mkyong").password("123456").roles("USER");
	}

	//.csrf() is optional, enabled by default, if using WebSecurityConfigurerAdapter constructor
	@Override
	protected void configure(HttpSecurity http) throws Exception {

	    http.authorizeRequests()
		.antMatchers("/admin/**").access("hasRole('ROLE_USER')")
		.and()
		    .formLogin().loginPage("/login").failureUrl("/login?error")
		    .usernameParameter("username").passwordParameter("password")		
		.and()
		    .logout().logoutSuccessUrl("/login?logout")
		.and()
		    .csrf(); 		
	}
} 

相当于 Spring Security XML 文件:

 <http auto-config="true">
	  <intercept-url pattern="/admin**" access="ROLE_USER" />
	  <form-login 
		login-page="/login" 
	        default-target-url="/welcome" 
		authentication-failure-url="/login?error" 
		username-parameter="username"
		password-parameter="password" />
	  <logout logout-success-url="/login?logout" />
	  <!-- enable csrf protection -->
	  <csrf/>
	</http>

	<authentication-manager>
	  <authentication-provider>
	    <user-service>
		<user name="mkyong" password="123456" authorities="ROLE_USER" />
	    </user-service>
	  </authentication-provider>
	</authentication-manager> 

4.自定义登录表单

4.1 显示自定义登录表单的页面。如果启用了 CSRF 保护,记得在登录和注销表单中添加${_csrf.token}

login.jsp

 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>Login Page</title>
<style>
.error {
	padding: 15px;
	margin-bottom: 20px;
	border: 1px solid transparent;
	border-radius: 4px;
	color: #a94442;
	background-color: #f2dede;
	border-color: #ebccd1;
}

.msg {
	padding: 15px;
	margin-bottom: 20px;
	border: 1px solid transparent;
	border-radius: 4px;
	color: #31708f;
	background-color: #d9edf7;
	border-color: #bce8f1;
}

#login-box {
	width: 300px;
	padding: 20px;
	margin: 100px auto;
	background: #fff;
	-webkit-border-radius: 2px;
	-moz-border-radius: 2px;
	border: 1px solid #000;
}
</style>
</head>
<body onload='document.loginForm.username.focus();'>

	<h1>Spring Security Custom Login Form (Annotation)</h1>

	<div id="login-box">

		<h2>Login with Username and Password</h2>

		<c:if test="${not empty error}">
			<div class="error">${error}</div>
		</c:if>
		<c:if test="${not empty msg}">
			<div class="msg">${msg}</div>
		</c:if>

		<form name='loginForm'
		    action="<c:url value='j_spring_security_check' />" method='POST'>

		    <table>
			<tr>
				<td>User:</td>
				<td><input type='text' name='user' value=''></td>
			</tr>
			<tr>
				<td>Password:</td>
				<td><input type='password' name='pass' /></td>
			</tr>
			<tr>
			        <td colspan='2'>
                                <input name="submit" type="submit" value="submit" />
                                </td>
			</tr>
		   </table>

		   <input type="hidden" 
                     name="${_csrf.parameterName}" value="${_csrf.token}" />
		</form>
	</div>

</body>
</html> 

4.2 显示欢迎消息的页面,默认页面。

hello.jsp

 <%@page session="false"%>
<html>
<body>
	<h1>Title : ${title}</h1>	
	<h1>Message : ${message}</h1>	
</body>
</html> 

4.3 本页面受密码保护,只有通过认证的用户才可以访问。

admin.jsp + logout

 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<body>
	<h1>Title : ${title}</h1>
	<h1>Message : ${message}</h1>

	<c:url value="/j_spring_security_logout" var="logoutUrl" />

		<!-- csrt support -->
	<form action="${logoutUrl}" method="post" id="logoutForm">
		<input type="hidden" 
			name="${_csrf.parameterName}"
			value="${_csrf.token}" />
	</form>

	<script>
		function formSubmit() {
			document.getElementById("logoutForm").submit();
		}
	</script>

	<c:if test="${pageContext.request.userPrincipal.name != null}">
		<h2>
			Welcome : ${pageContext.request.userPrincipal.name} | <a
				href="javascript:formSubmit()"> Logout</a>
		</h2>
	</c:if>

</body>
</html> 

5.Spring MVC 控制器

简单的控制器。

HelloController.java

 package com.mkyong.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController {

	@RequestMapping(value = { "/", "/welcome**" }, method = RequestMethod.GET)
	public ModelAndView welcomePage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Custom Login Form");
		model.addObject("message", "This is welcome page!");
		model.setViewName("hello");
		return model;

	}

	@RequestMapping(value = "/admin**", method = RequestMethod.GET)
	public ModelAndView adminPage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Custom Login Form");
		model.addObject("message", "This is protected page!");
		model.setViewName("admin");

		return model;

	}

	//Spring Security see this :
	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public ModelAndView login(
		@RequestParam(value = "error", required = false) String error,
		@RequestParam(value = "logout", required = false) String logout) {

		ModelAndView model = new ModelAndView();
		if (error != null) {
			model.addObject("error", "Invalid username and password!");
		}

		if (logout != null) {
			model.addObject("msg", "You've been logged out successfully.");
		}
		model.setViewName("login");

		return model;

	}

} 

6.初始值设定项类

下面是初始化器类,使它成为一个纯粹的基于注释的项目。

6.1 启用 Spring 安全配置的初始化器类。

SpringSecurityInitializer.java

 package com.mkyong.config.core;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {

} 

6.2 启用 Spring MVC 的初始化器类。

SpringMvcInitializer.java

 package com.mkyong.config.core;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.mkyong.config.AppConfig;

public class SpringMvcInitializer 
	extends AbstractAnnotationConfigDispatcherServletInitializer {

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

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return null;
	}

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

} 

AppConfig.java

 package com.mkyong.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@EnableWebMvc
@Configuration
@ComponentScan({ "com.mkyong.web.*" })
@Import({ SecurityConfig.class })
public class AppConfig {

	@Bean
	public InternalResourceViewResolver viewResolver() {
		InternalResourceViewResolver viewResolver
                           = new InternalResourceViewResolver();
		viewResolver.setViewClass(JstlView.class);
		viewResolver.setPrefix("/WEB-INF/pages/");
		viewResolver.setSuffix(".jsp");
		return viewResolver;
	}

} 

7.演示

7.1.欢迎页面-http://localhost:8080/spring-security-loginform-annotation/

spring-security-custom-login-annotation-welcome

7.2 尝试访问/admin页面,显示您的自定义登录表单。

spring-security-custom-login-annotation-login

7.3.如果用户名和密码不正确,显示/login?error

spring-security-custom-login-annotation-error

7.4.如果用户名和密码正确,Spring 会将请求重定向到最初请求的 URL 并显示页面。

spring-security-custom-login-annotation-admin

7.5.尝试注销,会重定向到/login?logout页面。

spring-security-custom-login-annotation-logout

下载源代码

Download it – spring-security-custom-login-form-annotation.zip (19 KB)

参考

  1. 春安你好世界注释示例
  2. 创建自定义登录表单
  3. 春安 3.2.0.RC1 亮点:CSRF 保护
  4. 维基百科:跨站请求伪造

login form spring security

Spring Security:编码的密码看起来不像 BCrypt

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-encoded-password-does-not-look-like-bcrypt/

在 Spring Security 中,使用 bcrypt 密码散列的数据库认证。

  import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  import org.springframework.security.crypto.password.PasswordEncoder;
  //...
	String password = "123456";
	PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
	String hashedPassword = passwordEncoder.encode(password);

spring-security.xml

 <authentication-manager>
	<authentication-provider>
	    <password-encoder hash="bcrypt" />
	    //...
	</authentication-provider>
  </authentication-manager> 
 CREATE  TABLE users (
  username VARCHAR(45) NOT NULL ,
  password VARCHAR(45) NOT NULL ,
  enabled TINYINT NOT NULL DEFAULT 1 ,
  PRIMARY KEY (username)); 

查看调试输出,总是说“编码的密码看起来不像 BCrypt ”,即使提供了正确的密码。

 //...
12:56:31.868 DEBUG o.s.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
12:56:31.868 WARN  o.s.s.c.bcrypt.BCryptPasswordEncoder - Encoded password does not look like BCrypt
12:56:31.868 DEBUG o.s.s.a.d.DaoAuthenticationProvider - Authentication failed: password does not match stored value 

解决办法

例如,在 bcrypt 散列算法中,每次都会生成长度为 60 的不同散列值

 $2a$10$LOqePml/koRGsk2YAIOFI.1YNKZg7EsQ5BAIuYP1nWOyYRl21dlne 

一个常见的错误,“密码”列(users 表)的长度小于 60,比如password VARCHAR(45),有些数据库会自动截断数据。因此,您总是得到警告“编码的密码看起来不像 BCrypt”。

要解决,请确保“密码”栏的长度至少为 60。

bcryt hashing spring security (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190309053253/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Spring Security 自定义登录表单示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-form-login-example/

默认情况下,如果没有指定登录表单,Spring Security 将自动创建一个默认的登录表单。请参考这个——春安 hello world 示例

在本教程中,我们将向您展示如何为 Spring Security 创建一个定制的登录表单(XML 示例)。

使用的技术:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布
  3. Eclipse 4.2
  4. JDK 1.6
  5. maven3

Note
In this example, previous Spring Security hello world example will be reused, enhance it to support a custom login form.

1.项目演示

//web.archive.org/web/20220309221809if_/https://www.youtube.com/embed/Y0QaOxnEn1Y

2.目录结构

查看本教程的最终目录结构。

spring-security-custom-login-xml-directory

3.Spring 安全配置

在 Spring XML 文件中定义了您的自定义登录表单。参见下面的解释:

  1. log in-page = "/log in "–显示自定义登录表单的页面
  2. 认证-失败-url="/login?错误”-如果验证失败,请转到第/login?error
  3. 注销-成功-URL = "/登录?注销”——如果注销成功,转到视图/logout
  4. username-parameter = " username "–包含“用户名”的请求的名称。在 HTML 中,这是输入文本的名称。
  5. –启用跨站请求伪造(CSRF)保护,参见此链接。在 XML 中,默认情况下,CSRF 保护是禁用的。

通常,我们不涉及登录或注销等认证处理,让 Spring 来处理,我们只处理成功或失败的页面来显示。

spring-security.xml

 <beans:beans 
	xmlns:beans="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/security
	http://www.springframework.org/schema/security/spring-security-3.2.xsd">

	<http auto-config="true">
		<intercept-url pattern="/admin**" access="ROLE_USER" />
		<form-login 
		    login-page="/login" 
		    default-target-url="/welcome" 
			authentication-failure-url="/login?error" 
			username-parameter="username"
			password-parameter="password" />
		<logout logout-success-url="/login?logout" />
		<!-- enable csrf protection -->
		<csrf/>
	</http>

	<authentication-manager>
		<authentication-provider>
		  <user-service>
			<user name="mkyong" password="123456" authorities="ROLE_USER" />
		  </user-service>
		</authentication-provider>
	</authentication-manager>

</beans:beans> 

在上面的祝贺中,/admin及其子文件夹都有密码保护。

Cross Site Request Forgery (CSRF) Protection
If CSRF is enabled, you have to include a _csrf.token in the page you want to login or logout. Refer to below login.jsp and admin.jsp (logout form). Otherwise, both login and logout function will be failed.Password in clear-text?
A pretty bad idea, you should always hash the password with SHA algorithm, this tutorial show you how – Spring Security password hashing example.

4.自定义登录表单

一个定制的登录表单来匹配上面(步骤 3)的 Spring Security 祝贺。应该是不言自明的。

login.jsp

 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>Login Page</title>
<style>
.error {
	padding: 15px;
	margin-bottom: 20px;
	border: 1px solid transparent;
	border-radius: 4px;
	color: #a94442;
	background-color: #f2dede;
	border-color: #ebccd1;
}

.msg {
	padding: 15px;
	margin-bottom: 20px;
	border: 1px solid transparent;
	border-radius: 4px;
	color: #31708f;
	background-color: #d9edf7;
	border-color: #bce8f1;
}

#login-box {
	width: 300px;
	padding: 20px;
	margin: 100px auto;
	background: #fff;
	-webkit-border-radius: 2px;
	-moz-border-radius: 2px;
	border: 1px solid #000;
}
</style>
</head>
<body onload='document.loginForm.username.focus();'>

	<h1>Spring Security Custom Login Form (XML)</h1>

	<div id="login-box">

		<h2>Login with Username and Password</h2>

		<c:if test="${not empty error}">
			<div class="error">${error}</div>
		</c:if>
		<c:if test="${not empty msg}">
			<div class="msg">${msg}</div>
		</c:if>

		<form name='loginForm'
		  action="<c:url value='j_spring_security_check' />" method='POST'>

		  <table>
			<tr>
				<td>User:</td>
				<td><input type='text' name='username' value=''></td>
			</tr>
			<tr>
				<td>Password:</td>
				<td><input type='password' name='password' /></td>
			</tr>
			<tr>
				<td colspan='2'><input name="submit" type="submit"
					value="submit" /></td>
			</tr>
		  </table>

		  <input type="hidden" name="${_csrf.parameterName}"
			value="${_csrf.token}" />

		</form>
	</div>

</body>
</html> 

而另外两个 JSP 页面,btw admin.jsp是受 Spring Security 密码保护的。

hello.jsp

 <%@page session="false"%>
<html>
<body>
	<h1>Title : ${title}</h1>	
	<h1>Message : ${message}</h1>	
</body>
</html> 

admin.jsp + logout

 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<body>
	<h1>Title : ${title}</h1>
	<h1>Message : ${message}</h1>

	<c:url value="/j_spring_security_logout" var="logoutUrl" />

	<!-- csrt for log out-->
	<form action="${logoutUrl}" method="post" id="logoutForm">
	  <input type="hidden" 
		name="${_csrf.parameterName}"
		value="${_csrf.token}" />
	</form>

	<script>
		function formSubmit() {
			document.getElementById("logoutForm").submit();
		}
	</script>

	<c:if test="${pageContext.request.userPrincipal.name != null}">
		<h2>
			Welcome : ${pageContext.request.userPrincipal.name} | <a
				href="javascript:formSubmit()"> Logout</a>
		</h2>
	</c:if>

</body>
</html> 

5.Spring MVC 控制器

简单的控制器。

HelloController.java

 package com.mkyong.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController {

	@RequestMapping(value = { "/", "/welcome**" }, method = RequestMethod.GET)
	public ModelAndView welcomePage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Custom Login Form");
		model.addObject("message", "This is welcome page!");
		model.setViewName("hello");
		return model;

	}

	@RequestMapping(value = "/admin**", method = RequestMethod.GET)
	public ModelAndView adminPage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Custom Login Form");
		model.addObject("message", "This is protected page!");
		model.setViewName("admin");

		return model;

	}

	//Spring Security see this :
	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public ModelAndView login(
		@RequestParam(value = "error", required = false) String error,
		@RequestParam(value = "logout", required = false) String logout) {

		ModelAndView model = new ModelAndView();
		if (error != null) {
			model.addObject("error", "Invalid username and password!");
		}

		if (logout != null) {
			model.addObject("msg", "You've been logged out successfully.");
		}
		model.setViewName("login");

		return model;

	}

} 

6.演示

6.1.欢迎页面–http://localhost:8080/spring-security-loginform-XML/

spring-security-custom-login-xml-welcome

6.2 尝试访问/admin页面,Spring Security 会拦截请求并重定向到/login,显示您的自定义登录表单。

spring-security-custom-login-xml-login

6.3.如果用户名和密码不正确,就会显示错误消息,Spring 会重定向到这个 URL /login?error

spring-security-custom-login-xml-error

6.4.如果用户名和密码正确,Spring 将重定向到最初请求的 URL 并显示页面。

spring-security-custom-login-xml-admin

6.5.尝试注销,会重定向到/login?logout页面。

spring-security-custom-login-xml-logout

下载源代码

Download it – spring-security-custom-login-form-xml.zip (15 KB)

参考

  1. Spring Security Hello World XML 示例
  2. 创建自定义登录表单
  3. 春安 3.2.0.RC1 亮点:CSRF 保护

使用数据库的 Spring 安全表单登录

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-form-login-using-database/

在本教程中,我们将向您展示如何在 Spring Security 中执行数据库认证(使用 XML 和注释)。

使用的技术:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布
  3. 春季 JDBC 3.2.3 .发布
  4. Eclipse 4.2
  5. JDK 1.6
  6. maven3
  7. Tomcat 6 或 7 (Servlet 3.x)
  8. MySQL 服务器 5.6

以前的登录表单内存认证将被重用,增强以支持以下特性:

  1. 数据库认证,使用 Spring-JDBC 和 MySQL。
  2. Spring Security,JSP TagLib,sec:authorize access="hasRole('ROLE_USER')
  3. 自定义 403 拒绝访问页面。

1.项目演示

//web.archive.org/web/20220323160657if_/https://www.youtube.com/embed/2ms57c2EdUg

2.项目目录

查看最终项目结构(基于 XML):

spring-security-database-xml-directory

查看最终项目结构(基于注释):

spring-security-database-annotation-directory

3.项目相关性

获取对 Spring、Spring Security、JDBC、Taglib 和 MySQL 的依赖

pom.xml

 <properties>
		<jdk.version>1.6</jdk.version>
		<spring.version>3.2.8.RELEASE</spring.version>
		<spring.security.version>3.2.3.RELEASE</spring.security.version>
		<jstl.version>1.2</jstl.version>
		<mysql.connector.version>5.1.30</mysql.connector.version>
	</properties>

	<dependencies>

		<!-- Spring 3 dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring Security -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<!-- Spring Security JSP Taglib -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-taglibs</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<!-- jstl for jsp page -->
		<dependency>
			<groupId>jstl</groupId>
			<artifactId>jstl</artifactId>
			<version>${jstl.version}</version>
		</dependency>

                <!-- connect to mysql -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql.connector.version}</version>
		</dependency>

	</dependencies>

</project> 

4.数据库ˌ资料库

要执行数据库身份验证,您必须创建表来存储用户和角色的详细信息。请参考这个 Spring 安全用户模式参考。下面是创建usersuser_roles表的 MySQL 脚本。

4.1 创建一个“用户”表。

users.sql

 CREATE  TABLE users (
  username VARCHAR(45) NOT NULL ,
  password VARCHAR(45) NOT NULL ,
  enabled TINYINT NOT NULL DEFAULT 1 ,
  PRIMARY KEY (username)); 

4.2 创建一个“用户角色”表。

user_roles.sql

 CREATE TABLE user_roles (
  user_role_id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(45) NOT NULL,
  role varchar(45) NOT NULL,
  PRIMARY KEY (user_role_id),
  UNIQUE KEY uni_username_role (role,username),
  KEY fk_username_idx (username),
  CONSTRAINT fk_username FOREIGN KEY (username) REFERENCES users (username)); 

4.3 插入一些测试记录。

 INSERT INTO users(username,password,enabled)
VALUES ('mkyong','123456', true);
INSERT INTO users(username,password,enabled)
VALUES ('alex','123456', true);

INSERT INTO user_roles (username, role)
VALUES ('mkyong', 'ROLE_USER');
INSERT INTO user_roles (username, role)
VALUES ('mkyong', 'ROLE_ADMIN');
INSERT INTO user_roles (username, role)
VALUES ('alex', 'ROLE_USER'); 

Note

  1. 用户名“mkyong”,角色 _ 用户和角色 _ 管理员。
  2. 用户名“alexa”,带角色 _ 用户。

5.Spring 安全配置

XML 和注释中的 Spring 安全性。

5.1 创建一个数据源来连接 MySQL。

spring-database.xml

 <beans 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">

		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/test" />
		<property name="username" value="root" />
		<property name="password" value="password" />
	</bean>

</beans> 

相当于弹簧注释:

SecurityConfig.java

 package com.mkyong.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@EnableWebMvc
@Configuration
@ComponentScan({ "com.mkyong.web.*" })
@Import({ SecurityConfig.class })
public class AppConfig {

	@Bean(name = "dataSource")
	public DriverManagerDataSource dataSource() {
	    DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
	    driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
	    driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/test");
	    driverManagerDataSource.setUsername("root");
	    driverManagerDataSource.setPassword("password");
	    return driverManagerDataSource;
	}

	@Bean
	public InternalResourceViewResolver viewResolver() {
	    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
	    viewResolver.setViewClass(JstlView.class);
	    viewResolver.setPrefix("/WEB-INF/pages/");
	    viewResolver.setSuffix(".jsp");
	    return viewResolver;
	}

} 

5.2 使用jdbc-user-service定义一个执行数据库认证的查询。

spring-security.xml

 <beans:beans 
	xmlns:beans="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/security
	http://www.springframework.org/schema/security/spring-security-3.2.xsd">

    <!-- enable use-expressions -->
	<http auto-config="true" use-expressions="true">

		<intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" />

		<!-- access denied page -->
		<access-denied-handler error-page="/403" />

		<form-login 
		    login-page="/login" 
		    default-target-url="/welcome" 
			authentication-failure-url="/login?error" 
			username-parameter="username"
			password-parameter="password" />
		<logout logout-success-url="/login?logout"  />
		<!-- enable csrf protection -->
		<csrf/>
	</http>

	<!-- Select users and user_roles from database -->
	<authentication-manager>
	  <authentication-provider>
		<jdbc-user-service data-source-ref="dataSource"
		  users-by-username-query=
		    "select username,password, enabled from users where username=?"
		  authorities-by-username-query=
		    "select username, role from user_roles where username =?  " />
	  </authentication-provider>
	</authentication-manager>

</beans:beans> 

相当于 Spring 安全注释:

SecurityConfig.java

 package com.mkyong.config;

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	DataSource dataSource;

	@Autowired
	public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {

	  auth.jdbcAuthentication().dataSource(dataSource)
		.usersByUsernameQuery(
			"select username,password, enabled from users where username=?")
		.authoritiesByUsernameQuery(
			"select username, role from user_roles where username=?");
	}	

	@Override
	protected void configure(HttpSecurity http) throws Exception {

	  http.authorizeRequests()
		.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
		.and()
		  .formLogin().loginPage("/login").failureUrl("/login?error")
		  .usernameParameter("username").passwordParameter("password")
		.and()
		  .logout().logoutSuccessUrl("/login?logout")
		.and()
		  .exceptionHandling().accessDeniedPage("/403")
		.and()
		  .csrf();
	}
} 

6.JSP 页面

定制登录页面的 JSP 页面。

6.1 默认页面,展示使用 Spring Security JSP taglib sec:authorize向拥有“ ROLE_USER 权限的用户显示内容。

hello.jsp

 <%@taglib prefix="sec"
	uri="http://www.springframework.org/security/tags"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<body>
	<h1>Title : ${title}</h1>
	<h1>Message : ${message}</h1>

	<sec:authorize access="hasRole('ROLE_USER')">
		<!-- For login user -->
		<c:url value="/j_spring_security_logout" var="logoutUrl" />
		<form action="${logoutUrl}" method="post" id="logoutForm">
			<input type="hidden" name="${_csrf.parameterName}"
				value="${_csrf.token}" />
		</form>
		<script>
			function formSubmit() {
				document.getElementById("logoutForm").submit();
			}
		</script>

		<c:if test="${pageContext.request.userPrincipal.name != null}">
			<h2>
				User : ${pageContext.request.userPrincipal.name} | <a
					href="javascript:formSubmit()"> Logout</a>
			</h2>
		</c:if>

	</sec:authorize>
</body>
</html> 

6.2 显示自定义登录表单的页面。

login.jsp

 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<head>
<title>Login Page</title>
<style>
.error {
	padding: 15px;
	margin-bottom: 20px;
	border: 1px solid transparent;
	border-radius: 4px;
	color: #a94442;
	background-color: #f2dede;
	border-color: #ebccd1;
}

.msg {
	padding: 15px;
	margin-bottom: 20px;
	border: 1px solid transparent;
	border-radius: 4px;
	color: #31708f;
	background-color: #d9edf7;
	border-color: #bce8f1;
}

#login-box {
	width: 300px;
	padding: 20px;
	margin: 100px auto;
	background: #fff;
	-webkit-border-radius: 2px;
	-moz-border-radius: 2px;
	border: 1px solid #000;
}
</style>
</head>
<body onload='document.loginForm.username.focus();'>

	<h1>Spring Security Login Form (Database Authentication)</h1>

	<div id="login-box">

		<h2>Login with Username and Password</h2>

		<c:if test="${not empty error}">
			<div class="error">${error}</div>
		</c:if>
		<c:if test="${not empty msg}">
			<div class="msg">${msg}</div>
		</c:if>

		<form name='loginForm'
		  action="<c:url value='/j_spring_security_check' />" method='POST'>

		<table>
			<tr>
				<td>User:</td>
				<td><input type='text' name='username'></td>
			</tr>
			<tr>
				<td>Password:</td>
				<td><input type='password' name='password' /></td>
			</tr>
			<tr>
				<td colspan='2'><input name="submit" type="submit"
				  value="submit" /></td>
			</tr>
		  </table>

		  <input type="hidden" name="${_csrf.parameterName}"
			value="${_csrf.token}" />

		</form>
	</div>

</body>
</html> 

6.3 该页面受密码保护,只有经过认证的用户“ ROLE_ADMIN ”可以访问。

admin.jsp

 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<body>
	<h1>Title : ${title}</h1>
	<h1>Message : ${message}</h1>

	<c:url value="/j_spring_security_logout" var="logoutUrl" />
	<form action="${logoutUrl}" method="post" id="logoutForm">
		<input type="hidden" name="${_csrf.parameterName}"
			value="${_csrf.token}" />
	</form>
	<script>
		function formSubmit() {
			document.getElementById("logoutForm").submit();
		}
	</script>

	<c:if test="${pageContext.request.userPrincipal.name != null}">
		<h2>
			Welcome : ${pageContext.request.userPrincipal.name} | <a
				href="javascript:formSubmit()"> Logout</a>
		</h2>
	</c:if>

</body>
</html> 

6.4 自定义 403 拒绝访问页面。

403.jsp

 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<body>
	<h1>HTTP Status 403 - Access is denied</h1>

	<c:choose>
		<c:when test="${empty username}">
		  <h2>You do not have permission to access this page!</h2>
		</c:when>
		<c:otherwise>
		  <h2>Username : ${username} <br/>
                    You do not have permission to access this page!</h2>
		</c:otherwise>
	</c:choose>

</body>
</html> 

7.Spring MVC 控制器

简单的控制器。

MainController.java

 package com.mkyong.web.controller;

import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MainController {

	@RequestMapping(value = { "/", "/welcome**" }, method = RequestMethod.GET)
	public ModelAndView defaultPage() {

	  ModelAndView model = new ModelAndView();
	  model.addObject("title", "Spring Security Login Form - Database Authentication");
	  model.addObject("message", "This is default page!");
	  model.setViewName("hello");
	  return model;

	}

	@RequestMapping(value = "/admin**", method = RequestMethod.GET)
	public ModelAndView adminPage() {

	  ModelAndView model = new ModelAndView();
	  model.addObject("title", "Spring Security Login Form - Database Authentication");
	  model.addObject("message", "This page is for ROLE_ADMIN only!");
	  model.setViewName("admin");
	  return model;

	}

	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public ModelAndView login(@RequestParam(value = "error", required = false) String error,
		@RequestParam(value = "logout", required = false) String logout) {

	  ModelAndView model = new ModelAndView();
	  if (error != null) {
		model.addObject("error", "Invalid username and password!");
	  }

	  if (logout != null) {
		model.addObject("msg", "You've been logged out successfully.");
	  }
	  model.setViewName("login");

	  return model;

	}

	//for 403 access denied page
	@RequestMapping(value = "/403", method = RequestMethod.GET)
	public ModelAndView accesssDenied() {

	  ModelAndView model = new ModelAndView();

	  //check if user is login
	  Authentication auth = SecurityContextHolder.getContext().getAuthentication();
	  if (!(auth instanceof AnonymousAuthenticationToken)) {
		UserDetails userDetail = (UserDetails) auth.getPrincipal();	
		model.addObject("username", userDetail.getUsername());
	  }

	  model.setViewName("403");
	  return model;

	}

} 

8.演示

8.1.默认页面
XML—http://localhost:8080/spring-security-loginform-database/
Annotation—http://localhost:8080/spring-security-loginform-database-Annotation/

spring-security-database-default

8.2 尝试访问/admin页面,只允许“mkyong”ROLE _ ADMIN访问。

spring-security-database-admin

8.3.如果“alex”试图访问/admin,则显示 403 访问被拒绝页面。

spring-security-database-403

8.3 "alex "在默认页面中显示sec:authorize的用法

spring-security-database-sec-tag

8.4.如果“mkyong”试图访问/admin,将显示管理页面。

spring-security-database-admin-login

下载源代码

Download XML version – spring-security-login-form-database-xml.zip (16 KB)Download Annotation version – spring-security-login-form-database-annotation.zip (22 KB)

参考

  1. Spring 安全参考–JDBC-ser-service
  2. Spring 安全参考–juser-schema
  3. Spring 安全参考–标签库
  4. 春安你好世界注释示例
  5. 创建自定义登录表单
  6. Spring Security–如何定制 403 拒绝访问页面

Spring Security Hello World 注释示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-hello-world-annotation-example/

security

在 preview post 中,我们使用 XML 文件来配置 Spring MVC 环境中的 Spring 安全性。在本教程中,我们将向您展示如何将之前基于 XML 的 Spring Security 项目转换为一个纯 Spring 注释项目。

使用的技术:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布
  3. Eclipse 4.2
  4. JDK 1.6
  5. maven3
  6. Tomcat 7 (Servlet 3.x)

几个音符

  1. 本教程使用WebApplicationInitializer来自动加载 Spring Context Loader,它仅在 Servlet 3.x 容器中受支持,例如 Tomcat 7 和 Jetty 8。
  2. 因为我们使用的是WebApplicationInitializer,所以不需要web.xml文件。
  3. 旧的 Servlet 2.x 容器支持 Spring 安全注释,例如 Tomcat 6。如果使用经典的 XML 文件加载 Spring 上下文,本教程仍然能够部署在 Servlet 2.x 容器上,例如 Tomcat 6

1.项目演示

看看效果如何。

//web.archive.org/web/20190304011458if_/http://www.youtube.com/embed/NKamWA6hDaU

2.目录结构

查看本教程的最终目录结构。

spring-security-helloworld-annotation-directory ## 3.Spring 安全依赖项

要使用 Spring security,需要spring-security-webspring-security-config

pom.xml

 <properties>
		<jdk.version>1.6</jdk.version>
		<spring.version>3.2.8.RELEASE</spring.version>
		<spring.security.version>3.2.3.RELEASE</spring.security.version>
		<jstl.version>1.2</jstl.version>
	</properties>

	<dependencies>

		<!-- Spring 3 dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring Security -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<!-- jstl for jsp page -->
		<dependency>
			<groupId>jstl</groupId>
			<artifactId>jstl</artifactId>
			<version>${jstl.version}</version>
		</dependency>

	</dependencies> 

4.Spring MVC Web 应用程序

一个简单的控制器:

  1. 如果 URL = /welcome/,返回 hello 页面。
  2. 如果 URL = /admin,返回管理页面。
  3. 如果 URL = /dba,返回管理页面。

稍后,我们将保护/admin/dbaURL。

HelloController.java

 package com.mkyong.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController {

	@RequestMapping(value = { "/", "/welcome**" }, method = RequestMethod.GET)
	public ModelAndView welcomePage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Hello World");
		model.addObject("message", "This is welcome page!");
		model.setViewName("hello");
		return model;

	}

	@RequestMapping(value = "/admin**", method = RequestMethod.GET)
	public ModelAndView adminPage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Hello World");
		model.addObject("message", "This is protected page - Admin Page!");
		model.setViewName("admin");

		return model;

	}

	@RequestMapping(value = "/dba**", method = RequestMethod.GET)
	public ModelAndView dbaPage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Hello World");
		model.addObject("message", "This is protected page - Database Page!");
		model.setViewName("admin");

		return model;

	}

} 

两个 JSP 页面。

hello.jsp

 <%@page session="false"%>
<html>
<body>
	<h1>Title : ${title}</h1>	
	<h1>Message : ${message}</h1>	
</body>
</html> 

admin.jsp

 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<body>
	<h1>Title : ${title}</h1>
	<h1>Message : ${message}</h1>

	<c:if test="${pageContext.request.userPrincipal.name != null}">
		<h2>Welcome : ${pageContext.request.userPrincipal.name} 
                 | <a href="<c:url value="/logout" />" > Logout</a></h2>  
	</c:if>
</body>
</html> 

5.Spring 安全配置

5.1 创建一个 Spring 安全配置文件,并用@EnableWebSecurity标注

SecurityConfig.java

 package com.mkyong.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
	  auth.inMemoryAuthentication().withUser("mkyong").password("123456").roles("USER");
	  auth.inMemoryAuthentication().withUser("admin").password("123456").roles("ADMIN");
	  auth.inMemoryAuthentication().withUser("dba").password("123456").roles("DBA");
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {

	  http.authorizeRequests()
		.antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
		.antMatchers("/dba/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_DBA')")
		.and().formLogin();

	}
} 

相当于 Spring Security xml 文件:

 <http auto-config="true">
		<intercept-url pattern="/admin**" access="ROLE_ADMIN" />
		<intercept-url pattern="/dba**" access="ROLE_ADMIN,ROLE_DBA" />
	</http>

	<authentication-manager>
	  <authentication-provider>
	    <user-service>
		<user name="mkyong" password="123456" authorities="ROLE_USER" />
		<user name="admin" password="123456" authorities="ROLE_ADMIN" />
		<user name="dba" password="123456" authorities="ROLE_DBA" />
	    </user-service>
	  </authentication-provider>
	</authentication-manager> 

5.2 创建一个类扩展AbstractSecurityWebApplicationInitializer,它会自动加载springSecurityFilterChain

SpringSecurityInitializer.java

 package com.mkyong.config.core;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {
   //do nothing
} 

web.xml文件中相当于 Spring Security:

 <filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy
                </filter-class>
	</filter>

	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping> 

6.Spring MVC 配置

6.1 一个 Config 类,定义视图的技术和导入上面的SecurityConfig.java

AppConfig.java

 package com.mkyong.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@EnableWebMvc
@Configuration
@ComponentScan({ "com.mkyong.web.*" })
@Import({ SecurityConfig.class })
public class AppConfig {

	@Bean
	public InternalResourceViewResolver viewResolver() {
		InternalResourceViewResolver viewResolver 
                          = new InternalResourceViewResolver();
		viewResolver.setViewClass(JstlView.class);
		viewResolver.setPrefix("/WEB-INF/pages/");
		viewResolver.setSuffix(".jsp");
		return viewResolver;
	}

} 

相当于 Spring XML 文件:

 <context:component-scan base-package="com.mkyong.web.*" />

	<bean
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix">
			<value>/WEB-INF/pages/</value>
		</property>
		<property name="suffix">
			<value>.jsp</value>
		</property>
	</bean> 

6.2 创建一个Initializer类,来加载一切。

SpringMvcInitializer.java

 package com.mkyong.config.core;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.mkyong.config.AppConfig;

public class SpringMvcInitializer 
       extends AbstractAnnotationConfigDispatcherServletInitializer {

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

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return null;
	}

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

} 

完成了。

Note
In Servlet 3.x container environment + Spring container will detect and loads the Initializer classes automatically.

7.演示

7.1.欢迎页面–http://localhost:8080/spring-security-hello world-annotation/welcome

spring-security-helloworld-annotation-welcome

7.2 尝试访问/admin页面,Spring Security 会拦截请求并重定向到/login,显示默认登录表单。

spring-security-helloworld-annotation-login

7.3.如果用户名和密码不正确,就会显示错误消息,Spring 会重定向到这个 URL /login?error

spring-security-helloworld-annotation-login-error

7.4.如果用户名和密码正确,Spring 会将请求重定向到最初请求的 URL 并显示页面。

spring-security-helloworld-annotation-admin

7.5.对于未授权用户,Spring 将显示 403 拒绝访问页面。例如,用户“mkyong”或“dba”试图访问/admin URL。

spring-security-helloworld-annotation-403

下载源代码

Download it – spring-security-helloworld-annotation.zip (12 KB)

参考

  1. 春天的安全
  2. Spring Security Java Config Preview:Web Security
  3. Hello Spring MVC 安全 Java 配置
  4. 维基百科:Java Servlet
  5. 维基百科:阿帕奇雄猫
  6. Spring Security Hello World XML 示例

hello world spring security

Spring Security hello world 示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-hello-world-example/

在本教程中,我们将向您展示如何将 Spring Security 与 Spring MVC web 应用程序集成在一起,以保护 URL 访问。在实现了 Spring Security 之后,要访问“管理”页面的内容,用户需要输入正确的“用户名”和“密码”。

使用的技术:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布
  3. Eclipse 4.2
  4. JDK 1.6
  5. maven3

Note
Spring Security 3.0 requires Java 5.0 Runtime Environment or higher

1.项目演示

//web.archive.org/web/20210223165920if_/https://www.youtube.com/embed/hblHPyMuHJc

freestar.config.enabled_slots.push({ placementName: "mkyong_incontent_1", slotId: "mkyong_incontent_1" });

2.目录结构

查看本教程的最终目录结构。

spring-security-helloworld-directory

3.Spring 安全依赖项

要使用 Spring security,需要spring-security-webspring-security-config

pom.xml

 <properties>
		<jdk.version>1.6</jdk.version>
		<spring.version>3.2.8.RELEASE</spring.version>
		<spring.security.version>3.2.3.RELEASE</spring.security.version>
		<jstl.version>1.2</jstl.version>
	</properties>

	<dependencies>

		<!-- Spring dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring Security -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<!-- jstl for jsp page -->
		<dependency>
			<groupId>jstl</groupId>
			<artifactId>jstl</artifactId>
			<version>${jstl.version}</version>
		</dependency>

	</dependencies> 

4.Spring MVC Web 应用程序

一个简单的控制器:

  1. 如果 URL = /welcome/,返回 hello 页面。
  2. 如果 URL = /admin,返回管理页面。

稍后,我们将向您展示如何使用 Spring Security 来保护带有用户登录表单的“/admin”URL。

HelloController.java

 package com.mkyong.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class HelloController {

	@RequestMapping(value = { "/", "/welcome**" }, method = RequestMethod.GET)
	public ModelAndView welcomePage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Hello World");
		model.addObject("message", "This is welcome page!");
		model.setViewName("hello");
		return model;

	}

	@RequestMapping(value = "/admin**", method = RequestMethod.GET)
	public ModelAndView adminPage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Hello World");
		model.addObject("message", "This is protected page!");
		model.setViewName("admin");

		return model;

	}

} 

两个 JSP 页面。

hello.jsp

 <%@page session="false"%>
<html>
<body>
	<h1>Title : ${title}</h1>	
	<h1>Message : ${message}</h1>	
</body>
</html> 

admin.jsp

 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<body>
	<h1>Title : ${title}</h1>
	<h1>Message : ${message}</h1>

	<c:if test="${pageContext.request.userPrincipal.name != null}">
	   <h2>Welcome : ${pageContext.request.userPrincipal.name} 
           | <a href="<c:url value="/j_spring_security_logout" />" > Logout</a></h2>  
	</c:if>
</body>
</html> 

mvc-dispatcher-servlet.xml

 <beans 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans     
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<context:component-scan base-package="com.mkyong.*" />

	<bean
	  class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	  <property name="prefix">
		<value>/WEB-INF/pages/</value>
	  </property>
	  <property name="suffix">
		<value>.jsp</value>
	  </property>
	</bean>

</beans> 

5.Spring 安全性:用户认证

创建一个 Spring 安全 XML 文件。

spring-security.xml

 <beans:beans 
	xmlns:beans="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/security
	http://www.springframework.org/schema/security/spring-security-3.2.xsd">

	<http auto-config="true">
		<intercept-url pattern="/admin**" access="ROLE_USER" />
	</http>

	<authentication-manager>
	  <authentication-provider>
	    <user-service>
		<user name="mkyong" password="123456" authorities="ROLE_USER" />
	    </user-service>
	  </authentication-provider>
	</authentication-manager>

</beans:beans> 

它告诉我们,只有用户“mkyong”被允许访问/admin URL。

6.集成 Spring 安全

要将 Spring security 与 Spring MVC web 应用程序集成,只需将DelegatingFilterProxy声明为一个 servlet 过滤器来拦截任何传入的请求。

web.xml

 <web-app id="WebApp_ID" version="2.4"

	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

	<display-name>Spring MVC Application</display-name>

	<!-- Spring MVC -->
	<servlet>
		<servlet-name>mvc-dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet
		</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>mvc-dispatcher</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener
		</listener-class>
	</listener>

        <!-- Loads Spring Security config file -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			/WEB-INF/spring-security.xml
		</param-value>
	</context-param>

	<!-- Spring Security -->
	<filter>
		<filter-name>springSecurityFilterChain</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy
		</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>springSecurityFilterChain</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app> 

7.演示

就这些,但是等等…登录表单在哪里?不用担心,如果您没有定义任何定制的登录表单,Spring 会自动创建一个简单的登录表单。

Custom Login Form
Read this “Spring Security form login example” to understand how to create a custom login form in Spring Security.

1.欢迎页面-http://localhost:8080/spring-security-helloworld-xml/welcome

spring-security-helloworld-welcome

2.尝试访问/admin页面,Spring Security 会拦截请求并重定向到/spring_security_login,会显示一个预定义的登录表单。

spring-security-helloworld-login

3.如果用户名和密码不正确,就会显示错误消息,Spring 会重定向到这个 URL /spring_security_login?login_error

spring-security-helloworld-login-error

4.如果用户名和密码正确,Spring 会将请求重定向到最初请求的 URL 并显示页面。

spring-security-helloworld-admin

下载源代码

Download it – spring-security-helloworld-xml.zip (9 KB)

参考

  1. 春安官方网站
  2. Spring 3 MVC hello world 示例
  3. Spring 安全表单登录示例(认证)

Tags : hello world spring securityfreestar.config.enabled_slots.push({ placementName: "mkyong_leaderboard_btf", slotId: "mkyong_leaderboard_btf" });

Spring Security + Hibernate 注释示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-hibernate-annotation-example/

spring-hibernate-logo

在本教程中,先前的Spring Security+hibernate 4 XML示例将被重用,并将其转换为基于注释的示例。

使用的技术:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布
  3. Hibernate 4.2.11 .最终版
  4. MySQL 服务器 5.6
  5. Tomcat 7 (Servlet 3.x 容器)

快速笔记:

  1. LocalSessionFactoryBuilder创建一个会话工厂
  2. 将会话工厂注入用户道
  3. 将 UserDao 集成到一个自定义的UserDetailsService中,从数据库中加载用户。

1.项目目录

最终项目目录结构。

spring-security-hibernate-annotation-directory ## 2.用户模型+映射文件

模型类及其基于注释的映射文件。

User.java

 package com.mkyong.users.model;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "users", catalog = "test")
public class User {

	private String username;
	private String password;
	private boolean enabled;
	private Set<UserRole> userRole = new HashSet<UserRole>(0);

	public User() {
	}

	public User(String username, String password, boolean enabled) {
		this.username = username;
		this.password = password;
		this.enabled = enabled;
	}

	public User(String username, String password, 
		boolean enabled, Set<UserRole> userRole) {
		this.username = username;
		this.password = password;
		this.enabled = enabled;
		this.userRole = userRole;
	}

	@Id
	@Column(name = "username", unique = true, 
		nullable = false, length = 45)
	public String getUsername() {
		return this.username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	@Column(name = "password", 
		nullable = false, length = 60)
	public String getPassword() {
		return this.password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Column(name = "enabled", nullable = false)
	public boolean isEnabled() {
		return this.enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
	public Set<UserRole> getUserRole() {
		return this.userRole;
	}

	public void setUserRole(Set<UserRole> userRole) {
		this.userRole = userRole;
	}

} 

UserRole.java

 package com.mkyong.users.model;

import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name = "user_roles", catalog = "test", 
	uniqueConstraints = @UniqueConstraint(
		columnNames = { "role", "username" }))
public class UserRole{

	private Integer userRoleId;
	private User user;
	private String role;

	public UserRole() {
	}

	public UserRole(User user, String role) {
		this.user = user;
		this.role = role;
	}

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "user_role_id", 
		unique = true, nullable = false)
	public Integer getUserRoleId() {
		return this.userRoleId;
	}

	public void setUserRoleId(Integer userRoleId) {
		this.userRoleId = userRoleId;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "username", nullable = false)
	public User getUser() {
		return this.user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	@Column(name = "role", nullable = false, length = 45)
	public String getRole() {
		return this.role;
	}

	public void setRole(String role) {
		this.role = role;
	}

} 

3.道类

DAO 类,通过 Hibernate 从数据库加载数据。

UserDao.java

 package com.mkyong.users.dao;

import com.mkyong.users.model.User;

public interface UserDao {

	User findByUserName(String username);

} 

UserDaoImpl.java

 package com.mkyong.users.dao;

import java.util.ArrayList;
import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.mkyong.users.model.User;

@Repository
public class UserDaoImpl implements UserDao {

	@Autowired
	private SessionFactory sessionFactory;

	@SuppressWarnings("unchecked")
	public User findByUserName(String username) {

		List<User> users = new ArrayList<User>();

		users = sessionFactory.getCurrentSession()
			.createQuery("from User where username=?")
			.setParameter(0, username)
			.list();

		if (users.size() > 0) {
			return users.get(0);
		} else {
			return null;
		}

	}

} 

4.用户详细信息服务

使用@Transactional来声明一个事务方法。

MyUserDetailsService.java

 package com.mkyong.users.service;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.mkyong.users.dao.UserDao;
import com.mkyong.users.model.UserRole;

@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {

	//get user from the database, via Hibernate
	@Autowired
	private UserDao userDao;

	@Transactional(readOnly=true)
	@Override
	public UserDetails loadUserByUsername(final String username) 
		throws UsernameNotFoundException {

		com.mkyong.users.model.User user = userDao.findByUserName(username);
		List<GrantedAuthority> authorities = 
                                      buildUserAuthority(user.getUserRole());

		return buildUserForAuthentication(user, authorities);

	}

	// Converts com.mkyong.users.model.User user to
	// org.springframework.security.core.userdetails.User
	private User buildUserForAuthentication(com.mkyong.users.model.User user, 
		List<GrantedAuthority> authorities) {
		return new User(user.getUsername(), user.getPassword(), 
			user.isEnabled(), true, true, true, authorities);
	}

	private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {

		Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();

		// Build user's authorities
		for (UserRole userRole : userRoles) {
			setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
		}

		List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);

		return Result;
	}

} 

5.Spring 安全注释

用注释声明和绑定一切,阅读注释,它应该是不言自明的。

SecurityConfig.java

 package com.mkyong.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	@Qualifier("userDetailsService")
	UserDetailsService userDetailsService;

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {

	    http.authorizeRequests().antMatchers("/admin/**")
		.access("hasRole('ROLE_ADMIN')").and().formLogin()
		.loginPage("/login").failureUrl("/login?error")
		.usernameParameter("username")
		.passwordParameter("password")
		.and().logout().logoutSuccessUrl("/login?logout")
		.and().csrf()
		.and().exceptionHandling().accessDeniedPage("/403");
	}

	@Bean
	public PasswordEncoder passwordEncoder(){
		PasswordEncoder encoder = new BCryptPasswordEncoder();
		return encoder;
	}

} 

使用LocalSessionFactoryBuilder创建一个会话工厂。

AppConfig.java

 package com.mkyong.config;

import java.util.Properties;
import org.apache.commons.dbcp.BasicDataSource;
import org.hibernate.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBuilder;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@EnableWebMvc
@Configuration
@ComponentScan({ "com.mkyong.*" })
@EnableTransactionManagement
@Import({ SecurityConfig.class })
public class AppConfig {

        @Bean
        public SessionFactory sessionFactory() {
                LocalSessionFactoryBuilder builder = 
			new LocalSessionFactoryBuilder(dataSource());
                builder.scanPackages("com.mkyong.users.model")
                      .addProperties(getHibernateProperties());

                return builder.buildSessionFactory();
        }

	private Properties getHibernateProperties() {
                Properties prop = new Properties();
                prop.put("hibernate.format_sql", "true");
                prop.put("hibernate.show_sql", "true");
                prop.put("hibernate.dialect", 
                    "org.hibernate.dialect.MySQL5Dialect");
                return prop;
        }

	@Bean(name = "dataSource")
	public BasicDataSource dataSource() {

		BasicDataSource ds = new BasicDataSource();
	        ds.setDriverClassName("com.mysql.jdbc.Driver");
		ds.setUrl("jdbc:mysql://localhost:3306/test");
		ds.setUsername("root");
		return ds;
	}

	//Create a transaction manager
	@Bean
        public HibernateTransactionManager txManager() {
                return new HibernateTransactionManager(sessionFactory());
        }

	@Bean
	public InternalResourceViewResolver viewResolver() {
		InternalResourceViewResolver viewResolver 
                             = new InternalResourceViewResolver();
		viewResolver.setViewClass(JstlView.class);
		viewResolver.setPrefix("/WEB-INF/pages/");
		viewResolver.setSuffix(".jsp");
		return viewResolver;
	}

} 

完成了。

6.项目演示

以下视频演示为 Spring 安全数据库登录教程。由于本教程生成相同的输出,所以视频演示被重用。

//web.archive.org/web/20190220134456if_/http://www.youtube.com/embed/2ms57c2EdUg

6.1 进入密码保护页面:http://localhost:8080/spring-security-hibernate-annotation/admin,显示登录页面。

spring-security-hibernate-annotation1

6.2 输入用户“mkyong”和密码“123456”。

spring-security-hibernate-annotation2

6.3 使用用户“alex”和密码“123456”尝试访问/admin页面,将显示 403 页面。

spring-security-hibernate-annotation3

下载源代码

Download it – spring-security-hibernate-annotation.zip (35 KB)

参考

  1. Spring Security + Hibernate XML 示例
  2. 春安你好世界注释示例
  3. LocalSessionFactoryBuilder JavaDoc
  4. 春季 ORM–冬眠
  5. 春季冬眠 4 LocalSessionFactoryBean JavaDoc
  6. 春季交易管理
  7. Hibernate ORM 文档
  8. 用 JDBC 的 Spring 安全表单登录数据库
  9. 休眠:没有为当前线程找到会话

annotation spring hibernate spring security

Spring Security + Hibernate XML 示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-hibernate-xml-example/

spring-hibernate-logo

在本教程中,我们将向您展示如何在 Spring Security、XML 配置示例中集成 Hibernate 4。

Note
For annotation version, please read this Spring Security + Hibernate Annotation Example.

使用的技术:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布
  3. Hibernate 4.2.11 .最终版
  4. MySQL 服务器 5.6
  5. JDK 1.6
  6. maven3
  7. Eclipse 4.3

快速笔记

  1. hibernate4.LocalSessionFactoryBean创建一个会话工厂
  2. 将会话工厂注入用户道
  3. 为了与 Spring Security 集成,创建一个实现UserDetailsService接口的类,并用 UserDao 加载用户
  4. 必须声明事务管理器,否则 Hibernate 在 Spring 中将无法工作

1.项目目录

最终项目目录结构。

spring-security-hibernate-directory ## 2.项目依赖性

POM 文件中项目依赖项的列表。

pom.xml

 <properties>
		<spring.version>3.2.8.RELEASE</spring.version>
		<spring.security.version>3.2.3.RELEASE</spring.security.version>
		<jstl.version>1.2</jstl.version>
		<mysql.connector.version>5.1.30</mysql.connector.version>
		<logback.version>1.1.2</logback.version>
		<slf4j.version>1.7.6</slf4j.version>
		<hibernate.version>4.2.11.Final</hibernate.version>
		<dbcp.version>1.4</dbcp.version>
		<servletapi.version>2.5</servletapi.version>
	</properties>

	<dependencies>

		<!-- database pool -->
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>${dbcp.version}</version>
		</dependency>

		<!-- Hibernate ORM -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernate.version}</version>
		</dependency>

		<!-- Spring 3 dependencies -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
			<exclusions>
			  <exclusion>
				<groupId>commons-logging</groupId>
				<artifactId>commons-logging</artifactId>
			  </exclusion>
			</exclusions>
		</dependency>

		<!-- Spring MVC -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring + aspects -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aspects</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- ORM integration, e.g Hibernate -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>

		<!-- Spring Security -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<!-- Spring Security JSP Taglib -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-taglibs</artifactId>
			<version>${spring.security.version}</version>
		</dependency>

		<!-- jstl for jsp page -->
		<dependency>
			<groupId>jstl</groupId>
			<artifactId>jstl</artifactId>
			<version>${jstl.version}</version>
		</dependency>

		<!-- MySql Driver -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql.connector.version}</version>
		</dependency>

		<!-- logging, slf4j -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j.version}</version>
		</dependency>

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
			<version>${slf4j.version}</version>
		</dependency>

		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>${logback.version}</version>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>servlet-api</artifactId>
			<version>${servletapi.version}</version>
			<scope>provided</scope>
		</dependency>

	</dependencies> 

3.用户表

创建存储用户和用户角色的表的 SQL 脚本。

mysql.sql

 CREATE  TABLE users (
  username VARCHAR(45) NOT NULL ,
  password VARCHAR(60) NOT NULL ,
  enabled TINYINT NOT NULL DEFAULT 1 ,
  PRIMARY KEY (username));

CREATE TABLE user_roles (
  user_role_id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(45) NOT NULL,
  role varchar(45) NOT NULL,
  PRIMARY KEY (user_role_id),
  UNIQUE KEY uni_username_role (role,username),
  KEY fk_username_idx (username),
  CONSTRAINT fk_username FOREIGN KEY (username) REFERENCES users (username));

INSERT INTO users(username,password,enabled)
VALUES ('mkyong','$2a$10$04TVADrR6/SPLBjsK0N30.Jf5fNjBugSACeGv1S69dZALR7lSov0y', true);
INSERT INTO users(username,password,enabled)
VALUES ('alex','$2a$10$04TVADrR6/SPLBjsK0N30.Jf5fNjBugSACeGv1S69dZALR7lSov0y', true);

INSERT INTO user_roles (username, role)
VALUES ('mkyong', 'ROLE_USER');
INSERT INTO user_roles (username, role)
VALUES ('mkyong', 'ROLE_ADMIN');
INSERT INTO user_roles (username, role)
VALUES ('alex', 'ROLE_USER'); 

4.用户模型+ Hibernate XML 映射

模型类及其 XML 映射文件。

User.java

 package com.mkyong.users.model;

import java.util.HashSet;
import java.util.Set;

public class User {

	private String username;
	private String password;
	private boolean enabled;
	private Set<UserRole> userRole = new HashSet<UserRole>(0);

	//getter and setter methods
} 

UserRole.java

 package com.mkyong.users.model;

public class UserRole{

	private Integer userRoleId;
	private User user;
	private String role;

	//getter and setter methods
} 

Users.hbm.xml

 <?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.mkyong.users.model.User" table="users" catalog="test">
        <id name="username" type="string">
            <column name="username" length="45" />
            <generator class="assigned" />
        </id>
        <property name="password" type="string">
            <column name="password" length="60" not-null="true" />
        </property>
        <property name="enabled" type="boolean">
            <column name="enabled" not-null="true" />
        </property>
        <set name="userRole" table="user_roles" inverse="true" lazy="true" fetch="select">
            <key>
                <column name="username" length="45" not-null="true" />
            </key>
            <one-to-many class="com.mkyong.users.model.UserRole" />
        </set>
    </class>
</hibernate-mapping> 

UserRoles.hbm.xml

 <?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.mkyong.users.model.UserRole" table="user_roles" catalog="test">
        <id name="userRoleId" type="java.lang.Integer">
            <column name="user_role_id" />
            <generator class="identity" />
        </id>
        <many-to-one name="user" class="com.mkyong.users.model.User" fetch="select">
            <column name="username" length="45" not-null="true" />
        </many-to-one>
        <property name="role" type="string">
            <column name="role" length="45" not-null="true" />
        </property>
    </class>
</hibernate-mapping> 

5.道类

创建一个UserDao类,通过 Hibernate 从数据库加载用户。

UserDao.java

 package com.mkyong.users.dao;

import com.mkyong.users.model.User;

public interface UserDao {

	User findByUserName(String username);

} 

UserDaoImpl.java

 package com.mkyong.users.dao;

import java.util.ArrayList;
import java.util.List;
import org.hibernate.SessionFactory;

import com.mkyong.users.model.User;

public class UserDaoImpl implements UserDao {

	private SessionFactory sessionFactory;

	@SuppressWarnings("unchecked")
	public User findByUserName(String username) {

		List<User> users = new ArrayList<User>();

		users = getSessionFactory().getCurrentSession()
			.createQuery("from User where username=?")
			.setParameter(0, username).list();

		if (users.size() > 0) {
			return users.get(0);
		} else {
			return null;
		}

	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

} 

6.用户详细信息服务

创建一个自定义的UserDetailsService,从UserDao加载用户,然后建立用户的权限。

Note
Example is reference from this Spring’s JdbcDaoImplMyUserDetailsService.java

 package com.mkyong.users.service;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.mkyong.users.dao.UserDao;
import com.mkyong.users.model.UserRole;

public class MyUserDetailsService implements UserDetailsService {

	private UserDao userDao;

	@Override
	public UserDetails loadUserByUsername(final String username) 
               throws UsernameNotFoundException {

		com.mkyong.users.model.User user = userDao.findByUserName(username);
		List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());

		return buildUserForAuthentication(user, authorities);

	}

	// Converts com.mkyong.users.model.User user to
	// org.springframework.security.core.userdetails.User
	private User buildUserForAuthentication(com.mkyong.users.model.User user, 
		List<GrantedAuthority> authorities) {
		return new User(user.getUsername(), 
			user.getPassword(), user.isEnabled(), 
                        true, true, true, authorities);
	}

	private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {

		Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();

		// Build user's authorities
		for (UserRole userRole : userRoles) {
			setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
		}

		List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);

		return Result;
	}

	public UserDao getUserDao() {
		return userDao;
	}

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

} 

7.Spring 安全 XML

看评论,应该不言自明。

spring-security.xml

 <beans:beans 
	xmlns:beans="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/security
	http://www.springframework.org/schema/security/spring-security-3.2.xsd">

	<!-- enable use-expressions -->
	<http auto-config="true" use-expressions="true">
		<intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" />

		<!-- access denied page -->
		<access-denied-handler error-page="/403" />
		<form-login 
		    login-page="/login" 
		    default-target-url="/welcome"
			authentication-failure-url="/login?error" 
			username-parameter="username"
			password-parameter="password" />
		<logout logout-success-url="/login?logout" />
		<!-- enable csrf protection -->
		<csrf />
	</http>

	<authentication-manager>
		<authentication-provider user-service-ref="myUserDetailsService" >
			<password-encoder hash="bcrypt" />    
		</authentication-provider>
	</authentication-manager>

</beans:beans> 

spring-database.xml

 <beans 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
	http://www.springframework.org/schema/aop 
	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
	http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

	<!-- MySQL data source -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">

		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/test" />
		<property name="username" value="root" />
		<property name="password" value="password" />
	</bean>

	<!-- Hibernate session factory -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="mappingResources">
			<list>
				<value>orm/Users.hbm.xml</value>
				<value>orm/UserRoles.hbm.xml</value>
			</list>
		</property>
		<property name="hibernateProperties">
		    <props>
			<prop key="hibernate.dialect">
                           org.hibernate.dialect.MySQL5Dialect
                        </prop>
			<prop key="hibernate.format_sql">true</prop>
			<prop key="hibernate.show_sql">true</prop>
		    </props>
		</property>
	</bean>

	<bean id="userDao" class="com.mkyong.users.dao.UserDaoImpl">
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

	<bean id="myUserDetailsService" 
                class="com.mkyong.users.service.MyUserDetailsService">
		<property name="userDao" ref="userDao" />
	</bean>

	<!-- MUST have transaction manager, using aop and aspects  -->
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory"></property>
	</bean>

	<tx:advice id="txAdvice" transaction-manager="transactionManager">
	    <tx:attributes>
		<tx:method name="get*" read-only="true" />
		<tx:method name="find*" read-only="true" />
		<tx:method name="*" />
	    </tx:attributes>
	</tx:advice>

	<aop:config>
	    <aop:pointcut id="userServicePointCut"
		expression="execution(* com.mkyong.users.service.*Service.*(..))" />
	    <aop:advisor advice-ref="txAdvice" pointcut-ref="userServicePointCut" />
	</aop:config>

</beans> 

完成了。

Note
Those JSP, web.xml and controller files are omitted, it’s quite a standard code, if you are interested on it, please download the full source code at the end of the article.

8.项目演示

下面的视频演示是针对 Spring 安全数据库登录与 JDBC 教程的。由于本教程生成相同的输出,但是使用 Hibernate 来加载用户,所以视频演示被重用。

//web.archive.org/web/20190223081526if_/http://www.youtube.com/embed/2ms57c2EdUg

8.1 访问受密码保护的页面:http://localhost:8080/spring-security-hibernate/admin,显示登录页面。

spring-security-hibernate1

8.2 输入用户“mkyong”和密码“123456”。

spring-security-hibernate2

8.3 使用用户“alex”和密码“123456”尝试访问/admin页面,将显示 403 页面。

spring-security-hibernate3

下载源代码

Download it – spring-security-hibernate.zip (30 KB)

参考

  1. 春季 ORM–冬眠
  2. 春季冬眠 4 LocalSessionFactoryBean JavaDoc
  3. 春季冬眠 3 LocalSessionFactoryBean JavaDoc
  4. 春季交易管理
  5. Hibernate ORM 文档
  6. 用 JDBC 的 Spring 安全表单登录数据库
  7. 休眠:没有为当前线程找到会话

hibernate spring hibernate spring security

Spring Security HTTP 基本认证示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-http-basic-authentication-example/

配置 HTTP 基本身份验证后,web 浏览器将显示用户身份验证的登录对话框。本教程向您展示了如何在 Spring Security 中配置 HTTP 基本身份验证。

 <http>
	<intercept-url pattern="/welcome*" access="ROLE_USER" />
	<http-basic />
  </http> 

最后一个 Spring Security 基于表单的登录示例将被重用,但是切换身份验证以支持 HTTP basic。

1.春天安全

要启用 HTTP basic,只需将“ form-login ”更改为“ http-basic ”标签。

 <beans:beans 
	xmlns:beans="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/security
	http://www.springframework.org/schema/security/spring-security-3.0.3.xsd">

	<!-- HTTP basic authentication in Spring Security -->
	<http>
		<intercept-url pattern="/welcome*" access="ROLE_USER" />
		<http-basic />
	</http>

	<authentication-manager>
	   <authentication-provider>
	       <user-service>
		   <user name="mkyong" password="123456" authorities="ROLE_USER" />
	       </user-service>
	   </authentication-provider>
	</authentication-manager>

</beans:beans> 

完了,就这样。

2.演示

当访问安全的 URL 时,浏览器会自动显示一个登录对话框。

网址:http://localhost:8080/spring MVC/welcome

http basic example ## 下载源代码

Download it – Spring-Security-HTTP-Basic-Authentication-Example.zip (9 KB)

参考

  1. 春日安全 hello world 示例
  2. Spring Security 基于表单的登录示例

authentication spring security

Spring 安全性:限制登录尝试示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-limit-login-attempts-example/

spring-security-limit-login-attempts-locked

在本教程中,我们将向您展示如何在 Spring Security 中限制登录尝试,这意味着,如果用户尝试使用无效密码登录超过 3 次,系统将锁定该用户,使其无法再登录。

使用的技术和工具:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布
  3. 春季 JDBC 3.2.3 .发布
  4. Eclipse 4.2
  5. JDK 1.6
  6. maven3
  7. MySQL 服务器 5.6
  8. Tomcat 7 (Servlet 3.x)

本教程的一些快速注释:

  1. 将使用 MySQL 数据库。
  2. 这是一个基于 Spring 安全注释的例子。
  3. 创建一个包含“accountNonLocked”列的“users”表。
  4. 创建一个“user_attempts”表来存储无效的登录尝试。
  5. 将使用弹簧 JDBC。
  6. 根据返回的异常显示自定义错误信息。
  7. 创建自定义的“authenticationProvider”

1.解决办法

查看现有的 Spring Security 的身份验证类,已经实现了“锁定”特性。要启用限制登录尝试,您需要将UserDetails.isAccountNonLocked设置为 false。

DaoAuthenticationProvider.java

 package org.springframework.security.authentication.dao;

public class DaoAuthenticationProvider 
	extends AbstractUserDetailsAuthenticationProvider {
	//...
} 

AbstractUserDetailsAuthenticationProvider.java

 package org.springframework.security.authentication.dao;

public abstract class AbstractUserDetailsAuthenticationProvider 
    implements AuthenticationProvider, InitializingBean,
    MessageSourceAware {

    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        public void check(UserDetails user) {
          if (!user.isAccountNonLocked()) {
              logger.debug("User account is locked");

          throw new LockedException(
              messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
                 "User account is locked"), user);
          }
           //...
        }
    } 

2.项目演示

//web.archive.org/web/20190210095107if_/http://www.youtube.com/embed/vyoD4ALC43s

3.项目目录

查看最终项目结构(基于注释):

spring-security-limit-login-attempts-directory

4.数据库ˌ资料库

下面是创建 users、user_roles 和 user_attempts 表的 MySQL 脚本。

4.1 创建一个“用户”表,列为“accountNonLocked”。

users.sql

 CREATE  TABLE users (
  username VARCHAR(45) NOT NULL ,
  password VARCHAR(45) NOT NULL ,
  enabled TINYINT NOT NULL DEFAULT 1 ,
  accountNonExpired TINYINT NOT NULL DEFAULT 1 ,
  accountNonLocked TINYINT NOT NULL DEFAULT 1 ,
  credentialsNonExpired TINYINT NOT NULL DEFAULT 1,
  PRIMARY KEY (username)); 

4.2 创建一个“用户角色”表。

user_roles.sql

 CREATE TABLE user_roles (
  user_role_id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(45) NOT NULL,
  role varchar(45) NOT NULL,
  PRIMARY KEY (user_role_id),
  UNIQUE KEY uni_username_role (role,username),
  KEY fk_username_idx (username),
  CONSTRAINT fk_username FOREIGN KEY (username) REFERENCES users (username)); 

4.3 创建“用户尝试次数”表。

user_attempts.sql

 CREATE TABLE user_attempts (
  id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(45) NOT NULL,
  attempts varchar(45) NOT NULL,
  lastModified datetime NOT NULL,
  PRIMARY KEY (id)
); 

4.4 插入一个用户进行测试。

 INSERT INTO users(username,password,enabled)
VALUES ('mkyong','123456', true);

INSERT INTO user_roles (username, role)
VALUES ('mkyong', 'ROLE_USER');
INSERT INTO user_roles (username, role)
VALUES ('mkyong', 'ROLE_ADMIN'); 

5.用户尝试类别

该类表示“用户尝试次数”表的数据。

UserAttempts.java

 package com.mkyong.users.model;

import java.util.Date;

public class UserAttempts {

	private int id;
	private String username;
	private int attempts;
	private Date lastModified;

	//getter and setter

} 

6.道类

一个 DAO 类,用来更新无效的登录尝试,阅读注释,不言自明。

UserDetailsDao.java

 package com.mkyong.users.dao;

import com.mkyong.users.model.UserAttempts;

public interface UserDetailsDao {

	void updateFailAttempts(String username);
	void resetFailAttempts(String username);
	UserAttempts getUserAttempts(String username);

} 

UserDetailsDaoImpl.java

 package com.mkyong.users.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.security.authentication.LockedException;
import org.springframework.stereotype.Repository;

import com.mkyong.users.model.UserAttempts;

@Repository
public class UserDetailsDaoImpl extends JdbcDaoSupport implements UserDetailsDao {

	private static final String SQL_USERS_UPDATE_LOCKED = "UPDATE USERS SET accountNonLocked = ? WHERE username = ?";
	private static final String SQL_USERS_COUNT = "SELECT count(*) FROM USERS WHERE username = ?";

	private static final String SQL_USER_ATTEMPTS_GET = "SELECT * FROM USER_ATTEMPTS WHERE username = ?";
	private static final String SQL_USER_ATTEMPTS_INSERT = "INSERT INTO USER_ATTEMPTS (USERNAME, ATTEMPTS, LASTMODIFIED) VALUES(?,?,?)";
	private static final String SQL_USER_ATTEMPTS_UPDATE_ATTEMPTS = "UPDATE USER_ATTEMPTS SET attempts = attempts + 1, lastmodified = ? WHERE username = ?";
	private static final String SQL_USER_ATTEMPTS_RESET_ATTEMPTS = "UPDATE USER_ATTEMPTS SET attempts = 0, lastmodified = null WHERE username = ?";

	private static final int MAX_ATTEMPTS = 3;

	@Autowired
	private DataSource dataSource;

	@PostConstruct
	private void initialize() {
		setDataSource(dataSource);
	}

	@Override
	public void updateFailAttempts(String username) {

	  UserAttempts user = getUserAttempts(username);
	  if (user == null) {
		if (isUserExists(username)) {
			// if no record, insert a new
			getJdbcTemplate().update(SQL_USER_ATTEMPTS_INSERT, new Object[] { username, 1, new Date() });
		}
	  } else {

		if (isUserExists(username)) {
			// update attempts count, +1
			getJdbcTemplate().update(SQL_USER_ATTEMPTS_UPDATE_ATTEMPTS, new Object[] { new Date(), username});
		}

		if (user.getAttempts() + 1 >= MAX_ATTEMPTS) {
			// locked user
			getJdbcTemplate().update(SQL_USERS_UPDATE_LOCKED, new Object[] { false, username });
			// throw exception
			throw new LockedException("User Account is locked!");
		}

	  }

	}

	@Override
	public UserAttempts getUserAttempts(String username) {

	  try {

		UserAttempts userAttempts = getJdbcTemplate().queryForObject(SQL_USER_ATTEMPTS_GET,
			new Object[] { username }, new RowMapper<UserAttempts>() {
			public UserAttempts mapRow(ResultSet rs, int rowNum) throws SQLException {

				UserAttempts user = new UserAttempts();
				user.setId(rs.getInt("id"));
				user.setUsername(rs.getString("username"));
				user.setAttempts(rs.getInt("attempts"));
				user.setLastModified(rs.getDate("lastModified"));

				return user;
			}

		});
		return userAttempts;

	  } catch (EmptyResultDataAccessException e) {
		return null;
	  }

	}

	@Override
	public void resetFailAttempts(String username) {

	  getJdbcTemplate().update(
             SQL_USER_ATTEMPTS_RESET_ATTEMPTS, new Object[] { username });

	}

	private boolean isUserExists(String username) {

	  boolean result = false;

	  int count = getJdbcTemplate().queryForObject(
                            SQL_USERS_COUNT, new Object[] { username }, Integer.class);
	  if (count > 0) {
		result = true;
	  }

	  return result;
	}

} 

7.用户详细信息服务

默认情况下,JdbcDaoImpl会一直将accountNonLocked设置为 true,这不是我们想要的。查看源代码。

JdbcDaoImpl.java

 package org.springframework.security.core.userdetails.jdbc;

public class JdbcDaoImpl extends JdbcDaoSupport implements UserDetailsService {
  //...
  protected List<UserDetails> loadUsersByUsername(String username) {
	return getJdbcTemplate().query(usersByUsernameQuery, new String[] {username}, new RowMapper<UserDetails>() {
	  public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
		String username = rs.getString(1);
		String password = rs.getString(2);
		boolean enabled = rs.getBoolean(3);
		return new User(username, password, enabled, true, true, true, AuthorityUtils.NO_AUTHORITIES);
	  }

  });
} 

为了节省开发时间,您可以扩展JdbcDaoImpl并覆盖loadUsersByUsernamecreateUserDetails来获得定制的UserDetails

CustomUserDetailsService.java

 package com.mkyong.users.service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
import org.springframework.stereotype.Service;

/**
 * Reference org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl
 * 
 * @author mkyong
 * 
 */
@Service("userDetailsService")
public class CustomUserDetailsService extends JdbcDaoImpl {

	@Autowired
	private DataSource dataSource;

	@PostConstruct
	private void initialize() {
		setDataSource(dataSource);
	}

	@Override
	@Value("select * from users where username = ?")
	public void setUsersByUsernameQuery(String usersByUsernameQueryString) {
		super.setUsersByUsernameQuery(usersByUsernameQueryString);
	}

	@Override
	@Value("select username, role from user_roles where username =?")
	public void setAuthoritiesByUsernameQuery(String queryString) {
		super.setAuthoritiesByUsernameQuery(queryString);
	}

	//override to get accountNonLocked  
	@Override
	public List<UserDetails> loadUsersByUsername(String username) {
	  return getJdbcTemplate().query(super.getUsersByUsernameQuery(), new String[] { username },
		new RowMapper<UserDetails>() {
		  public UserDetails mapRow(ResultSet rs, int rowNum) throws SQLException {
			String username = rs.getString("username");
			String password = rs.getString("password");
			boolean enabled = rs.getBoolean("enabled");
			boolean accountNonExpired = rs.getBoolean("accountNonExpired");
			boolean credentialsNonExpired = rs.getBoolean("credentialsNonExpired");
			boolean accountNonLocked = rs.getBoolean("accountNonLocked");

			return new User(username, password, enabled, accountNonExpired, credentialsNonExpired,
				accountNonLocked, AuthorityUtils.NO_AUTHORITIES);
		  }

	  });
	}

	//override to pass accountNonLocked
	@Override
	public UserDetails createUserDetails(String username, UserDetails userFromUserQuery,
			List<GrantedAuthority> combinedAuthorities) {
		String returnUsername = userFromUserQuery.getUsername();

		if (super.isUsernameBasedPrimaryKey()) {
		  returnUsername = username;
		}

		return new User(returnUsername, userFromUserQuery.getPassword(), 
                       userFromUserQuery.isEnabled(),
		       userFromUserQuery.isAccountNonExpired(), 
                       userFromUserQuery.isCredentialsNonExpired(),
			userFromUserQuery.isAccountNonLocked(), combinedAuthorities);
	}

} 

8.DaoAuthenticationProvider

创建一个自定义身份验证提供程序,对于每次无效的登录尝试,更新 user_attempts 表,如果达到最大尝试次数,则抛出LockedException

LimitLoginAuthenticationProvider.java

 package com.mkyong.web.handler;

import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;

import com.mkyong.users.dao.UserDetailsDao;
import com.mkyong.users.model.UserAttempts;

@Component("authenticationProvider")
public class LimitLoginAuthenticationProvider extends DaoAuthenticationProvider {

	@Autowired
	UserDetailsDao userDetailsDao;

	@Autowired
	@Qualifier("userDetailsService")
	@Override
	public void setUserDetailsService(UserDetailsService userDetailsService) {
		super.setUserDetailsService(userDetailsService);
	}

	@Override
	public Authentication authenticate(Authentication authentication) 
          throws AuthenticationException {

	  try {

		Authentication auth = super.authenticate(authentication);

		//if reach here, means login success, else an exception will be thrown
		//reset the user_attempts
		userDetailsDao.resetFailAttempts(authentication.getName());

		return auth;

	  } catch (BadCredentialsException e) {	

		//invalid login, update to user_attempts
		userDetailsDao.updateFailAttempts(authentication.getName());
		throw e;

	  } catch (LockedException e){

		//this user is locked!
		String error = "";
		UserAttempts userAttempts = 
                    userDetailsDao.getUserAttempts(authentication.getName());

               if(userAttempts!=null){
			Date lastAttempts = userAttempts.getLastModified();
			error = "User account is locked! <br><br>Username : " 
                           + authentication.getName() + "<br>Last Attempts : " + lastAttempts;
		}else{
			error = e.getMessage();
		}

	  throw new LockedException(error);
	}

	}

} 

9.弹簧控制器

一个标准的控制器类,参考login方法,它向你展示了如何摆弄会话值——“SPRING _ SECURITY _ LAST _ EXCEPTION”,并自定义错误消息。

MainController.java

 package com.mkyong.web.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MainController {

	@RequestMapping(value = { "/", "/welcome**" }, method = RequestMethod.GET)
	public ModelAndView defaultPage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Limit Login - Annotation");
		model.addObject("message", "This is default page!");
		model.setViewName("hello");
		return model;

	}

	@RequestMapping(value = "/admin**", method = RequestMethod.GET)
	public ModelAndView adminPage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Limit Login - Annotation");
		model.addObject("message", "This page is for ROLE_ADMIN only!");
		model.setViewName("admin");

		return model;

	}

	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public ModelAndView login(
                @RequestParam(value = "error", required = false) String error,
		@RequestParam(value = "logout", required = false) String logout, 
                HttpServletRequest request) {

		ModelAndView model = new ModelAndView();
		if (error != null) {
			model.addObject("error", 
                           getErrorMessage(request, "SPRING_SECURITY_LAST_EXCEPTION"));
		}

		if (logout != null) {
			model.addObject("msg", "You've been logged out successfully.");
		}
		model.setViewName("login");

		return model;

	}

	//customize the error message
	private String getErrorMessage(HttpServletRequest request, String key){

		Exception exception = 
                   (Exception) request.getSession().getAttribute(key);

		String error = "";
		if (exception instanceof BadCredentialsException) {
			error = "Invalid username and password!";
		}else if(exception instanceof LockedException) {
			error = exception.getMessage();
		}else{
			error = "Invalid username and password!";
		}

		return error;
	}

	// for 403 access denied page
	@RequestMapping(value = "/403", method = RequestMethod.GET)
	public ModelAndView accesssDenied() {

		ModelAndView model = new ModelAndView();

		// check if user is login
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		if (!(auth instanceof AnonymousAuthenticationToken)) {
			UserDetails userDetail = (UserDetails) auth.getPrincipal();
			System.out.println(userDetail);

			model.addObject("username", userDetail.getUsername());

		}

		model.setViewName("403");
		return model;

	}

} 

10.Spring 安全配置

附上您定制的authenticationProvider

SecurityConfig.java

 package com.mkyong.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	@Qualifier("authenticationProvider")
	AuthenticationProvider authenticationProvider;

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth.authenticationProvider(authenticationProvider);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http.authorizeRequests().antMatchers("/admin/**")
		  .access("hasRole('ROLE_USER')").and().formLogin()
		  .loginPage("/login").failureUrl("/login?error")
			.usernameParameter("username")
			.passwordParameter("password")
		  .and().logout().logoutSuccessUrl("/login?logout").and().csrf();
	}
} 

完成了。

11.演示

演示页面–http://localhost:8080/spring-security-limit-log in-annotation/admin

11.1,第一次无效登录尝试,将显示一个正常的错误消息。

spring-security-limit-login-attempts-1

11.2,如果达到无效登录尝试的最大次数,将显示错误消息“用户帐户被锁定”。

spring-security-limit-login-attempts-locked

11.3、如果用户处于“锁定”状态,仍然尝试再次登录。将显示锁定的详细信息。

spring-security-limit-login-attempts-locked-detail

11.4 查看“用户”表,如果“account non locked”= 0 或 false,则表示该用户处于锁定状态。

spring-security-limit-login-attempts-locked-database

下载源代码

Download it – spring-security-limit-login-annotation.zip (38 KB)Download it – spring-security-limit-login-xml.zip (32 KB)

参考

  1. StackOverflow:如何在 Spring Security 中限制登录尝试?
  2. Spring 官方参考:核心服务-authenticationProvider
  3. Spring Security 自定义登录表单注释示例
  4. Spring DaoAuthenticationProvider JavaDoc
  5. Spring JdbcDaoImpl JavaDoc

login spring security

Spring 安全注销示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-logout-example/

在 Spring Security 中,要注销,只需添加一个链接到 URL“j _ Spring _ Security _ logout”,例如:

 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<body>
	<h2>messages, whatever</h2>	
	<a href="<c:url value="j_spring_security_logout" />" > Logout</a>
</body>
</html> 

在 Spring security 中,声明“logout”标签,并配置“logout-success-url”属性:

 <beans:beans 
	xmlns:beans="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
	http://www.springframework.org/schema/security
	http://www.springframework.org/schema/security/spring-security-3.0.3.xsd">

	<http auto-config="true">
		<intercept-url pattern="/welcome*" access="ROLE_USER" />
		<logout logout-success-url="/welcome" />
	</http>

	<authentication-manager>
	  <authentication-provider>
	    <user-service>
		<user name="mkyong" password="password" authorities="ROLE_USER" />
	    </user-service>
	  </authentication-provider>
	</authentication-manager>

</beans:beans> 

下载源代码

Download it – Spring-Security-LogOut-Example.zip (8 KB)logout spring security (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190301115812/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Spring 安全密码哈希示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-password-hashing-example/

在本教程中,我们将向您展示如何使用BCryptPasswordEncoder散列密码并在 Spring Security 中执行登录验证。

在过去,通常情况下,我们使用 MD5 Md5PasswordEncoder或 SHA ShaPasswordEncoder哈希算法对密码进行编码……您仍然可以使用任何您喜欢的编码器,但 Spring 建议使用 BCrypt BCryptPasswordEncoder,这是一种更强的哈希算法,带有随机生成的 salt。

使用的技术:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布
  3. 春季 JDBC 3.2.3 .发布
  4. MySQL 服务器 5.6

1.查看密码编码

熟悉的旧认证PasswordEncoder界面已被弃用…

 package org.springframework.security.authentication.encoding;

//Implementation : Md5PasswordEncoder and ShaPasswordEncoder
@Deprecated
public interface PasswordEncoder { 

相反,你应该使用这个新的加密接口。

 package org.springframework.security.crypto.password;

//Implementation : BCryptPasswordEncoder
public interface PasswordEncoder { 

2.生成 BCrypt 密码

首先,散列一个密码,并将其放入数据库中,以便以后进行登录验证。这个例子使用BCryptPasswordEncoder散列一个密码“123456”。

PasswordEncoderGenerator.java

 package com.mkyong.web.controller;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class PasswordEncoderGenerator {

  public static void main(String[] args) {

	int i = 0;
	while (i < 10) {
		String password = "123456";
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
		String hashedPassword = passwordEncoder.encode(password);

		System.out.println(hashedPassword);
		i++;
	}

  }
} 

在 BCrypt 哈希算法中,每次都会生成长度为 60 的不同哈希值。

 $2a$10$EblZqNptyYvcLm/VwDCVAuBjzZOI7khzdyGPBr08PpIi0na624b8.
$2a$10$trT3.R/Nfey62eczbKEnueTcIbJXW.u1ffAo/XfyLpofwNDbEB86O
$2a$10$teJrCEnsxNT49ZpXU7n22O27aCGbVYYe/RG6/XxdWPJbOLZubLIi2
$2a$10$BHG59UT6p7bgT6U2fQ/9wOyTIdejh4Rk1vWilvl4b6ysNPdhnViUS
$2a$10$W9oRWeFmOT0bByL5fmAceucetmEYFg2yzq3e50mcu.CO7rUDb/poG
$2a$10$HApapHvDStTEwjjneMCvxuqUKVyycXZRfXMwjU0rRmaWMsjWQp/Zu
$2a$10$GYCkBzp2NlpGS/qjp5f6NOWHeF56ENAlHNuSssSJpE1MMYJevHBWO
$2a$10$gwbTCaIR/qE1uYhvEY6GG.bNDQcZuYQX9tkVwaK/aD7ZLPptC.7QC
$2a$10$5uKS72xK2ArGDgb2CwjYnOzQcOmB7CPxK6fz2MGcDBM9vJ4rUql36
$2a$10$6TajU85/gVrGUm5fv5Z8beVF37rlENohyLk3BEpZJFi6Av9JNkw9O 

每次用 BCrypt 散列一个值都会得到不同的值,这很正常,因为 salt 是随机生成的。在本教程中,我们获得第一个输出,并将其插入数据库。

3.数据库ˌ资料库

创建表并插入用户“mkyong”进行测试。

 CREATE  TABLE users (
  username VARCHAR(45) NOT NULL ,
  password VARCHAR(60) NOT NULL ,
  enabled TINYINT NOT NULL DEFAULT 1 ,
  PRIMARY KEY (username));

CREATE TABLE user_roles (
  user_role_id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(45) NOT NULL,
  role varchar(45) NOT NULL,
  PRIMARY KEY (user_role_id),
  UNIQUE KEY uni_username_role (role,username),
  KEY fk_username_idx (username),
  CONSTRAINT fk_username FOREIGN KEY (username) REFERENCES users (username));

INSERT INTO users(username,password,enabled)
VALUES ('mkyong','$2a$10$EblZqNptyYvcLm/VwDCVAuBjzZOI7khzdyGPBr08PpIi0na624b8.', true);

INSERT INTO user_roles (username, role)
VALUES ('mkyong', 'ROLE_USER');
INSERT INTO user_roles (username, role)
VALUES ('mkyong', 'ROLE_ADMIN'); 

4.启用密码编码器

在 XML 配置中启用密码编码器的几种方法。

4.1 使用默认的 BCryptPasswordEncoder

spring-security.xml

 <authentication-manager>
	<authentication-provider>
	    <password-encoder hash="bcrypt" />
	</authentication-provider>
  </authentication-manager> 

4.2 向 BCryptPasswordEncoder 传递一个“强度”参数。

spring-security.xml

 <authentication-manager>
	<authentication-provider>
	    <password-encoder ref="encoder" />
	</authentication-provider>
  </authentication-manager>

  <beans:bean id="encoder" 
	class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
	<beans:constructor-arg name="strength" value="11" />
  </beans:bean> 

4.3 将编码器传递给DaoAuthenticationProvider

spring-security.xml

 <bean id="authProvider" 
	class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
	<property name="userDetailsService" ref="customUserService" />
	<property name="passwordEncoder" ref="encoder" />
  </bean>

  <bean id="encoder" 
	class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/> 

4.4 注释示例。

 @Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	DataSource dataSource;

	@Autowired
	public void configAuthentication(AuthenticationManagerBuilder auth) 
		throws Exception {

		auth.jdbcAuthentication().dataSource(dataSource)
			.passwordEncoder(passwordEncoder())
			.usersByUsernameQuery("sql...")
			.authoritiesByUsernameQuery("sql...");
	}	

	@Bean
	public PasswordEncoder passwordEncoder(){
		PasswordEncoder encoder = new BCryptPasswordEncoder();
		return encoder;
	} 

5.项目演示

访问密码保护页面:localhost:8080/spring-security-password-hashing/admin,显示登录页面。输入密码“123456”,Spring Security 将对密码进行哈希处理,并与数据库中的哈希密码进行比较。

spring-security-password-encoder

数据库中的用户和密码。

spring-security-password-encoder-database

下载源代码

Download – spring-security-password-hashing.zip(18 KB)Download – spring-security-password-hashing-annotation.zip (22 KB)

参考

  1. 维基百科:Bcrypt
  2. BCryptPasswordEncoder JavaDoc
  3. Spring 安全参考:密码编码器

Spring Security 记住我示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring-security/spring-security-remember-me-example/

spring-security-remember-me

在本教程中,我们将向您展示如何在 Spring Security 中实现“记住我”登录功能,这意味着,即使在用户的会话过期后,系统也会记住用户并执行自动登录。

使用的技术和工具:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布
  3. 春季 JDBC 3.2.3 .发布
  4. Eclipse 4.2
  5. JDK 1.6
  6. maven3
  7. MySQL 服务器 5.6
  8. Tomcat 6 和 7 (Servlet 3.x)
  9. 用谷歌浏览器测试

一些快速注释:

  1. 在 Spring Security 中,有两种实现“记住我”的方法——简单的基于哈希的令牌和持久令牌方法。
  2. 要了解“记住我”是如何工作的,请阅读这些文章-Spring remember me reference持久登录 Cookie 最佳实践改进的持久登录 Cookie 最佳实践
  3. 这个例子使用的是“持久令牌方法”,参考 Spring 的PersistentTokenBasedRememberMeServices
  4. 这个例子使用了 MySQL 和数据库认证(通过 Spring JDBC)。
  5. 将创建表“persistent_logins”来存储登录令牌和序列。

项目工作流:

  1. 如果用户登录时勾选了“记住我”,系统将在请求的浏览器中存储一个“记住我”cookie。
  2. 如果用户的浏览器提供了有效的“记住我”cookie,系统将执行自动登录。
  3. 如果用户通过“记住我”cookie 登录,要更新用户详细信息,用户需要再次键入用户名和密码(这是一种避免窃取 cookie 来更新用户信息的良好做法。

这是“记住我”应该如何工作的一个非常高的水平,详情请参考“快速笔记”中的以上链接。

1.项目演示

//web.archive.org/web/20190225101058if_/http://www.youtube.com/embed/HUnGc4WKgRk

2.项目目录

查看项目目录结构。

spring-security-remember-me-directory ## 3.MySQL 脚本

创建usersuser_rolespersistent_logins的 SQL 脚本。

 CREATE  TABLE users (
  username VARCHAR(45) NOT NULL ,
  password VARCHAR(45) NOT NULL ,
  enabled TINYINT NOT NULL DEFAULT 1 ,
  PRIMARY KEY (username));

CREATE TABLE user_roles (
  user_role_id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(45) NOT NULL,
  role varchar(45) NOT NULL,
  PRIMARY KEY (user_role_id),
  UNIQUE KEY uni_username_role (role,username),
  KEY fk_username_idx (username),
  CONSTRAINT fk_username FOREIGN KEY (username) REFERENCES users (username));

INSERT INTO users(username,password,enabled)
VALUES ('mkyong','123456', true);

INSERT INTO user_roles (username, role)
VALUES ('mkyong', 'ROLE_USER');
INSERT INTO user_roles (username, role)
VALUES ('mkyong', 'ROLE_ADMIN');

CREATE TABLE persistent_logins (
    username varchar(64) not null,
    series varchar(64) not null,
    token varchar(64) not null,
    last_used timestamp not null,
    PRIMARY KEY (series)
); 

4.记住我(XML 示例)

为了在 XML 配置中启用“记住我”,将remember-me标记放在http中,如下所示:

spring-security.xml

 <!-- enable use-expressions -->
  <http auto-config="true" use-expressions="true">
    <intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" />

    <form-login login-page="/login" 
	default-target-url="/welcome"
	authentication-failure-url="/login?error" 
	username-parameter="username"
	password-parameter="password" 
	login-processing-url="/auth/login_check"
	authentication-success-handler-ref="savedRequestAwareAuthenticationSuccessHandler" />

    <logout logout-success-url="/login?logout" delete-cookies="JSESSIONID" />
    <csrf />

    <!-- enable remember me -->
    <remember-me 
        token-validity-seconds="1209600"
	remember-me-parameter="remember-me" 
	data-source-ref="dataSource" />

  </http> 

spring-database.xml

 <bean id="dataSource"
	class="org.springframework.jdbc.datasource.DriverManagerDataSource">

	<property name="driverClassName" value="com.mysql.jdbc.Driver" />
	<property name="url" value="jdbc:mysql://localhost:3306/test" />
	<property name="username" value="root" />
	<property name="password" value="password" />
  </bean>

  <!-- If request parameter "targetUrl" is existed, then forward to this url --> 
  <!-- For update login form -->
  <bean id="savedRequestAwareAuthenticationSuccessHandler"
	class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
	<property name="targetUrlParameter" value="targetUrl" />
  </bean> 
  1. token-validity-seconds—“勿忘我”cookie 的过期日期,以秒为单位。例如,1209600 = 2 周(14 天),86400 = 1 天,18000 = 5 小时。
  2. 记住我的参数–“复选框”的名称。默认为' _spring_security_remember_me '。
  3. data-source-ref–如果指定,将使用“持久令牌方法”。默认为“简单的基于散列的令牌方法”。

5.记住我(注释示例)

相当于注释:

SecurityConfig.java

 package com.mkyong.config;

import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	DataSource dataSource;
	//...

	@Override
	protected void configure(HttpSecurity http) throws Exception {

	  http.authorizeRequests()
	      .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
	    .and()
	      .formLogin()
	        .successHandler(savedRequestAwareAuthenticationSuccessHandler())
		.loginPage("/login")
	        .failureUrl("/login?error")
		.loginProcessingUrl("/auth/login_check")
		.usernameParameter("username")
		.passwordParameter("password")
	    .and()
		.logout().logoutSuccessUrl("/login?logout")
	    .and()
	        .csrf()
	    .and()
		.rememberMe().tokenRepository(persistentTokenRepository())
		.tokenValiditySeconds(1209600);
	}

	@Bean
	public PersistentTokenRepository persistentTokenRepository() {
		JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
		db.setDataSource(dataSource);
		return db;
	}

	@Bean
	public SavedRequestAwareAuthenticationSuccessHandler 
                savedRequestAwareAuthenticationSuccessHandler() {

               SavedRequestAwareAuthenticationSuccessHandler auth 
                    = new SavedRequestAwareAuthenticationSuccessHandler();
		auth.setTargetUrlParameter("targetUrl");
		return auth;
	}	

} 

P.S 在批注配置中,“记住我”复选框的默认 http 名称是“记住我”。

6.HTML/JSP 页面

6.1 在 JSP 中,您可以使用 Spring 安全标签sec:authorize access="isRememberMe()"来确定该用户是否通过“记住我”cookies 登录。

admin.jsp

 <%@taglib prefix="sec"
	uri="http://www.springframework.org/security/tags"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<body>
	<h1>Title : ${title}</h1>
	<h1>Message : ${message}</h1>

	<c:url value="/j_spring_security_logout" var="logoutUrl" />
	<form action="${logoutUrl}" method="post" id="logoutForm">
		<input type="hidden" name="${_csrf.parameterName}"
			value="${_csrf.token}" />
	</form>
	<script>
		function formSubmit() {
			document.getElementById("logoutForm").submit();
		}
	</script>

	<c:if test="${pageContext.request.userPrincipal.name != null}">
	  <h2>
		Welcome : ${pageContext.request.userPrincipal.name} | <a
			href="javascript:formSubmit()"> Logout</a>
	  </h2>
	</c:if>

	<sec:authorize access="isRememberMe()">
		<h2># This user is login by "Remember Me Cookies".</h2>
	</sec:authorize>

	<sec:authorize access="isFullyAuthenticated()">
		<h2># This user is login by username / password.</h2>
	</sec:authorize>

</body>
</html> 

6.2 带有“记住我”复选框的简单登录表单。

login.jsp

 <form name='loginForm'
	action="<c:url value='/auth/login_check?targetUrl=${targetUrl}' />"
	method='POST'>

	<table>
	<tr>
		<td>User:</td>
		<td><input type='text' name='username'></td>
	</tr>
	<tr>
		<td>Password:</td>
		<td><input type='password' name='password' /></td>
	</tr>

	<!-- if this is login for update, ignore remember me check -->
	<c:if test="${empty loginUpdate}">
	<tr>
		<td></td>
		<td>Remember Me: <input type="checkbox" name="remember-me" /></td>
	</tr>
	</c:if>

	<tr>
	        <td colspan='2'><input name="submit" type="submit"
		value="submit" /></td>
	</tr>

	</table>

	<input type="hidden" name="${_csrf.parameterName}"
		value="${_csrf.token}" />

  </form> 

6.3 更新页面。只有使用密码登录的用户才能访问此页面。

update.jsp

 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<body>
	<h1>Title : Spring Security Remember Me Example - Update Form</h1>
	<h1>Message : This page is for ROLE_ADMIN and fully authenticated only 
            (Remember me cookie is not allowed!)</h1>

	<h2>Update Account Information...</h2>
</body>
</html> 

7.控制器

Spring 控制器类,阅读注释不言自明。

MainController.java

 package com.mkyong.web.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class MainController {

	@RequestMapping(value = { "/", "/welcome**" }, method = RequestMethod.GET)
	public ModelAndView defaultPage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Remember Me");
		model.addObject("message", "This is default page!");
		model.setViewName("hello");
		return model;

	}

	@RequestMapping(value = "/admin**", method = RequestMethod.GET)
	public ModelAndView adminPage() {

		ModelAndView model = new ModelAndView();
		model.addObject("title", "Spring Security Remember Me");
		model.addObject("message", "This page is for ROLE_ADMIN only!");
		model.setViewName("admin");

		return model;

	}

	/**
	 * This update page is for user login with password only.
	 * If user is login via remember me cookie, send login to ask for password again.
	 * To avoid stolen remember me cookie to update info
	 */
	@RequestMapping(value = "/admin/update**", method = RequestMethod.GET)
	public ModelAndView updatePage(HttpServletRequest request) {

		ModelAndView model = new ModelAndView();

		if (isRememberMeAuthenticated()) {
			//send login for update
			setRememberMeTargetUrlToSession(request);
			model.addObject("loginUpdate", true);
			model.setViewName("/login");

		} else {
			model.setViewName("update");
		}

		return model;

	}

	/**
	 * both "normal login" and "login for update" shared this form.
	 * 
	 */
	@RequestMapping(value = "/login", method = RequestMethod.GET)
	public ModelAndView login(@RequestParam(value = "error", required = false) String error,
	  @RequestParam(value = "logout", required = false) String logout, 
          HttpServletRequest request) {

		ModelAndView model = new ModelAndView();
		if (error != null) {
			model.addObject("error", "Invalid username and password!");

			//login form for update page
                        //if login error, get the targetUrl from session again.
			String targetUrl = getRememberMeTargetUrlFromSession(request);
			System.out.println(targetUrl);
			if(StringUtils.hasText(targetUrl)){
				model.addObject("targetUrl", targetUrl);
				model.addObject("loginUpdate", true);
			}

		}

		if (logout != null) {
			model.addObject("msg", "You've been logged out successfully.");
		}
		model.setViewName("login");

		return model;

	}

	/**
	 * Check if user is login by remember me cookie, refer
	 * org.springframework.security.authentication.AuthenticationTrustResolverImpl
	 */
	private boolean isRememberMeAuthenticated() {

		Authentication authentication = 
                    SecurityContextHolder.getContext().getAuthentication();
		if (authentication == null) {
			return false;
		}

		return RememberMeAuthenticationToken.class.isAssignableFrom(authentication.getClass());
	}

	/**
	 * save targetURL in session
	 */
	private void setRememberMeTargetUrlToSession(HttpServletRequest request){
		HttpSession session = request.getSession(false);
		if(session!=null){
			session.setAttribute("targetUrl", "/admin/update");
		}
	}

	/**
	 * get targetURL from session
	 */
	private String getRememberMeTargetUrlFromSession(HttpServletRequest request){
		String targetUrl = "";
		HttpSession session = request.getSession(false);
		if(session!=null){
			targetUrl = session.getAttribute("targetUrl")==null?""
                             :session.getAttribute("targetUrl").toString();
		}
		return targetUrl;
	}

} 

8.演示

8.1 访问受保护页面-http://localhost:8080/spring-security-remember-me/admin,系统会将用户重定向到登录表单。尝试在选中“记住我”的情况下登录。

spring-security-remember-me-example-0spring-security-remember-me-example-2

8.2 在 Google Chrome 中,设置->显示高级设置->隐私、内容设置… ->“所有 cookie 和站点数据”——有两个 cookie 用于本地主机,一个用于当前会话,一个用于“记住我”登录 cookie。

spring-security-remember-me-example-1

8.3 审查表“持久登录”,用户名、系列和令牌已存储。

spring-security-remember-me-table

8.4 重启 web 应用,去 Chrome“所有 cookies 和站点数据”,移除浏览器的 session“JSESSIONID”。再次尝试访问登录页面。现在,系统将“记住你”,并通过浏览器中的登录 cookies 自动登录。

spring-security-remember-me-example-3

8.5 尝试访问“更新”页面-http://localhost:8080/spring-security-remember-me/admin/update,如果用户通过“记住我”cookies 登录,系统会将用户重新重定向到登录表单。这是一个很好的做法,可以避免通过窃取 cookie 来更新用户详细信息。

spring-security-remember-me-example-4

8.6 搞定。

spring-security-remember-me-example-5

9.混杂的

要学习的一些重要的 Spring 安全类:

  1. org . spring framework . security . config . annotation . web . configurers . remember me configurer . Java
  2. org . spring framework . security . web . authentic ation . remember me . abstractrememberservices . Java
  3. org . spring framework . security . web . authentic ation . remember me . persistenttokenbasedrememberservices . Java
  4. org . spring framework . security . web . authentic ation . remember me . tokenbasedremembermeservices . Java
  5. org . spring framework . security . web . authentic ation . remember me . remembermeauthenticationfilter

下载源代码

Download it – spring-security-remember-me.zip (18 KB)Download it – spring-security-remember-me-annotation.zip (25 KB)

参考

  1. 春安记得我参考资料
  2. 持久登录 Cookie 最佳实践
  3. 改进的持久登录 Cookie 最佳实践
  4. 对于一个网站来说,实现“记住我”的最好方法是什么?
  5. Spring JDBC template JavaDoc
  6. 使用数据库的 Spring 安全表单登录–XML 和注释示例

login form remember me spring security

spring Security–没有为 id“null”映射的 PasswordEncoder

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring-boot/spring-security-there-is-no-passwordencoder-mapped-for-the-id-null/

发送带有用户名和密码的 GET 请求,但遇到密码编码器错误?

测验

  • Spring Boot 2.1.2 .版本
  • 发布
 $ curl localhost:8080/books -u user:password

{
	"timestamp":"2019-02-22T15:03:49.322+0000",
	"status":500,
	"error":"Internal Server Error",
	"message":"There is no PasswordEncoder mapped for the id \"null\"",
	"path":"/books"
} 

日志中的错误

 java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" 

下面是配置。

SpringSecurityConfig.java

 package com.mkyong.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                .withUser("user").password("password").roles("USER")
                .and()
                .withUser("admin").password("password").roles("ADMIN");

    }

} 

解决办法

在 Spring Security 5.0 之前,默认的PasswordEncoderNoOpPasswordEncoder,需要明文密码。在 Spring Security 5 中,默认为DelegatingPasswordEncoder,需要密码存储格式

读作这个这个

解决方案 1–添加密码存储格式,对于纯文本,添加{noop}

SpringSecurityConfig.java

 package com.mkyong.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.inMemoryAuthentication()
                .withUser("user").password("{noop}password").roles("USER")
                .and()
                .withUser("admin").password("{noop}password").roles("ADMIN");

    }

} 

解决方案二UserDetailsServiceUser.withDefaultPasswordEncoder()

SpringSecurityConfig.java

 package com.mkyong.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService() {

        User.UserBuilder users = User.withDefaultPasswordEncoder();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(users.username("user").password("password").roles("USER").build());
        manager.createUser(users.username("admin").password("password").roles("USER", "ADMIN").build());
        return manager;

    }

} 

参考

春季安全教程

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/tutorials/spring-security-tutorials/

spring security tutorials

Spring Security 是一个灵活而强大的认证和访问控制框架,用于保护基于 Spring 的 Java web 应用程序。

本教程中使用的 Spring 版本:

  1. 弹簧 3.2.8 释放
  2. Spring Security 3.2.3 .发布

Note
Try this Spring Boot + Spring Security + Thymeleaf example

1.Spring 安全示例

向您展示如何使用 Spring Security 保护您的 web 应用程序的示例。

2.常见问题

春季安全中的一些常见问题。

一些过时或废弃的文章…可能会在未来更新。

参考

  1. 春安官方页面
  2. Spring Security 3.2.x 参考
  3. 使用 Spring Security 进行自定义认证

spring–使用 MailSender 通过 Gmail SMTP 服务器发送电子邮件

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring/spring-sending-e-mail-via-gmail-smtp-server-with-mailsender/

Spring 附带了一个有用的'org . spring framework . mail . JavaMail . javamailsenderimpl'类来简化通过 JavaMail API 发送电子邮件的过程。这里有一个 Maven 构建项目,使用 Spring 的“ JavaMailSenderImpl 通过 Gmail SMTP 服务器发送电子邮件。

1.项目依赖性

添加 JavaMail 和 Spring 的依赖项。

文件:pom.xml

 <project  
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
  http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mkyong.common</groupId>
  <artifactId>SpringExample</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>SpringExample</name>
  <url>http://maven.apache.org</url>

  <repositories>
  	<repository>
  		<id>Java.Net</id>
  		<url>http://download.java.net/maven/2/</url>
  	</repository>
  </repositories>

  <dependencies>

    <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>3.8.1</version>
             <scope>test</scope>
    </dependency>

    <!-- Java Mail API -->
    <dependency>
	    <groupId>javax.mail</groupId>
	    <artifactId>mail</artifactId>
	    <version>1.4.3</version>
    </dependency>

    <!-- Spring framework -->
    <dependency>
     	    <groupId>org.springframework</groupId>
	    <artifactId>spring</artifactId>
	    <version>2.5.6</version>
    </dependency>

  </dependencies>
</project> 

2.春天的邮件发送者

用 Spring 的 MailSender 接口发送电子邮件的 Java 类。

文件:MailMail.java

 package com.mkyong.common;

import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;

public class MailMail
{
	private MailSender mailSender;

	public void setMailSender(MailSender mailSender) {
		this.mailSender = mailSender;
	}

	public void sendMail(String from, String to, String subject, String msg) {

		SimpleMailMessage message = new SimpleMailMessage();

		message.setFrom(from);
		message.setTo(to);
		message.setSubject(subject);
		message.setText(msg);
		mailSender.send(message);	
	}
} 

3.Bean 配置文件

配置 mailSender bean 并指定 Gmail SMTP 服务器的电子邮件详细信息。

Note
Gmail configuration details – http://mail.google.com/support/bin/answer.py?hl=en&answer=13287

文件:Spring-Mail.xml

 <beans 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
	<property name="host" value="smtp.gmail.com" />
	<property name="port" value="587" />
	<property name="username" value="username" />
	<property name="password" value="password" />

	<property name="javaMailProperties">
	   <props>
       	      <prop key="mail.smtp.auth">true</prop>
       	      <prop key="mail.smtp.starttls.enable">true</prop>
       	   </props>
	</property>
</bean>

<bean id="mailMail" class="com.mkyong.common.MailMail">
	<property name="mailSender" ref="mailSender" />
</bean>

</beans> 

4.运行它

 package com.mkyong.common;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App 
{
    public static void main( String[] args )
    {
    	ApplicationContext context = 
             new ClassPathXmlApplicationContext("Spring-Mail.xml");

    	MailMail mm = (MailMail) context.getBean("mailMail");
        mm.sendMail("from@no-spam.com",
    		   "to@no-spam.com",
    		   "Testing123", 
    		   "Testing only \n\n Hello Spring Email Sender");

    }
} 

下载源代码

Download it – Spring-Email-Gmail-Smtp-Example.zipTags : email gmail smtp spring

spring–发送带附件的电子邮件

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring/spring-sending-e-mail-with-attachment/

这里有一个使用 Spring 通过 Gmail SMTP 服务器发送带有附件的电子邮件的例子。为了在你的电子邮件中包含附件,你必须使用 Spring 的Java mail sender&mime message,而不是mail sender&simple mail message

1.项目依赖性

添加 JavaMail 和 Spring 的依赖项。

文件:pom.xml

 <project  
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
  http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mkyong.common</groupId>
  <artifactId>SpringExample</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>SpringExample</name>
  <url>http://maven.apache.org</url>

  <repositories>
  	<repository>
  		<id>Java.Net</id>
  		<url>http://download.java.net/maven/2/</url>
  	</repository>
  </repositories>

  <dependencies>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.1</version>
        <scope>test</scope>
    </dependency>

    <!-- Java Mail API -->
    <dependency>
	    <groupId>javax.mail</groupId>
	    <artifactId>mail</artifactId>
	    <version>1.4.3</version>
    </dependency>

    <!-- Spring framework -->
    <dependency>
     	<groupId>org.springframework</groupId>
	    <artifactId>spring</artifactId>
	    <version>2.5.6</version>
    </dependency>

  </dependencies>
</project> 

freestar.config.enabled_slots.push({ placementName: "mkyong_incontent_1", slotId: "mkyong_incontent_1" });

2.春天的邮件发送者

你要用 JavaMailSender 代替 MailSender 发送附件,用 MimeMessageHelper 附加资源。在本例中,它将从您的文件系统(FileSystemResource)中获取“c:\log.txt”文本文件作为电子邮件附件。

除了文件系统,你还可以从 URL path( UrlResource )、class path(class path resource)、InputStream(InputStream resource)……请参考 Spring 的 AbstractResource 实现类。

文件:MailMail.java

 package com.mkyong.common;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.MailParseException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

public class MailMail
{
	private JavaMailSender mailSender;
	private SimpleMailMessage simpleMailMessage;

	public void setSimpleMailMessage(SimpleMailMessage simpleMailMessage) {
		this.simpleMailMessage = simpleMailMessage;
	}

	public void setMailSender(JavaMailSender mailSender) {
		this.mailSender = mailSender;
	}

	public void sendMail(String dear, String content) {

	   MimeMessage message = mailSender.createMimeMessage();

	   try{
		MimeMessageHelper helper = new MimeMessageHelper(message, true);

		helper.setFrom(simpleMailMessage.getFrom());
		helper.setTo(simpleMailMessage.getTo());
		helper.setSubject(simpleMailMessage.getSubject());
		helper.setText(String.format(
			simpleMailMessage.getText(), dear, content));

		FileSystemResource file = new FileSystemResource("C:\\log.txt");
		helper.addAttachment(file.getFilename(), file);

	     }catch (MessagingException e) {
		throw new MailParseException(e);
	     }
	     mailSender.send(message);
         }
} 

3.Bean 配置文件

配置 mailSender bean、电子邮件模板,并指定 Gmail SMTP 服务器的电子邮件详细信息。

文件:Spring-Mail.xml

 <beans 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
	<property name="host" value="smtp.gmail.com" />
	<property name="port" value="587" />
	<property name="username" value="username" />
	<property name="password" value="password" />

	<property name="javaMailProperties">
		<props>
           	<prop key="mail.smtp.auth">true</prop>
           	<prop key="mail.smtp.starttls.enable">true</prop>
       	</props>
	</property>
</bean>

<bean id="mailMail" class="com.mkyong.common.MailMail">
	<property name="mailSender" ref="mailSender" />
	<property name="simpleMailMessage" ref="customeMailMessage" />
</bean>

<bean id="customeMailMessage"
	class="org.springframework.mail.SimpleMailMessage">

	<property name="from" value="from@no-spam.com" />
	<property name="to" value="to@no-spam.com" />
	<property name="subject" value="Testing Subject" />
	<property name="text">
	<value>
		<![CDATA[
			Dear %s,
			Mail Content : %s
		]]>
	</value>
    </property>
</bean>

</beans> 

4.运行它

 package com.mkyong.common;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App 
{
    public static void main( String[] args )
    {
    	ApplicationContext context = 
            new ClassPathXmlApplicationContext("Spring-Mail.xml");

    	MailMail mm = (MailMail) context.getBean("mailMail");
        mm.sendMail("Yong Mook Kim", "This is text content");

    }
} 

输出

 Dear Yong Mook Kim,
 Mail Content : This is text content

 Attachment : log.txt 

下载源代码

Download it – Spring-Email-Attachment-Example.zipTags : email springfreestar.config.enabled_slots.push({ placementName: "mkyong_leaderboard_btf", slotId: "mkyong_leaderboard_btf" });

Spring SetFactoryBean 示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring/spring-setfactorybean-example/

' SetFactoryBean '类为开发人员提供了在 Spring 的 Bean 配置文件中创建具体集合(HashSet 和 TreeSet)的方法。

这里有一个 ListFactoryBean 示例,它将在运行时实例化一个 HashSet,并将其注入到一个 Bean 属性中

 package com.mkyong.common;

import java.util.Set;

public class Customer 
{
	private Set sets;
	//...
} 

Spring 的 bean 配置文件。

 <beans 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<bean id="CustomerBean" class="com.mkyong.common.Customer">
		<property name="sets">
			<bean class="org.springframework.beans.factory.config.SetFactoryBean">
				<property name="targetSetClass">
					<value>java.util.HashSet</value>
				</property>
				<property name="sourceSet">
					<list>
						<value>1</value>
						<value>2</value>
						<value>3</value>
					</list>
				</property>
			</bean>
		</property>
	</bean>

</beans> 

或者,您也可以使用 util schema 和来实现同样的事情。

 <beans 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://www.springframework.org/schema/util
	http://www.springframework.org/schema/util/spring-util-2.5.xsd">

	<bean id="CustomerBean" class="com.mkyong.common.Customer">
		<property name="sets">
			<util:set set-class="java.util.HashSet">
				<value>1</value>
				<value>2</value>
				<value>3</value>
			</util:set>
		</property>
	</bean>

</beans> 

记得包括 util 模式,否则您将遇到以下错误

 Caused by: org.xml.sax.SAXParseException: 
	The prefix "util" for element "util:set" is not bound. 

运行它…

 package com.mkyong.common;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App 
{
    public static void main( String[] args )
    {
    	ApplicationContext context = new ClassPathXmlApplicationContext("SpringBeans.xml");

    	Customer cust = (Customer)context.getBean("CustomerBean");
    	System.out.println(cust);

    }
} 

输出

 Customer [sets=[3, 2, 1]] Type=[class java.util.HashSet] 

您已经实例化了 HashSet 和,并在运行时将其注入到 Customer 的 sets 属性中。

下载源代码

Download It – Spring-SetFactoryBean-Example.zip (5KB) ## 参考

  1. SetFactoryBean Javadoc

spring (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190225101247/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

spring simple JDBC template batch update()示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring/spring-simplejdbctemplate-batchupdate-example/

在本教程中,我们将向您展示如何在 SimpleJdbcTemplate 类中使用batchUpdate()

参见 SimpleJdbcTemplate 类中的batchUpdate()示例。

 //insert batch example
public void insertBatch(final List<Customer> customers){
	String sql = "INSERT INTO CUSTOMER " +
		"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";

	List<Object[]> parameters = new ArrayList<Object[]>();

	for (Customer cust : customers) {
        parameters.add(new Object[] {cust.getCustId(), 
            cust.getName(), cust.getAge()}
        );
    }
    getSimpleJdbcTemplate().batchUpdate(sql, parameters);        
} 

或者,您可以直接执行 SQL。

 //insert batch example with SQL
public void insertBatchSQL(final String sql){

	getJdbcTemplate().batchUpdate(new String[]{sql});

} 

Spring 的 bean 配置文件

 <beans 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<bean id="customerSimpleDAO" 
        class="com.mkyong.customer.dao.impl.SimpleJdbcCustomerDAO">

		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="dataSource" 
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">

		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/mkyongjava" />
		<property name="username" value="root" />
		<property name="password" value="password" />
	</bean>

</beans> 

运行它

 package com.mkyong.common;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mkyong.customer.dao.CustomerDAO;
import com.mkyong.customer.model.Customer;

public class App 
{
    public static void main( String[] args )
    {
    	ApplicationContext context = 
    		new ClassPathXmlApplicationContext("Spring-Customer.xml");

        CustomerDAO customerSimpleDAO = 
                      (CustomerDAO) context.getBean("customerSimpleDAO");

        Customer customer1 = new Customer(1, "mkyong1",21);
        Customer customer3 = new Customer(2, "mkyong2",22);
        Customer customer2 = new Customer(3, "mkyong3",23);

        List<Customer>customers = new ArrayList<Customer>();
        customers.add(customer1);
        customers.add(customer2);
        customers.add(customer3);

        customerSimpleDAO.insertBatch(customers);

        String sql = "UPDATE CUSTOMER SET NAME ='BATCHUPDATE'";
        customerSimpleDAO.insertBatchSQL(sql);

    }
} 

在本例中,您将插入三个客户的记录,并批量更新所有客户的姓名。

下载源代码

Download it – Spring-SimpleJdbcTemplate-batchUpdate-Example.zip (15 KB)jdbc spring (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190310101928/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Spring SimpleJdbcTemplate 查询示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/spring/spring-simplejdbctemplate-querying-examples/

这里有几个例子来展示如何使用 SimpleJdbcTemplate query()方法从数据库中查询或提取数据。在 JdbcTemplate query()中,您需要手动将返回的结果转换为所需的对象类型,并传递一个对象数组作为参数。在 SimpleJdbcTemplate 中,它更加用户友好和简单。

jdbctemplate vesus simplejdbctemplate
Please compare this SimpleJdbcTemplate example with this JdbcTemplate example.

1.查询单行

这里有两种方法向您展示如何从数据库中查询或提取单个行,并将其转换为模型类。

1.1 自定义行映射器

一般来说,总是建议实现 RowMapper 接口来创建自定义的 RowMapper 以满足您的需求。

 package com.mkyong.customer.model;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

public class CustomerRowMapper implements RowMapper
{
	public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
		Customer customer = new Customer();
		customer.setCustId(rs.getInt("CUST_ID"));
		customer.setName(rs.getString("NAME"));
		customer.setAge(rs.getInt("AGE"));
		return customer;
	}

} 
 public Customer findByCustomerId(int custId){

	String sql = "SELECT * FROM CUSTOMER WHERE CUST_ID = ?";

	Customer customer = getSimpleJdbcTemplate().queryForObject(
			sql,  new CustomerParameterizedRowMapper(), custId);

	return customer;
} 

1.2 BeanPropertyRowMapper

在 SimpleJdbcTemplate 中,需要使用“ParameterizedBeanPropertyRowMapper”而不是“BeanPropertyRowMapper”。

 public Customer findByCustomerId2(int custId){

	String sql = "SELECT * FROM CUSTOMER WHERE CUST_ID = ?";

	Customer customer = getSimpleJdbcTemplate().queryForObject(sql,
          ParameterizedBeanPropertyRowMapper.newInstance(Customer.class), custId);

	return customer;
} 

2.查询多行

从数据库中查询或提取多行,并将其转换为列表。

2.1 ParameterizedBeanPropertyRowMapper

 public List<Customer> findAll(){

	String sql = "SELECT * FROM CUSTOMER";

	List<Customer> customers = 
		getSimpleJdbcTemplate().query(sql, 
		   ParameterizedBeanPropertyRowMapper.newInstance(Customer.class));

	return customers;
} 

3.查询单个值

从数据库中查询或提取单个列值。

3.1 单列名称

它展示了如何以字符串形式查询单个列名。

 public String findCustomerNameById(int custId){

	String sql = "SELECT NAME FROM CUSTOMER WHERE CUST_ID = ?";

	String name = getSimpleJdbcTemplate().queryForObject(
		sql, String.class, custId);

	return name;

} 

3.2 总行数

它展示了如何从数据库中查询总行数。

 public int findTotalCustomer(){

	String sql = "SELECT COUNT(*) FROM CUSTOMER";

	int total = getSimpleJdbcTemplate().queryForInt(sql);

	return total;
} 

运行它

 package com.mkyong.common;

import java.util.ArrayList;
import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mkyong.customer.dao.CustomerDAO;
import com.mkyong.customer.model.Customer;

public class SimpleJdbcTemplateApp 
{
    public static void main( String[] args )
    {
    	 ApplicationContext context = 
    		new ClassPathXmlApplicationContext("Spring-Customer.xml");

         CustomerDAO customerSimpleDAO = 
                (CustomerDAO) context.getBean("customerSimpleDAO");

         Customer customerA = customerSimpleDAO.findByCustomerId(1);
         System.out.println("Customer A : " + customerA);

         Customer customerB = customerSimpleDAO.findByCustomerId2(1);
         System.out.println("Customer B : " + customerB);

         List<Customer> customerAs = customerSimpleDAO.findAll();
         for(Customer cust: customerAs){
         	 System.out.println("Customer As : " + customerAs);
         }

         List<Customer> customerBs = customerSimpleDAO.findAll2();
         for(Customer cust: customerBs){
         	 System.out.println("Customer Bs : " + customerBs);
         }

         String customerName = customerSimpleDAO.findCustomerNameById(1);
         System.out.println("Customer Name : " + customerName);

         int total = customerSimpleDAO.findTotalCustomer();
         System.out.println("Total : " + total);

    }
} 

结论

SimpleJdbcTemplate 不是 JdbcTemplate 的替代品,它只是 java5 友好的补充。

下载源代码

Download it – Spring-SimpleJdbcTemplate-Querying-Example.zip (15 KB)jdbc spring

spring Test——如何在 jsonPath 中测试 JSON 数组

原文:http://web.archive.org/web/20230101150211/https://www.mkyong.com/spring-boot/spring-test-how-to-test-a-json-array-in-jsonpath/

在 Spring 中,我们可以使用像hasItemhasSize这样的 Hamcrest APIs 来测试 JSON 数组。查看以下示例:

 package com.mkyong;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookRepository mockRepository;

    /*
        {
            "timestamp":"2019-03-05T09:34:13.280+0000",
            "status":400,
            "errors":["Author is not allowed.","Please provide a price","Please provide a author"]
        }
     */
    //article : jsonpath in array
    @Test
    public void save_emptyAuthor_emptyPrice_400() throws Exception {

        String bookInJson = "{\"name\":\"ABC\"}";

        mockMvc.perform(post("/books")
                .content(bookInJson)
                .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.timestamp", is(notNullValue())))
                .andExpect(jsonPath("$.status", is(400)))
                .andExpect(jsonPath("$.errors").isArray())
                .andExpect(jsonPath("$.errors", hasSize(3)))
                .andExpect(jsonPath("$.errors", hasItem("Author is not allowed.")))
                .andExpect(jsonPath("$.errors", hasItem("Please provide a author")))
                .andExpect(jsonPath("$.errors", hasItem("Please provide a price")));

        verify(mockRepository, times(0)).save(any(Book.class));

    }

} 

用 Spring Boot 2 号进行了测试

参考

Tags : json json array spring test unit test

春季教程

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/tutorials/spring-tutorials/

Spring tutorials

Rod Johnson 创建的 Spring framework 是一个非常强大的控制反转(IoC)框架,可以帮助分离项目组件的依赖关系。

在这一系列教程中,它提供了许多关于使用 Spring 框架的分步示例和解释。

New Spring 3.0 Tutorials (23/06/2011)
Added many Spring 3.0 tutorials on using Spring EL, JavaConfig, AspectJ and Spring Object/XML mapping (oxm). For what’s new in Spring 3.0, you can refer to this official Spring 3.0 references.

弹簧快速启动

快速了解 Spring 框架开发的基础。

Spring JavaConfig (Spring 3.0)

Spring 3.0 支持 JavaConfig,现在您可以使用注释在 Spring 中进行配置。

弹簧依赖注入(DI)

Spring 如何做依赖注入(DI)来管理对象依赖?

  • Spring 依赖注入(DI)
    Spring 如何通过 Setter 注入和 Constructor 注入来应用依赖注入(DI)设计模式。
  • Spring DI via setter 方法
    依赖注入一个 bean via setter 方法。
  • Spring DI via 构造函数
    依赖注入 bean via 构造函数。
  • Spring 中的构造函数注入类型不明确
    构造函数注入参数类型不明确的问题总是发生在包含多个具有许多参数的构造函数方法的 bean 中。

基本豆

您需要在 Spring Ioc 容器中使用的所有类都被认为是“bean ”,并在 Spring bean 配置文件中或通过注释进行声明。

Spring 表达式语言(Spring 3.0)

Spring 3.0 引入了功能丰富而强大的表达式语言,称为 Spring expression language,或 Spring EL。

Spring 自动组件扫描

Spring 能够自动扫描、检测和注册您的 bean。

弹簧自动布线 Bean

Spring 的“自动连接”模式可以在 XML 和注释中自动连接或连接 beans。

Spring AOP(面向方面编程)

Spring AOP 模块化了方面中的横切关注点。简单来说,一个拦截器来拦截一些方法。

Spring AOP + AspectJ 框架

AspectJ 从 Spring 2.0 开始支持,更加灵活和强大。然而,这个例子是在 Spring 3.0 中演示的。

Spring 对象/XML 映射器(Spring 3.0)

在 Spring 3.0 中,对象到 XML 映射(OXM)从 Spring Web 服务转移到了核心的 Spring 框架。

弹簧 JDBC 支架

Spring 提供了许多助手类来简化整个 JDBC 数据库操作。

弹簧休眠支持

Spring 附带了许多方便的类来支持 Hibernate ORM 框架。

Spring 电子邮件支持

Spring 提供了 MailSender 来通过 JavaMail API 发送电子邮件。

春季调度支持

Spring 在 JDK 计时器和石英框架中都有很好的支持。

将 Spring 与其他 Web 框架集成

Spring 集成了其他 web 框架。

春季常见问题

Spring 常见错误

一些 Spring 常见的错误消息。

弹簧参考

Spring @Value 默认值

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring3/spring-value-default-value/

在本教程中,我们将向您展示如何为@Value设置默认值

1.@价值示例

要在弹簧表达式中设置默认值,使用Elvis operator:

 #{expression?:default value} 

几个例子:

 @Value("#{systemProperties['mongodb.port'] ?: 27017}")
	private String mongodbPort;

	@Value("#{config['mongodb.url'] ?: '127.0.0.1'}")
	private String mongodbUrl;	

	@Value("#{aBean.age ?: 21}")
	private int age; 

P.S @Value自春季 3.0 开始发售

2.@值和属性示例

要设置属性占位符的默认值:

 ${property:default value} 

几个例子:

 //@PropertySource("classpath:/config.properties}")
	//@Configuration

	@Value("${mongodb.url:127.0.0.1}")
	private String mongodbUrl;

	@Value("#{'${mongodb.url:172.0.0.1}'}")
	private String mongodbUrl;

	@Value("#{config['mongodb.url']?:'127.0.0.1'}")
	private String mongodbUrl; 

config.properties

 mongodb.url=1.2.3.4
mongodb.db=hello 

对于“配置”bean。

 <util:properties id="config" location="classpath:config.properties"/> 

跟进
必须在 XML 或注释中注册一个静态PropertySourcesPlaceholderConfigurer bean,以便 Spring @Value知道如何解释${}

 //@PropertySource("classpath:/config.properties}")
  //@Configuration

  @Bean
  public static PropertySourcesPlaceholderConfigurer propertyConfigIn() {
	return new PropertySourcesPlaceholderConfigurer();
  } 

参考

  1. 春埃尔的表情

spring spring3

Spring WebFlux 测试——阻塞读取超时 5000 毫秒

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/spring-boot/spring-webflux-test-timeout-on-blocking-read-for-5000-milliseconds/

WebTestClient测试一个 Spring Webflux 端点,并显示以下错误消息。这有可能增加超时时间吗?

 java.lang.IllegalStateException: Timeout on blocking read for 5000 MILLISECONDS

	at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:117)
	at reactor.core.publisher.Mono.block(Mono.java:1524)
	at org.springframework.test.web.reactive.server.ExchangeResult.formatBody(ExchangeResult.java:247)
	at org.springframework.test.web.reactive.server.ExchangeResult.toString(ExchangeResult.java:216)
	at java.base/java.lang.String.valueOf(String.java:2788)
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:135)
	at org.springframework.test.web.reactive.server.ExchangeResult.assertWithDiagnostics(ExchangeResult.java:200) 

解决办法

默认情况下,WebTestClient会在 5 秒后超时。我们可以用@AutoConfigureWebTestClient配置超时

 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient(timeout = "10000")//10 seconds
public class TestCommentWebApplication {

    @Autowired
    private WebTestClient webClient; 

参考

unit test webflux (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190308021305/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Java 8–将可选的转换为字符串

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/java8/java-8-convert-optionalstring-to-string/

在 Java 8 中,我们可以使用.map(Object::toString)将一个Optional<String>转换成一个String

 String result = list.stream()
              .filter(x -> x.length() == 1)
              .findFirst()  // returns Optional
              .map(Object::toString)
              .orElse(""); 

样品

获得一个值的标准方法。

Java8Example1.java

 package com.mkyong;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class Java8Example1 {

    public static void main(String[] args) {

        List<String> list = Arrays.asList("a", "b", "c", "d", "e");

        Optional<String> result = list.stream()
          .filter(x -> x.length() == 1)
          .findFirst();

        if (result.isPresent()) {
            System.out.println(result.get()); // a
        }

    }

} 

输出

 a 

尝试map(Object::toString)

Java8Example2.java

 package com.mkyong;

import java.util.Arrays;
import java.util.List;

public class Java8Example2 {

    public static void main(String[] args) {

        List<String> list = Arrays.asList("a", "b", "c", "d", "e");

        String s = list.stream().filter(x -> x.length() == 1)
                .findFirst()
                .map(Object::toString)
                .orElse(null);

        System.out.println(s); // a
    }

} 

输出

 a 

参考

Struts 2 标签示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-a-tag-example/

Download It – Struts2-A-Tag-Example.zip

Struts 2 " 一个"标签用于呈现一个 HTML " <一个> "标签。最佳实践是始终使用“ < s:url > ”标签来创建 url 并将其嵌入到“ a 标签中。举个例子,

 <s:url value="http://www.google.com" var="googleURL" />
<s:a href="%{googleURL}">Google</s:a> 

在本教程中,它展示了使用 Struts 2 " a 标签的 3 种方法。

1.行动

转发请求的操作类。

ATagAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class ATagAction extends ActionSupport{

	public String execute() {
		return SUCCESS;
	}

} 

2.标签示例

一个 JSP 页面,展示了使用“ a 标签以不同的方式呈现 URL。

a.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
 <html>
<head>
</head>

<body>
<h1>Struts 2 a tag example</h1>

<ol>

<li>
<s:url value="http://www.mkyong.com" var="mkyongURL" />
<s:a href="%{mkyongURL}">J2EE web development tutorials</s:a>
</li>

<li>
<s:a href="http://www.google.com">Google search engine</s:a>
</li>

<li>
<s:url action="aTagAction.action" var="aURL" />
<s:a href="%{aURL}">aTagAction</s:a>
</li>

</ol>

</body>
</html> 

3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />
	<package name="default" namespace="/" extends="struts-default">

		<action name="aTagAction" 
			class="com.mkyong.common.action.aTagAction" >
			<result name="success">pages/a.jsp</result>
		</action>

	</package>
</struts> 

4.演示

http://localhost:8080/struts 2 example/atag action . action

输出

Struts 2 a tag example

在 HTML 源文件中输出

 <html> 
<head> 
</head> 

<body> 
<h1>Struts 2 a tag example</h1> 

<ol> 
<li> 
<a href="http://www.mkyong.com">J2EE web development tutorials</a> 
</li> 

<li> 
<a href="http://www.google.com">Google search engine</a> 
</li> 

<li> 
<a href="/Struts2Example/aTagAction.action">aTagAction</a> 
</li> 
</ol> 

</body> 
</html> 

参考

  1. Struts 2 a 标签文档

struts2

Struts 2 操作标记示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-action-tag-example/

Download It – Struts2-Action-Tag-Example.zip

Struts 2 " action "标签用于直接从 JSP 页面调用 action 类。如果“ executeResult ”属性设置为 true,结果页面的内容将直接呈现在当前页面中。

使用一个完整的示例可以很好地说明这一点:

1.行动

一个 Action 类,用很少的方法将结果转发到不同的结果页面。

ParamTagAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class ActionTagAction extends ActionSupport{

	public String execute() {
		return SUCCESS;
	}

	public String sayHello(){
		return "sayHello";
	}

	public String sayStruts2(){
		return "sayStruts2";
	}

	public String saySysOut(){
		System.out.println("SysOut SysOut SysOut");
		return "saySysOut";
	}

} 

2.动作标签示例

JSP 页面展示了" action 标签的使用。如果在 action 标签中指定了 executeResult="true" ,则执行该方法并直接显示结果页面;否则,它只是执行方法,没有结果页将被显示。

action.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 action tag example</h1>

<ol>

<li>
Execute the action's result, render the page here. 
<s:action name="sayHelloAction" executeResult="true"/>
</li>

<li>
Doing the same as above, but call action's sayStruts2() method. 
<s:action name="sayHelloAction!sayStruts2" executeResult="true"/>
</li>

<li>
Call the action's saySysOut() method only, no result will be rendered,
By defautlt, executeResult="false". 
<s:action name="sayHelloAction!saySysOut" />
</li>

</ol>

</body>
</html> 

sayHello.jsp

 <html>
<head>
</head>

<body>
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script><h2>Hello Hello Hello ~ from sayHello.jsp</h2>

</body>
</html> 

sayStruts2.jsp

 <html>
<head>
</head>

<body>
<h2>Struts 2 Struts 2 Struts 2 ~ from sayStruts2.jsp</h2>

</body>
</html> 

saySysOut.jsp

 <html>
<head>
</head>

<body>
<h2>SysOut SysOut SysOut ~ from saySysOut.jsp</h2>

</body>
</html> 

3.struts.xml

声明了几个结果名来演示 executeResult 效果。

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
   <constant name="struts.devMode" value="true" />
   <package name="default" namespace="/" extends="struts-default">

	<action name="actionTagAction" 
		class="com.mkyong.common.action.ActionTagAction" >
		<result name="success">pages/action.jsp</result>
	</action>

	<action name="sayHelloAction" 
		class="com.mkyong.common.action.ActionTagAction" 
                method="sayHello">

		<result name="sayHello">sayHello.jsp</result>
		<result name="sayStruts2">sayStruts2.jsp</result>
		<result name="saySysOut">saySysOut.jsp</result>
	</action>

  </package>
</struts> 

4.演示

http://localhost:8080/struts 2 example/actiontagaction . action

输出

Struts 2 action tag example

参考

  1. Struts 2 动作标签文档

struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190304031539/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

struts 2 action error & action message 示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-actionerror-actionmessage-example/

Download It – Struts2-ActionError-ActionMessage-Example.zip

展示 Struts 2 的 ActionErrorActionMessage 类用法的教程。

1.action error–用于向用户发送错误反馈信息–通过 < s:actionerror/ > 显示。

 <s:if test="hasActionErrors()">
   <div class="errors">
      <s:actionerror/>
   </div>
</s:if> 

2.action message——用于向用户发送信息反馈消息,通过 <显示 s:actionmessage/ >

 <s:if test="hasActionMessages()">
   <div class="welcome">
      <s:actionmessage/>
   </div>
</s:if> 

这是一个简单的登录表单,如果用户名不等于“mkyong ”,则显示错误消息(actionerror ),否则重定向到另一个页面并显示欢迎消息(actionmessage)。此外,所有标签和错误消息都是从资源包(属性文件)中检索的。

1.文件夹结构

查看此项目结构

Struts 2 Login Folder Structure ## 2.属性文件

存储消息的两个属性文件。

登录操作.属性

 #Welcome messages
welcome.hello = Hello

#error message
username.required = Username is required
password.required = Password is required 

global.properties

 #Global messages
global.username = Username
global.password = Password
global.submit = Submit
global.reset = Reset 

3.行动

一个经典的 action 类,做一个简单的检查确保用户名等于“mkyong”,用 addActionError() 设置错误消息或者用 addActionMessage() 设置成功消息。

 package com.mkyong.user.action;

import com.opensymphony.xwork2.ActionSupport;

public class LoginAction extends ActionSupport{

	private String username;
	private String password;

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	//business logic
	public String execute() {

		return "SUCCESS";

	}

	//simple validation
	public void validate(){
		if("mkyong".equals(getUsername())){
			addActionMessage("You are valid user!");
		}else{
			addActionError("I don't know you, dont try to hack me!");
		}
	}
} 

4.JSP 视图

两个简单的 css 样式的 JSP 页面来定制错误消息。

login.jsp

 <%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>

<style type="text/css">
.errors {
	background-color:#FFCCCC;
	border:1px solid #CC0000;
	width:400px;
	margin-bottom:8px;
}
.errors li{ 
	list-style: none; 
}
</style>

</head>

<body>
<h1>Struts 2 ActionError & ActionMessage Example</h1>

<s:if test="hasActionErrors()">
   <div class="errors">
      <s:actionerror/>
   </div>
</s:if>

<s:form action="validateUser">
	<s:textfield key="global.username" name="username"/>
	<s:password key="global.password" name="password"/>
	<s:submit key="global.submit" name="submit"/>
</s:form>

</body>
</html> 

welcome.jsp

 <%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>

<style type="text/css">
.welcome {
	background-color:#DDFFDD;
	border:1px solid #009900;
	width:200px;
}
.welcome li{ 
	list-style: none; 
}
</style>

</head>
<body>
<h1>Struts 2 Struts 2 ActionError & ActionMessage Example</h1>

<s:if test="hasActionMessages()">
   <div class="welcome">
      <s:actionmessage/>
   </div>
</s:if>

<h2>

<s:property value="getText('welcome.hello')" /> : 
<s:property value="username"/>

</h2>

</body>
</html> 

5.struts.xml

把所有的联系在一起。

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

  <constant name="struts.custom.i18n.resources" value="global" />

  <package name="user" namespace="/user" extends="struts-default">
     <action name="login">
	 <result>pages/login.jsp</result>
     </action>
     <action name="validateUser" class="com.mkyong.user.action.LoginAction">
	 <result name="SUCCESS">pages/welcome.jsp</result>
	 <result name="input">pages/login.jsp</result>
     </action>
   </package>

</struts> 

In Struts 2, the functionality and usage of ActionError & ActionMessage are quite similar with Struts 1.

6.运行它

http://localhost:8080/struts 2 example/user/log in . action

Struts 2 ActionError 1

用户名无效,显示带有的错误信息

Struts 2 ActionError 2

用户名有效,显示欢迎消息

Struts 2 ActionError 3

参考

  1. 验证感知文档

struts2

Struts 2 和 JSON 示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-and-json-example/

在 Struts 2 示例中,您将学习如何通过“struts2-json-plugin.jar”库将对象转换成 JSON 格式。

1.获取依赖库

获取 struts2-json-plugin.jar 库。
POM . XML

 <!-- Struts 2 -->
    <dependency>
          <groupId>org.apache.struts</groupId>
	  <artifactId>struts2-core</artifactId>
	  <version>2.1.8</version>
    </dependency>

    <!-- Struts 2 JSON Plugins -->
    <dependency>
          <groupId>org.apache.struts</groupId>
	  <artifactId>struts2-json-plugin</artifactId>
	  <version>2.1.8</version>
    </dependency> 

2.行动(JSON)

这是一个将被转换成 JSON 格式的动作类。

 package com.mkyong.common.action;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.opensymphony.xwork2.Action;

public class JSONDataAction{

	private String string1 = "A";
	private String[] stringarray1 = {"A1","B1"};
	private int number1 = 123456789;
	private int[] numberarray1 = {1,2,3,4,5,6,7,8,9};
	private List<String> lists = new ArrayList<String>();
	private Map<String, String> maps = new HashMap<String, String>();

	//no getter method, will not include in the JSON
	private String string2 = "B";

	public JSONDataAction(){
		lists.add("list1");
		lists.add("list2");
		lists.add("list3");
		lists.add("list4");
		lists.add("list5");

		maps.put("key1", "value1");
		maps.put("key2", "value2");
		maps.put("key3", "value3");
		maps.put("key4", "value4");
		maps.put("key5", "value5");
	}

	public String execute() {
               return Action.SUCCESS;
        }

	public String getString1() {
		return string1;
	}

	public void setString1(String string1) {
		this.string1 = string1;
	}

	public String[] getStringarray1() {
		return stringarray1;
	}

	public void setStringarray1(String[] stringarray1) {
		this.stringarray1 = stringarray1;
	}

	public int getNumber1() {
		return number1;
	}

	public void setNumber1(int number1) {
		this.number1 = number1;
	}

	public int[] getNumberarray1() {
		return numberarray1;
	}

	public void setNumberarray1(int[] numberarray1) {
		this.numberarray1 = numberarray1;
	}

	public List<String> getLists() {
		return lists;
	}

	public void setLists(List<String> lists) {
		this.lists = lists;
	}

	public Map<String, String> getMaps() {
		return maps;
	}

	public void setMaps(Map<String, String> maps) {
		this.maps = maps;
	}

} 

3.struts.xml

要输出 JSON 数据,需要声明一个扩展了“ json-default ”的包,结果类型为“ json ”。

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

   <constant name="struts.devMode" value="true" />

   <package name="default" namespace="/" extends="json-default">
      <action name="getJSONResult" 
           class="com.mkyong.common.action.JSONDataAction">
       	   <result type="json" />
      </action>
    </package>

</struts> 

4.演示

访问动作 URL, JSONDataAction 的属性将被转换成 JSON 格式。
http://localhost:8080/struts 2 example/getjsonresult . action

Struts 2 JSON example

JSON 格式…

{
   "lists":["list1","list2","list3","list4","list5"],
   "maps":
   {
     "key4":"value4","key3":"value3","key5":"value5","key2":"value2","key1":"value1"
   },
   "number1":123456789,
   "numberarray1":[1,2,3,4,5,6,7,8,9],
   "string1":"A",
   "stringarray1":["A1","B1"]
}

Hope this super simple example can give you an overall idea of how JSON plugin worked with Struts 2. However, there are still many useful settings are not cover here, make sure you read the Struts 2 JSON plugin documentation for more details.

下载源代码

Download It – Struts2-JSON-Example.zip

参考

  1. Struts 2 JSON 插件
  2. JSON 官方文档

json struts2

Struts 2 附加标签示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-append-tag-example/

Download It – Struts2-Append-Tag-Example.zip

Struts 2 append 标签用于将几个迭代器(由 List 或 Map 创建)组合成一个迭代器。在本教程中,您将使用 Struts 2 append 标签来完成以下任务:

  1. 将三个数组列表组合成一个迭代器。
  2. 将三个散列表组合成一个迭代器。
  3. ArrayListHashMap 组合成一个迭代器。

Assume 2 iterators, each has two entries, after combine with append tag into a single iterator, the order of the entries will look like following :

  1. 第一个迭代器的第一个条目。
  2. 第一个迭代器的第二个条目。
  3. 第二个迭代器的第一个条目。
  4. 第二个迭代器的第二个条目。

这只适用于列表迭代器;地图迭代器,顺序会随机。

1.行动

一个具有 3 个 ArrayList 和 3 个 HashMap 属性的 Action 类。

追加动作

 package com.mkyong.common.action;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.opensymphony.xwork2.ActionSupport;

public class AppendTagAction extends ActionSupport{

	private List<String> list1 = new ArrayList<String>();
	private List<String> list2 = new ArrayList<String>();
	private List<String> list3 = new ArrayList<String>();

	private Map<String,String> map1 = new HashMap<String,String>();
	private Map<String,String> map2 = new HashMap<String,String>();
	private Map<String,String> map3 = new HashMap<String,String>();

	public String execute() {

		list1.add("List1 - 1");
		list1.add("List1 - 2");
		list1.add("List1 - 3");

		list2.add("List2 - 1");
		list2.add("List2 - 2");
		list2.add("List2 - 3");

		list3.add("List3 - 1");
		list3.add("List3 - 2");
		list3.add("List3 - 3");

		map1.put("map1-key1", "map1-value1");
		map1.put("map1-key2", "map1-value2");
		map1.put("map1-key3", "map1-value3");

		map2.put("map2-key1", "map2-value1");
		map2.put("map2-key2", "map2-value2");
		map2.put("map2-key3", "map2-value3");

		map3.put("map3-key1", "map3-value1");
		map3.put("map3-key2", "map3-value2");
		map3.put("map3-key3", "map3-value3");

		return SUCCESS;
	}

	//getter methods...
} 

2.附加标签示例

一个 JSP 页面,展示了如何使用 append 标记将 3 ArrayList/3 HashMap/1 ArrayList+1 HashMap 组合成一个迭代器,并循环遍历其值并打印出来。

appendIterator.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
 <html>
<head>
</head>

<body>
<h1>Struts 2 Append tag example</h1>

1\. Combine 3 ArrayList into a single iterator.
<s:append var="customListIterator">
     <s:param value="%{list1}" />
     <s:param value="%{list2}" />
     <s:param value="%{list3}" />
</s:append>
<ol>
<s:iterator value="%{#customListIterator}">
     <li><s:property /></li>
</s:iterator>
</ol>

2\. Combine 3 HashMap into a single iterator.
<s:append var="customMapIterator">
     <s:param value="%{map1}" />
     <s:param value="%{map2}" />
     <s:param value="%{map3}" />
</s:append>
<ol>
<s:iterator value="%{#customMapIterator}">
     <li><s:property /></li>
</s:iterator>
</ol>

3\. Combine ArrayList and HashMap into a single iterator.
<s:append var="customMixedIterator">
     <s:param value="%{list1}" />
     <s:param value="%{map1}" />
</s:append>
<ol>
<s:iterator value="%{#customMixedIterator}">
     <li><s:property /></li>
</s:iterator>
</ol>

</body>
</html> 

3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 	<constant name="struts.devMode" value="true" />

	<package name="default" namespace="/" extends="struts-default">

		<action name="appendTagAction" 
			class="com.mkyong.common.action.AppendTagAction" >
			<result name="success">pages/appendIterator.jsp</result>
		</action>

	</package>

</struts> 

4.演示

http://localhost:8080/struts 2 example/appendtagaction . action

输出

 Struts 2 Append tag example

1\. Combine 3 ArrayList into a single iterator.

  1\. List1 - 1
  2\. List1 - 2
  3\. List1 - 3
  4\. List2 - 1
  5\. List2 - 2
  6\. List2 - 3
  7\. List3 - 1
  8\. List3 - 2
  9\. List3 - 3

2\. Combine 3 HashMap into a single iterator.

  1\. map1-key3=map1-value3
  2\. map1-key1=map1-value1
  3\. map1-key2=map1-value2
  4\. map2-key2=map2-value2
  5\. map2-key3=map2-value3
  6\. map2-key1=map2-value1
  7\. map3-key3=map3-value3
  8\. map3-key1=map3-value1
  9\. map3-key2=map3-value2

3\. Combine ArrayList and HashMap into a single iterator.

  1\. List1 - 1
  2\. List1 - 2
  3\. List1 - 3
  4\. map1-key3=map1-value3
  5\. map1-key1=map1-value1
  6\. map1-key2=map1-value2 

参考

  1. Struts 2 追加标签文档

struts2

Struts 2 自动完成器示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-autocompleter-example/

Download It – Struts2-AutoCompleter-Example.zip

在 Struts 2 中, < sx:autocompleter > 标签是一个组合框,当用户在文本框中输入时,它会自动提示下拉建议列表。

This feature is implemented by dojo library, So, make sure you include “struts2-dojo-plugin.jar” as dependency library, put “struts-dojo-tags” tag on top of the page and output its header information via <sx:head />.

举个例子,

 <%@ taglib prefix="sx" uri="/struts-dojo-tags" %>
<html>
<head>
<sx:head />
</head>
<body>
<sx:autocompleter label="What's your lucky number?" 
name="yourLuckyNumber" autoComplete="false"
list="{'1','12','13','14'}" /> 

产生以下 HTML

 <html> 
<head> 
<script language="JavaScript" type="text/javascript"> 
    // Dojo configuration
    djConfig = {
        isDebug: false,
        bindEncoding: "UTF-8"
          ,baseRelativePath: "/Struts2Example/struts/dojo/"
          ,baseScriptUri: "/Struts2Example/struts/dojo/"
         ,parseWidgets : false

    };
</script> 
<script language="JavaScript" type="text/javascript"
        src="/Struts2Example/struts/dojo/struts_dojo.js"></script> 

<script language="JavaScript" type="text/javascript"
        src="/Struts2Example/struts/ajax/dojoRequire.js"></script> 

<link rel="stylesheet" href="/Struts2Example/struts/xhtml/styles.css" 
type="text/css"/> 

<script language="JavaScript" src="/Struts2Example/struts/utils.js" 
type="text/javascript"></script> 

<script language="JavaScript" src="/Struts2Example/struts/xhtml/validation.js" 
type="text/javascript"></script> 

<script language="JavaScript" src="/Struts2Example/struts/css_xhtml/validation.js" 
type="text/javascript"></script> 
</head> 
...
<tr>
<td class="tdLabel">
<label for="resultAction_yourLuckyNumber" class="label">
What's your lucky number?:</label></td>
<td>  
<select dojoType="struts:ComboBox" id="resultAction_yourLuckyNumber" 
autoComplete="false" name="yourLuckyNumber" 
keyName="yourLuckyNumberKey" visibleDownArrow="true" >
    <option value="1">1</option>
    <option value="12">12</option>
    <option value="13">13</option>
    <option value="14">14</option>
</select>
</td>
</tr>
<script language="JavaScript" type="text/javascript">
djConfig.searchIds.push("resultAction_yourLuckyNumber");</script> 

Struts 2 示例

一个完整的 < s:autocompleter > 标签的例子,当用户在相应的文本框中输入时,生成下拉建议列表。

1.pom.xml

下载 Struts 2 dojo 依赖库。

pom.xml

 //...
    <!-- Struts 2 -->
    <dependency>
      <groupId>org.apache.struts</groupId>
	  <artifactId>struts2-core</artifactId>
	  <version>2.1.8</version>
    </dependency>

    <!-- Struts 2 Dojo Ajax Tags -->
    <dependency>
      <groupId>org.apache.struts</groupId>
	  <artifactId>struts2-dojo-plugin</artifactId>
	  <version>2.1.8</version>
    </dependency>
//... 

2.动作类

Action 类来为" autocompleter "组件生成 web 框架选项列表。

AutoCompleterAction.java

 package com.mkyong.common.action;

import java.util.ArrayList;
import java.util.List;

import com.opensymphony.xwork2.ActionSupport;

public class AutoCompleterAction extends ActionSupport{

	private List<String> webframeworks = new ArrayList<String>();

	private String yourFavWebFramework;
	private String yourLuckyNumber;

	public AutoCompleterAction(){
		webframeworks.add("Spring MVC");
		webframeworks.add("Struts 1.x");
		webframeworks.add("Struts 2.x");
		webframeworks.add("JavaServer Faces (JSF)");
		webframeworks.add("Google Web Toolkit (GWT)");
		webframeworks.add("Apache Wicket");
		webframeworks.add("Apache Click");
		webframeworks.add("Apache Cocoon");
		webframeworks.add("JBoss Seam");
		webframeworks.add("Stripes");
		webframeworks.add("Apache Tapestry");
		webframeworks.add("Others");
	}

	public String getYourLuckyNumber() {
		return yourLuckyNumber;
	}

	public void setYourLuckyNumber(String yourLuckyNumber) {
		this.yourLuckyNumber = yourLuckyNumber;
	}

	public String getYourFavWebFramework() {
		return yourFavWebFramework;
	}

	public void setYourFavWebFramework(String yourFavWebFramework) {
		this.yourFavWebFramework = yourFavWebFramework;
	}

	public List<String> getWebframeworks() {
		return webframeworks;
	}

	public void setWebframeworks(List<String> webframeworks) {
		this.webframeworks = webframeworks;
	}

	public String display() {
		return NONE;
	}

} 

3.结果页面

通过“ < s:autocompleter > ”标签渲染“ autocompleter ”组件,通过 Java list 和 OGNL 生成自动下拉建议列表。

autocompleter.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib prefix="sx" uri="/struts-dojo-tags" %>

<html>
<head>
<sx:head />
</head>

<body>
<h1>Struts 2 autocompleter example</h1>

<s:form action="resultAction" namespace="/" method="POST" >

<sx:autocompleter label="What's your lucky number?" 
name="yourLuckyNumber" autoComplete="false"
list="{'1','12','13','14','21','22','23','24',
'31','32','33','34','41','42','43','44'}" />

<sx:autocompleter label="What's your favorite web framework?" 
list="webframeworks" name="yourFavWebFramework" />

<s:submit value="submit" name="submit" />

</s:form>

</body>
</html> 

result.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>

<body>
<h1>Struts 2 autocompleter example</h1>

<h2>
   Lucky Number : <s:property value="yourLuckyNumber"/> 
</h2> 

<h2>
   Web Appication Frameworks : <s:property value="yourFavWebFramework"/> 
</h2> 

</body>
</html> 

3.struts.xml

全部链接起来~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 <constant name="struts.devMode" value="true" />

<package name="default" namespace="/" extends="struts-default">

  <action name="autoCompleterAction" 
	class="com.mkyong.common.action.AutoCompleterAction" 
        method="display">
	<result name="none">pages/autocompleter.jsp</result>
  </action>

  <action name="resultAction" 
        class="com.mkyong.common.action.AutoCompleterAction" >
	<result name="success">pages/result.jsp</result>
  </action>
</package>

</struts> 

4.演示

http://localhost:8080/struts 2 example/autocomplete action . action

Struts 2 AutoCompleter exampleStruts 2 AutoCompleter exampleHere’s another example to show the use of JSON data to provide a list of the select options to the autocompleter component – Struts 2 autocompleter + JSON example.

参考

  1. Struts 2 自动完成器文档
  2. Struts 2 ajax 和 javascript 配方
  3. Struts 2 组合框示例

auto complete struts2

Struts 2 自动完成器+ JSON 示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-autocompleter-json-example/

Download It – Struts2-AutoCompleter-JSON-Example.zip

在最后一个 Struts 2 autocompleter 示例中,您了解了如何通过 Java list 和 ONGL 表达式为 autocompleter 组件生成一个选择选项列表。或者,也可以通过 JSON 数据生成选择选项。

Before you proceed, make sure you understand the basic usage of autocompleter component and JSON plugin. Read the below articles.

  1. Struts 2 自动完成器示例
  2. Struts 2 JSON 示例

Struts 2 自动完成器+ JSON 示例

在本教程中,您将使用 Struts 2 JSON 插件将一个对象转换成 JSON 格式,并将其传递给自动完成器组件。

1.获取依赖库

获取所有的依赖库。

pom.xml

 <!-- Struts 2 -->
    <dependency>
          <groupId>org.apache.struts</groupId>
	  <artifactId>struts2-core</artifactId>
	  <version>2.1.8</version>
    </dependency>

    <!-- Struts 2 Dojo Ajax Tags -->
    <dependency>
          <groupId>org.apache.struts</groupId>
	  <artifactId>struts2-dojo-plugin</artifactId>
	  <version>2.1.8</version>
    </dependency>

    <!-- Struts 2 JSON Plugins -->
    <dependency>
          <groupId>org.apache.struts</groupId>
	  <artifactId>struts2-json-plugin</artifactId>
	  <version>2.1.8</version>
    </dependency> 

2.行动

一个稍后要转换成 JSON 格式的类,为自动完成器组件提供一个选择选项列表。

DatabaseJSON.java

 package com.mkyong.common.action;

import java.util.HashMap;
import java.util.Map;

import com.opensymphony.xwork2.Action;

public class DatabaseJSON{

	private Map<String, String> databases = new HashMap<String, String>();

	public DatabaseJSON(){
		databases.put("MySQL", "MySQL");
		databases.put("Oracle", "Oracle");
		databases.put("PostgreSQL", "PostgreSQL");
		databases.put("Microsoft SQL Server", "Microsoft SQL Server");
		databases.put("DB2", "DB2");
		databases.put("Others", "Others");
	}

	public String execute() {
                return Action.SUCCESS;
	}

	public Map<String, String> getDatabases() {
		return databases;
	}

	public void setDatabases(Map<String, String> databases) {
		this.databases = databases;
	}
} 

一个普通的 Action 类,只做重定向工作并存储 autocompleter 值。
AutoCompleterAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class AutoCompleterAction extends ActionSupport{

	private String yourDatabase;

	public String display() {
		return NONE;
	}

	public String getYourDatabase() {
		return yourDatabase;
	}

	public void setYourDatabase(String yourDatabase) {
		this.yourDatabase = yourDatabase;
	}

} 

3.结果

这里有点棘手,使用一个" s:url "标签指向一个" databaseJSON "动作,它将以 JSON 格式返回一个选项列表。并通过 href="%{databaseList}" 将其链接到自动完成器组件。

 <%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib prefix="sx" uri="/struts-dojo-tags" %>

<html>
<head>
<sx:head />
</head>

<body>
<h1>Struts 2 autocompleter + JSON example</h1>

<s:form action="resultAction" namespace="/" method="POST" >

<s:url id="databaseList" action="databaseJSON" />

<sx:autocompleter label="What's your favorite Database Server?" 
href="%{databaseList}" name="yourFavDatabase" />

<s:submit value="submit" name="submit" />

</s:form>

</body>
</html> 

4.struts.xml

按如下方式配置操作和 JSON 提供程序:

databases
It means, convert the DatabaseJSON’s databases property into JSON format, but the entire object.

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 	<constant name="struts.devMode" value="true" />

	<package name="json" namespace="/" extends="json-default">
     	    <action name="databaseJSON" 
     		class="com.mkyong.common.action.DatabaseJSON">
       	 	<result type="json" >
       	 		<param name="root">databases</param>
       	 	</result>
     	    </action>
  	</package>

	<package name="default" namespace="/" extends="struts-default">
	    <action name="autoCompleterAction" 
		class="com.mkyong.common.action.AutoCompleterAction" 
	        method="display">
		<result name="none">pages/autocompleter-json.jsp</result>
	    </action>

	    <action name="resultAction" 
	        class="com.mkyong.common.action.AutoCompleterAction" >
		<result name="success">pages/result.jsp</result>
	    </action>
	</package>

</struts> 

4.演示

访问动作 URL,现在 JSON 数据提供了自动完成器选择选项。

http://localhost:8080/struts 2 example/autocomplete action . action

Struts 2 AutoCompleter JSON example

或者,您可以通过以下 URL
直接访问 JSON 数据:http://localhost:8080/struts 2 example/database JSON . action

 {
   "PostgreSQL":"PostgreSQL",
   "MySQL":"MySQL",
   "Others":"Others",
   "Oracle":"Oracle",
   "Microsoft SQL Server":"Microsoft SQL Server",
   "DB2":"DB2"
} 

参考

  1. Struts 2 JSON 插件
  2. JSON 官方文档
  3. Struts 2 自动完成器示例
  4. Struts 2 JSON 示例

auto complete json struts2

Struts 2 bean 标记示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-bean-tag-example/

Download It – Struts2-Bean-Tag-Example.zip

Struts 2 " bean "标签用于在 JSP 页面中实例化一个类。在本教程中,您将使用“ bean ”标记实例化一个名为“ HelloBean ”的类,通过“ param ”元素设置其属性并打印出值。

1.简单豆

一个简单的类,稍后使用 bean 标签来实例化它。

地狱篇. java

 package com.mkyong.common.action;

public class HelloBean{

	private String msg;

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

} 

2.行动

转发请求的操作类。

BeanTagAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class BeanTagAction extends ActionSupport{

	public String execute() {
		return SUCCESS;
	}

} 

2.Bean 标记示例

一个 JSP 页面,展示了如何使用“ bean ”标记实例化“ HelloBean ”。

In “bean” tag, you can assign a name to the bean via a “var” attribute, later you can access the bean via #var_bean_name , or its property value via #var_bean_name.property.

bean.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
 <html>
<head>
</head>

<body>
<h1>Struts 2 Bean tag example</h1>

<s:bean name="com.mkyong.common.action.HelloBean" var="hello">
  <s:param name="msg">Hello Bean Tag</s:param>
</s:bean>

The HelloBean's msg property value : <s:property value="#hello.msg"/>

</body>
</html> 

3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
    <constant name="struts.devMode" value="true" />
    <package name="default" namespace="/" extends="struts-default">

	<action name="beanTagAction" 
	    class="com.mkyong.common.action.BeanTagAction" >
	    <result name="success">pages/bean.jsp</result>
	</action>

    </package>
</struts> 

4.演示

http://localhost:8080/struts 2 example/bean tag action . action

输出

Struts 2 bean tag example

参考

  1. Struts 2 Bean 标签文档

struts2

Struts 2 中文本地化问题

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-chinese-localization-issue/

一个 Struts 2 i18n 显示中文字符的本地化问题…

案例 1:包含特殊字符的属性文件

一个属性文件以中文存储用户名、密码和提交信息。这个属性文件是以 UTF 8 格式创建的,但是内容不是用 native2ascii 编码的。

struts2-localization-case1-1

让我们尝试通过几个 UI 标签来显示中文字符。视图页面被声明为以带有 HTML meta 标签的 UTF-8 格式显示。

 ...
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
...
<s:form action="validateUser">
	<s:textfield key="global.username" name="username"/>
	<s:password key="global.password" name="password"/>	
	<s:submit key="global.submit" name="submit" />

	<div>Testing 1 : <s:property value="getText('global.username')" /></div>
	<div>Testing 2 : <s:text name="global.password" /></div></br/>
</s:form>
...
<s:url id="localezhCN" namespace="/" action="locale" >
   <s:param name="request_locale" >zh_CN</s:param>
</s:url>
...
<s:a href="%{localezhCN}" >Chinese</s:a>
... 

结果

struts2-localization-case1-2

令人惊讶的是,下面三个 UI 标签能够正确显示中文消息,

 <s:textfield key="global.username" name="username"/>
<s:password key="global.password" name="password"/>	
Testing 2 : <s:text name="global.password" /> 

但是“ s:submit ”和“ getText() ”都无法显示?

According to Java’s i18n documentation, to display a special character correctly with resource bundle, it must be precessed with the native2ascii tool.

在深入研究了 TextProvider.getText() 源代码后,它使用 resource bundle.getString()从资源包中检索消息,因此不正确的消息是合理的。但是为什么“ s:text ”、“ s:textfield ”和“ s:password ”能够正确显示中文信息,为什么“ s:submit ”会失败呢?我脑子里的问题太多了,让我们来看案例 2…

案例 2:带有特殊字符的属性文件(编码)

这一次,使用 native2ascii 工具处理属性文件,并正确编码中文字符。

struts2-localization-case2-1

结果

struts2-localization-case2-2

结果完全相反,现在“ s:submit ”和“ getText() ”能够正确显示,但是其他 UI 组件都失败了。这是预期工作,因为 Struts 2 推荐 getText() 显示 i18n 或本地化消息。问题是,为什么“ s:submit 不一样?

支柱 2..怎么了?

以下是我心中的一些疑问…

  1. 为什么 s:submit 的表现如此不同?
  2. i18n 应该很简单,为什么 Struts 2 有这种问题?还是我误解了 Struts 2 i18n 的工作原理?
  3. 为什么资源包中的消息有这么多显示方式?为什么不直接用一种方法分组呢?在 Struts 1 中,只使用“bean:message ”,为什么在 Struts 2 中看起来如此复杂?
  4. Struts 2 支持 XML 资源包吗?我只是不喜欢使用 native2ascii 工具将数据编码为 UTF 8 格式,这使得属性文件不可读。Apache Wicket 在这个问题上做得非常好,可能 Struts 2 应该借鉴一下。
  5. 那么,如何在 Struts 2 中正确显示汉字呢? Many articles and tutorials are told that the following methods are able to display the message from resource bundle:
 <s:text name="global.username"/>
<s:property value="getText('global.username')"/> 

但是,这仅适用于英语或一些“类似英语(欧洲)”的字符,例如法国、德国。但是对于中文或日文的“大方块”字符,两种方法将返回完全不同的输出。真的不知道如何将 Struts 2 本地化为中文和日文。

更新于…(2010 年 6 月 16 日)

从百度搜索引擎中挖掘解决方案,在其中一篇中文文章中找到了间接解决方案——Struts 2 国际化实例

解决办法

问题出在 HTML meta 标签上,

 <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head> 

In Struts 1, the above meta tag is required to display the UTF-8 data correctly, but this is not true for Struts 2.

在 Struts 2 中,meta 标签不再起作用,我们应该把 < %@页面 content type = " text/html;charset = UTF-8 "%>标签在查看页面的第一行。举个例子,

 <%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>
... 

结果

struts2-localization-work

所有的中文信息都显示正确。

对我之前问题的回答

  1. 为什么 s:submit 的表现如此不同?答:对此不予置评
  2. i18n 应该很简单,为什么 Struts 2 有这种问题?还是我误解了 Struts 2 i18n 的工作原理?
    答:一定要把 < %@页 content type = " text/html;charset = UTF-8 "%>"在查看页面的第一行。
  3. 为什么资源包中的消息有这么多显示方式?为什么不直接用一种方法分组呢?在 Struts 1 中,只使用“bean:message ”,为什么在 Struts 2 中看起来如此复杂?
    A: s:text,key,getText(),name…,都能够正确渲染中文或 UTF 8 编码的数据,只要确保在视图页面中放入正确的“charset”即可。我仍然倾向于只使用一种方法来控制消息包(比如 Struts 1),太多的等价选项只会让开发人员感到困惑。
  4. Struts 2 支持 XML 资源包吗?我只是不喜欢使用 native2ascii 工具将数据编码为 UTF 8 格式,这使得属性文件不可读。Apache Wicket 在这个问题上做得非常好,可能 Struts 2 应该借鉴一下。
    答:希望 Struts 2 能在下一个版本支持 XML 资源包。
  5. 那么,如何在 Struts 2 中正确显示汉字呢?
    答:见上解。

Download it – Struts2-i18n-issue-Example

参考

  1. http://www . mkyong . com/Java/Java-convert-Chinese-character-to-unicode-with-native 2 ascii/
  2. http://forums.sun.com/thread.jspa?threadID=5185040
  3. http://www . mail-archive . com/user @ struts . Apache . org/msg 85490 . html
  4. http://www . code ranch . com/t/452139/Struts/application resources-properties-utf-characters # 2013 557
  5. http://struts.apache.org/2.1.8/docs/localization.html
  6. http://hxzon 00 . blog . 163 . com/blog/static/10489241620088121449163/

multiple languages struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190601051037/https://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

struts 2–为操作类配置静态参数

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-configure-static-parameter-for-action-class/

Download It – Struts-Action-Static-ParamExample.zip

在某些情况下,您可能需要为动作类分配一些预定义的或静态的参数值。

定义了动作的静态参数

在 Struts 2 中,可以在 struts.xml 文件中进行配置,通过 < param > 标签,例如

struts.xml

 <struts>

   <constant name="struts.custom.i18n.resources" value="global" />
   <constant name="struts.devMode" value="true" />

   <package name="default" namespace="/" extends="struts-default">
	<action name="locale" class="com.mkyong.common.action.LocaleAction">
		<result name="SUCCESS">pages/welcome.jsp</result>
		<param name="EnglishParam">English</param>
    	        <param name="ChineseParam">Chinese</param>
     	        <param name="FranceParam">France</param>
	</action>
   </package>	
</struts> 

它将三个预定义参数值分配给一个 LocaleAction 操作类。

从操作中获取静态参数

为了从 struts.xml 中获取静态参数值,Action 类必须实现参数化接口。它可以通过地图属性JavaBean 属性来访问。

The Action’s static parameters is control by the staticParams Interceptor, which is included in the default stack “struts-default.xml”. ## 1.地图属性

在动作类初始化期间,staticParams 拦截器将通过 setParams() 方法将预定义参数值的引用传递给动作类。

 //...
import com.opensymphony.xwork2.config.entities.Parameterizable;

public class LocaleAction implements Parameterizable{

	Map<String, String> params;
	//...
	public void setParams(Map<String, String> params) {
		this.params = params;
	}
} 

2.JavaBean 属性

在 Action 类初始化期间,如果您正确地创建了 getter 和 setter 方法,staticParams 拦截器会将预定义的参数值设置为对应于“param”元素的每个 JavaBean 属性。

 //...
import com.opensymphony.xwork2.config.entities.Parameterizable;

public class LocaleAction implements Parameterizable{

	String englishParam;
	String chineseParam;
	String franceParam;

	public String getEnglishParam() {
		return englishParam;
	}

	public void setEnglishParam(String englishParam) {
		this.englishParam = englishParam;
	}

	public String getChineseParam() {
		return chineseParam;
	}

	public void setChineseParam(String chineseParam) {
		this.chineseParam = chineseParam;
	}

	public String getFranceParam() {
		return franceParam;
	}

	public void setFranceParam(String franceParam) {
		this.franceParam = franceParam;
	}
    //...
} 

struts2

Struts 2 创建自己的拦截器

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-creating-own-interceptor/

Download it – Struts2-Create-Own-Interceptor-Example.zip

在本教程中,它展示了如何在 Struts 2 中创建自己的拦截器。

总结步骤:

  1. 创建一个类实现com . open symphony . xwork 2 . interceptor . interceptor
  2. 实现 intercept(ActionInvocation 调用)方法。
  3. struts.xml 中配置拦截器。
  4. 把它和行动联系起来。

Struts 2 interceptors
Struts 2 comes with many ready interceptors, make sure you check the list of the available Struts 2 interceptors before you create your own interceptor.

创建自己的拦截器的完整示例:

1.行动

转发用户请求和打印消息的简单操作。

HelloAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class HelloAction extends ActionSupport{

	public String execute() throws Exception {

		System.out.println("HelloAction execute() is called");
		return SUCCESS;

	}
} 

2.拦截机

一个完整的拦截器示例。

PrintMsgInterceptor.java

 package com.mkyong.common.interceptor;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;

public class PrintMsgInterceptor implements Interceptor{

        //called during interceptor destruction
	public void destroy() {
		System.out.println("CustomInterceptor destroy() is called...");
	}

	//called during interceptor initialization
	public void init() {
		System.out.println("CustomInterceptor init() is called...");
	}

	//put interceptor code here
	public String intercept(ActionInvocation invocation) throws Exception {

		System.out.println("CustomInterceptor, before invocation.invoke()...");

		String result = invocation.invoke();

		System.out.println("CustomInterceptor, after invocation.invoke()...");

		return result;
	}

} 

解释
拦截器类必须实现com . open symphony . xwork 2 . interceptor . interceptor 接口。拦截器初始化时,调用init();拦截器破坏, destroy() 被调用。最后,将所有完成工作的拦截器代码放在intercept(action invocation invocation)方法中。

invocation.invoke()
In the interceptor intercept() method, you must called the invocation.invoke() and return it’s result. This is the method responsible for calling the next interceptor or the action. The action will failed to continue without calling the invocation.invoke() method.destroy() is not reliable
It’s not recommend to put any code inside the destroy(), because this method is not reliable. When your application server is force shutdown or be killed by command, the destroy() will not be called. ## 3.struts.xml

struts.xml 文件中配置拦截器。

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <package name="default" namespace="/" extends="struts-default">

    <interceptors>	
	<interceptor name="printMsgInterceptor" 
	class="com.mkyong.common.interceptor.PrintMsgInterceptor"></interceptor>

         <interceptor-stack name="newStack">
     		<interceptor-ref name="printMsgInterceptor"/>
		<interceptor-ref name="defaultStack" />
          </interceptor-stack>

    </interceptors>

    <action name="helloAction" 
	class="com.mkyong.common.action.HelloAction" >
	<interceptor-ref name="newStack"/>
	<result name="success">pages/hello.jsp</result>
     </action>

   </package>
</struts> 

4.演示

在服务器初始化期间,拦截器 init() 方法被调用。

 INFO: Overriding property struts.i18n.reload - old value: false new value: true
15 Julai 2010 11:37:42 AM com.opensymphony.xwork2.util.logging.commons.CommonsLogger info
INFO: Overriding property struts.configuration.xml.reload - old value: false new value: true

CustomInterceptor init() is called...

15 Julai 2010 11:37:42 AM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
15 Julai 2010 11:37:42 AM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
15 Julai 2010 11:37:42 AM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/20  config=null
15 Julai 2010 11:37:42 AM org.apache.catalina.startup.Catalina start
INFO: Server startup in 994 ms 

当您通过 URL 访问操作时:http://localhost:8080/struts 2 example/hello action . action

 INFO: Overriding property struts.i18n.reload - old value: false new value: true
15 Julai 2010 11:37:42 AM com.opensymphony.xwork2.util.logging.commons.CommonsLogger info
INFO: Overriding property struts.configuration.xml.reload - old value: false new value: true

CustomInterceptor init() is called...

15 Julai 2010 11:37:42 AM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
15 Julai 2010 11:37:42 AM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
15 Julai 2010 11:37:42 AM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/20  config=null
15 Julai 2010 11:37:42 AM org.apache.catalina.startup.Catalina start
INFO: Server startup in 994 ms

CustomInterceptor, before invocation.invoke()...
HelloAction execute() is called
CustomInterceptor, after invocation.invoke()... 

参考

  1. Struts 2 拦截器文档
  2. Struts 2 构建自己的拦截器

interceptor struts2

Struts 2 日期标记示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-date-tag-example/

Download It – Struts2-Date-Tag-Example.zip

Struts 2 " date 标签用于以两种方式格式化日期对象:

  1. 自定义日期格式(如“日/月/年”)。
  2. “nice”属性将日期格式化为易于阅读的符号,如“此日期是 162 天前”。

在本教程中,它展示了使用 Struts 2 " date "标记将日期对象格式化为"自定义日期格式"和"易读符号"。

1.行动

转发请求的操作类,并用预定义的日期初始化日期对象。

date tagging . Java

 package com.mkyong.common.action;

import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;

import com.opensymphony.xwork2.ActionSupport;

public class DateTagAction extends ActionSupport{

	public Date customDate;

	public String execute() {

		Calendar cal = Calendar.getInstance();
		//set date to january 31, 2010
		cal.set(2010, 0, 31);
		Date newDate = cal.getTime();

		setCustomDate(newDate);

		return SUCCESS;

	}

	public Date getCustomDate() {
		return customDate;
	}

	public void setCustomDate(Date customDate) {
		this.customDate = customDate;
	}

} 

2.日期标签示例

一个 JSP 页面,展示了如何使用“ date 标记来格式化日期对象:

  1. 默认日期格式。
  2. 自定义日期格式。
  3. 易读的符号。

date.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
 <html>
<head>
</head>

<body>
<h1>Struts 2 date tag example</h1>

<ol>

<li>
Default date format
--> <strong><s:date name="customDate" /></strong>
</li>

<li>
Date format in "dd/MM/yyyy"
--> <strong><s:date name="customDate" format="dd/MM/yyyy" /></strong>
</li>

<li>
In Date tag, set the nice attribute to "true"
--> <strong><s:date name="customDate" nice="true" /></strong>
</li>

</ol>

</body>
</html> 

3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />
	<package name="default" namespace="/" extends="struts-default">

		<action name="dateTagAction" 
			class="com.mkyong.common.action.DateTagAction" >
			<result name="success">pages/date.jsp</result>
		</action>

	</package>
</struts> 

4.演示

http://localhost:8080/struts 2 example/datetagaction . action

输出

Struts 2 date tag example

参考

  1. Struts 2 日期标签文档

date struts2

Struts 2 日期时间选择器示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-datetimepicker-example/

Download It – Struts2-DateTimePicker-Example.zip

在 Struts 2 中,dojo ajax 标签“<sx:datetime picker>”会呈现一个文本框并在后面追加一个日历图标,点击日历图标会提示一个日期时间选择器组件。

创建一个日期时间选择组件,只需确保:
1。下载 struts2-dojo-plugin.jar 库。
2。包括“struts-dojo-tags”标记,并输出其标题。

 <%@ taglib prefix="sx" uri="/struts-dojo-tags" %>
<html>
<head>
<sx:head />
</head> 

举个例子,

 <%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib prefix="sx" uri="/struts-dojo-tags" %>
<html>
<head>
<sx:head />
</head>
<body>
<sx:datetimepicker name="date2" label="Format (dd-MMM-yyyy)" 
displayFormat="dd-MMM-yyyy" value="%{'2010-01-01'}"/>
... 

产生了下面的 HTML,几个 dojo 和 JavaScript 库来创建一个日期时间选择组件。

 <html> 
<head> 
<script language="JavaScript" type="text/javascript"> 
    // Dojo configuration
    djConfig = {
        isDebug: false,
        bindEncoding: "UTF-8"
          ,baseRelativePath: "/Struts2Example/struts/dojo/"
          ,baseScriptUri: "/Struts2Example/struts/dojo/"
         ,parseWidgets : false
    };
</script> 
<script language="JavaScript" type="text/javascript"
        src="/Struts2Example/struts/dojo/struts_dojo.js"></script> 

<script language="JavaScript" type="text/javascript"
        src="/Struts2Example/struts/ajax/dojoRequire.js"></script> 

<link rel="stylesheet" href="/Struts2Example/struts/xhtml/styles.css" type="text/css"/> 

<script language="JavaScript" src="/Struts2Example/struts/utils.js" 
type="text/javascript"></script> 

<script language="JavaScript" src="/Struts2Example/struts/xhtml/validation.js" 
type="text/javascript"></script> 

<script language="JavaScript" src="/Struts2Example/struts/css_xhtml/validation.js" 
type="text/javascript"></script> 
</head> 
...
<td class="tdLabel">
<label for="widget_1291193434" class="label">Format (dd-MMM-yyyy):
</label></td> 
<td>
<div dojoType="struts:StrutsDatePicker"    id="widget_1291193434"    
value="2010-01-01"    name="date2"    inputName="dojo.date2"    
displayFormat="dd-MMM-yyyy"  saveFormat="rfc"></div> 
</td> 
</tr> 
<script language="JavaScript" type="text/javascript">
djConfig.searchIds.push("widget_1291193434");</script> 

Struts 2 示例

一个完整的完整示例,使用 < s:datetimepicker > 标签生成一个 datetimepicker 组件,并展示如何使用 OGNL 和 Java 属性将默认日期设置为“ datetimepicker ”组件。

1.pom.xml

下载 Struts 2 dojo 依赖库。

pom.xml

 //...
   <!-- Struts 2 -->
   <dependency>
      <groupId>org.apache.struts</groupId>
      <artifactId>struts2-core</artifactId>
      <version>2.1.8</version>
    </dependency>

    <!-- Struts 2 Dojo Ajax Tags -->
    <dependency>
      <groupId>org.apache.struts</groupId>
      <artifactId>struts2-dojo-plugin</artifactId>
      <version>2.1.8</version>
    </dependency>
//... 

2.动作类

存储选定日期的操作类。

date time pick . Java

 package com.mkyong.common.action;

import java.util.Date;
import com.opensymphony.xwork2.ActionSupport;

public class DateTimePickerAction extends ActionSupport{

	private Date date1;
	private Date date2;
	private Date date3;

	//return today date
	public Date getTodayDate(){

		return new Date();
	}

	//getter and setter methods
	public String execute() throws Exception{

		return SUCCESS;
	}

	public String display() {
		return NONE;
	}

} 

3.结果页面

通过“ < s:datetimepicker > ”标签呈现日期时间选择器组件,通过 Java 属性和 OGNL 设置默认日期。

The ‘displayFormat‘ attribute is supported in many date patterns, read this article – Date Format Patterns.Ensure you put the “struts-dojo-tags” tag and render it’s header <sx:head />

 <%@ taglib prefix="sx" uri="/struts-dojo-tags" %>
<html>
<head>
<sx:head />
</head> 

datetimepicker.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib prefix="sx" uri="/struts-dojo-tags" %>
<html>
<head>
<sx:head />
</head>

<body>
<h1>Struts 2 datetimepicker example</h1>

<s:form action="resultAction" namespace="/" method="POST" >

<sx:datetimepicker name="date1" label="Format (dd-MMM-yyyy)" 
displayFormat="dd-MMM-yyyy" value="todayDate" />

<sx:datetimepicker name="date2" label="Format (dd-MMM-yyyy)" 
displayFormat="dd-MMM-yyyy" value="%{'2010-01-01'}"/>

<sx:datetimepicker name="date3" label="Format (dd-MMM-yyyy)" 
displayFormat="dd-MMM-yyyy" value="%{'today'}"/>

<s:submit value="submit" name="submit" />

</s:form>

</body>
</html> 

result.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>

<body>
<h1>Struts 2 datetimepicker example</h1>

<h2>
   Date1 : <s:property value="date1"/> 
</h2> 

<h2>
   Date 2 : <s:property value="date2"/> 
</h2> 

<h2>
   Date 3 : <s:property value="date3"/> 
</h2> 

</body>
</html> 

3.struts.xml

全部链接起来~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 <constant name="struts.devMode" value="true" />

<package name="default" namespace="/" extends="struts-default">

  <action name="dateTimePickerAction" 
	class="com.mkyong.common.action.DateTimePickerAction" 
        method="display">
	<result name="none">pages/datetimepicker.jsp</result>
  </action>

  <action name="resultAction" 
        class="com.mkyong.common.action.DateTimePickerAction" >
	<result name="success">pages/result.jsp</result>
  </action>
</package>

</struts> 

4.演示

http://localhost:8080/struts 2 example/datetimepickeraction . action

Struts 2 datetimepicker exampleStruts 2 datetimepicker example

参考

  1. Struts 2 datetimepicker 文档
  2. Struts 2 ajax 和 javascript 配方
  3. 日期格式模式

date struts2

Struts 2 调试标记示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-debug-tag-example/

Download It – Struts2-Debug-Tag-Example.zip

在 Struts 2 中,“ debug 标签是一个非常有用的调试标签,用来输出“值栈的内容,以及 web 页面中的“栈上下文细节。在本教程中,它展示了在 JSP 页面中使用“ debug ”标记。

1.行动

一个简单的 Action 类,带有一个“ propertyInStack 属性,稍后显示在值堆栈中。

DebugTagAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class DebugTagAction extends ActionSupport{

	public String propertyInStack;

	public String getPropertyInStack() {
		return propertyInStack;
	}

	public void setPropertyInStack(String propertyInStack) {
		this.propertyInStack = propertyInStack;
	}

} 

2.日期标签示例

一个 JSP 页面,显示使用" debug "标签输出系统的"值栈"和"栈上下文"。

debug.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
 <html>
<head>
</head>

<body>
<h1>Struts 2 debug tag example</h1>

<s:debug />

</body>
</html> 

The <s:debug /> will generate a text link named “debug“, you need to click on the text link to expand the debugging details. ## 3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />
	<package name="default" namespace="/" extends="struts-default">

		<action name="debugTagAction" 
			class="com.mkyong.common.action.DebugTagAction" >
			<result name="success">pages/debug.jsp</result>
		</action>

	</package>
</struts> 

4.演示

http://localhost:8080/struts 2 example/debugtagaction . action

输出

Struts 2 debug tag example

参考

  1. Struts 2 调试标签文档

struts2

struts 2–开发模式示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-development-mode-example/

在 Struts 2 开发中,这应该是要学习的第一个可配置值。为了启用 Struts 2 开发模式,您可以通过给予自动配置和属性文件重新加载额外的日志记录和调试特性来显著提高您的 Struts 2 开发速度。

The auto reload feature is really a convenient feature. Each time i made changed in properties or XML configuration file, the application is no longer need to restart to take effect.By default, the Struts 2 development mode is disabled.

启用 Strut2 开发模式

在 struts 属性文件或 XML 配置文件中将" struts.devMode "值设置为 true。

struts.properties

struts.devMode = true

struts.xml

 <struts>
 	<constant name="struts.devMode" value="true" />	
</struts> 

禁用 Strut2 开发模式

在 struts 属性文件或 XML 配置文件中将" struts.devMode "设置为 false。

struts.properties

struts.devMode = false

struts.xml

 <struts>
 	<constant name="struts.devMode" value="false" />	
</struts> 

The development mode is only suitable in development or debugging environment. In production environment, you HAVE TO disabled it. It will caused significant impact on performance, because the entire application configuration, properties files will be reloaded on every request, many extra logging and debug information will be provide also.Before commit Struts configuration file, just make sure the development mode is turn off. I saw many accidentally commit cases – commit with development mode enable, and someone just grab the source code for QA environment. To be Frankly, QA seldom will do the performance test, they just make sure the functionality are there, and end with a development mode enabled application deployed to the production. Guess what? you will receive many screaming phone calls from clients very soon… ## 参考

  1. Struts 2 开发模式文档

struts2

Struts 2 下载文件示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-download-file-example/

Download it – Struts2-Download-File-Example.zip

一个 Struts 2 的例子,展示了使用自定义结果类型来允许用户下载文件。

1.行动

在 Action 类中,声明了 InputStream 数据类型及其 getter 方法。

DownloadAction.java

 package com.mkyong.common.action;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import com.opensymphony.xwork2.ActionSupport;

public class DownloadAction extends ActionSupport{

	private InputStream fileInputStream;

	public InputStream getFileInputStream() {
		return fileInputStream;
	}

	public String execute() throws Exception {
	    fileInputStream = new FileInputStream(new File("C:\\downloadfile.txt"));
	    return SUCCESS;
	}
} 

2.查看页面

一个普通的页面,有一个下载文件的链接。

下载页. jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>

<body>
<h1>Struts 2 download file example</h1>

<s:url id="fileDownload" namespace="/" action="download" ></s:url>

<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script><h2>Download file - <s:a href="%{fileDownload}">fileABC.txt</s:a>
</h2>

</body>
</html> 

3.struts.xml

定义下载文件细节,不言自明。值是动作的 InputStream 属性的名称。

Read this Struts 2 Stream Result documentation for more detail explanation.

struts.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

<constant name="struts.devMode" value="true" />

<package name="default" namespace="/" extends="struts-default">
   <action name="">
	<result name="success">pages/downloadPage.jsp</result>
   </action>

   <action name="download" class="com.mkyong.common.action.DownloadAction">
	<result name="success" type="stream">
	  <param name="contentType">application/octet-stream</param>
	  <param name="inputName">fileInputStream</param>
	  <param name="contentDisposition">attachment;filename="fileABC.txt"</param>
	  <param name="bufferSize">1024</param>
	</result>
   </action>
</package>

</struts> 

4.运行它

http://localhost:8080/struts 2 example/

Struts2 download file example

参考

  1. http://struts.apache.org/2.x/docs/stream-result.html
  2. http://www.iana.org/assignments/media-types/
  3. http://www . mkyong . com/struts/struts-download-file-from-website-example/
  4. http://www . mkyong . com/Java/how-to-download-file-from-website-Java-JSP/
  5. http://struts . Apache . org/2 . x/docs/how-can-we-return-a-text-string-as-the-response . html

download file struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190304031742/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Struts 2 execAndWait 拦截器示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-execandwait-interceptor-example/

Download it – Struts2-ExecAndWait-Interceptor-Example.zip

Struts 2 附带了一个非常有趣的“执行和等待”拦截器,名为“执行和等待”,这是一个非常方便的拦截器,用于在后台长时间运行的操作,同时向用户显示一个自定义的等待页面。在本教程中,它展示了一个使用 Struts 2 execAndWait 拦截器的完整示例。

1.行动

一个普通的 action 类,用一个很长的运行过程来演示 execAndWait 效果。

LongProcessAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class LongProcessAction extends ActionSupport{

	public String execute() throws Exception {

		//it should be delay few seconds, 
		//unless you have a super powerful computer.
		for(int i =0; i<1000000; i++){
			System.out.println(i);
		}
		return SUCCESS;

	}
} 

2.JSP 页面

创建两个页面:

  1. wait.jsp-在处理长时间运行的流程时向用户显示。
  2. success.jsp——流程完成后显示给用户。

HTML meta refresh
Remember to put the meta refresh on top of the waiting page; Otherwise, the page will not redirect to the success page, even the process is completed.

在这个wait.jsp中,元刷新被设置为每 5 秒重新加载一次页面,如果该过程完成,它将重定向到 success.jsp 的,否则停留在同一页面。

wait.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<meta http-equiv="refresh" content="5;url=<s:url includeParams="all" />"/>
</head>

<body>
<h1>Struts 2 execAndWait example</h1>

<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script><h2>Please wait while we process your request...</h2>

</body>
</html> 

success.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 execAndWait example</h1>

<h2>Done</h2>

</body>
</html> 

3.执行和等待拦截器

链接 action 类并声明了" execAndWait "拦截器。

execAndWait parameters

  1. 延迟(可选):显示 wait.jsp 的初始延迟时间,以毫秒为单位。默认为无延迟。
  2. delaySleepInterval(可选):检查后台进程是否已经完成的间隔,以毫秒为单位。默认值为 100 毫秒。

struts.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />

	<package name="default" namespace="/" extends="struts-default">

		<action name="longProcessAction" 
			class="com.mkyong.common.action.LongProcessAction" >

			<interceptor-ref name="execAndWait">
		        <param name="delay">1000</param>
		        <param name="delaySleepInterval">500</param>
		    </interceptor-ref>

		    <result name="wait">pages/wait.jsp</result>
		    <result name="success">pages/success.jsp</result>
		</action>

	</package>

</struts> 

在这种情况下,它将延迟 1 秒显示wait.jsp,并每隔 500 毫秒检查一次后台进程是否已经完成。即使这个过程完成了,仍然需要等待 wait.jsp 的meta refresh 来激活页面重载。

4.演示

访问 URL:http://localhost:8080/struts 2 example/longprocessaction . action

延时 1 秒,显示wait.jsp

Struts 2 ExecAndWait interceptor example

过程完成后,自动显示success.jsp

Struts 2 ExecAndWait interceptor example

参考

  1. Struts 2 execAndWait 拦截器文档
  2. HTML 元刷新

interceptor struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190214232659/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Struts 2 文件上传示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-file-upload-example/

Download It – Struts2-File-Upload-Example.zip

在 Struts 2 中, < s:file > 标签用于创建一个 HTML 文件上传组件,允许用户从本地磁盘选择文件并上传到服务器。在本教程中,您将创建一个带有文件上传组件的 JSP 页面,设置上传文件的最大大小和允许的内容类型,并显示上传文件的详细信息。

1.动作类

Action 类用于文件上传,声明一个“file”变量来存储用户上传的文件,两个 String 变量来存储文件名和内容类型。“ fileUpload 拦截器”将通过 set“X”content type()和 set“X”FileName()自动注入上传的文件细节,确保方法名拼写正确。

P.S X 是存储上传文件的变量。

The file upload function is depends on the “fileUpload Interceptor“, make sure it is included in the Action’s stack. The lucky is, the default stack is already includes the “fileUpload Interceptor“.

FileUploadAction.java

 package com.mkyong.common.action;

import java.io.File;

import com.opensymphony.xwork2.ActionSupport;

public class FileUploadAction extends ActionSupport{

	private File fileUpload;
	private String fileUploadContentType;
	private String fileUploadFileName;

	public String getFileUploadContentType() {
		return fileUploadContentType;
	}

	public void setFileUploadContentType(String fileUploadContentType) {
		this.fileUploadContentType = fileUploadContentType;
	}

	public String getFileUploadFileName() {
		return fileUploadFileName;
	}

	public void setFileUploadFileName(String fileUploadFileName) {
		this.fileUploadFileName = fileUploadFileName;
	}

	public File getFileUpload() {
		return fileUpload;
	}

	public void setFileUpload(File fileUpload) {
		this.fileUpload = fileUpload;
	}

	public String execute() throws Exception{

		return SUCCESS;

	}

	public String display() {
		return NONE;
	}

} 

2.结果页面

使用 < s:file > 标签渲染一个文件上传组件,将表单 enctype 类型设置为“multipart/form-data”

fileupload.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<s:head />
</head>

<body>
<h1>Struts 2 <s:file> file upload example</h1>

<s:form action="resultAction" namespace="/" 
method="POST" enctype="multipart/form-data">

<s:file name="fileUpload" label="Select a File to upload" size="40" />

<s:submit value="submit" name="submit" />

</s:form>

</body>
</html> 

result.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>

<body>
<h1>Struts 2 <s:file> file upload example</h1>

<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script><h2>
   File Name : <s:property value="fileUploadFileName"/> 
</h2> 

<h2>
   Content Type : <s:property value="fileUploadContentType"/> 
</h2> 

<h2>
   File : <s:property value="fileUpload"/> 
</h2> 

</body>
</html> 

3.struts.xml

全部链接起来~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 	<constant name="struts.devMode" value="true" />
 	<constant name="struts.custom.i18n.resources" value="global" />

	<package name="default" namespace="/" extends="struts-default">

	<action name="fileUploadAction" 
	    class="com.mkyong.common.action.FileUploadAction" method="display">
	    <result name="none">pages/fileupload.jsp</result>
	</action>

	<action name="resultAction" class="com.mkyong.common.action.FileUploadAction">
	    <interceptor-ref name="exception"/>
            <interceptor-ref name="i18n"/>
            <interceptor-ref name="fileUpload">
       		<param name="allowedTypes">text/plain</param>
       		<param name="maximumSize">10240</param>
  	    </interceptor-ref> 
            <interceptor-ref name="params">
                <param name="excludeParams">dojo\..*,^struts\..*</param>
            </interceptor-ref>
            <interceptor-ref name="validation">
                <param name="excludeMethods">input,back,cancel,browse</param>
            </interceptor-ref>
            <interceptor-ref name="workflow">
                <param name="excludeMethods">input,back,cancel,browse</param>
            </interceptor-ref>

	    <result name="success">pages/result.jsp</result>
	    <result name="input">pages/fileupload.jsp</result>

	</action>
   </package>	
</struts> 

文件大小限制
在本例中,您通过“ fileUpload interceptor 设置上传文件大小限制,该值以字节为单位计数。在这种情况下,上传文件的最大大小为 10kb。

The default maximum file size of the upload file is 2MB

文件类型
您也可以通过文件上传拦截器设置允许的文件类型。在这种情况下,上传文件只接受" text/plain 内容类型。

In Struts 2, there are may ways to lead same Rome, make sure you check this Struts 2 file upload documentation.

4.演示

http://localhost:8080/struts 2 example/file upload action . action

Struts 2 file upload example

如果您上传的文件大于 10kb,或者不是文本文件,则会出现错误消息。

Struts 2 file upload error page

上传一个名为“XWORK-LICENSE.txt”的文本文件,文件大小:5kb。

Struts 2 file upload example

上传的文件将被视为临时文件,具有长的随机文件名,upload _ _ 376584 a7 _ 12981122379 _ _ 8000 _ 0000010 . tmp。请确保将此临时文件复制到其他地方。阅读 FileUtils 文档轻松复制文件。

参考

  1. Struts 2 文件文档
  2. http://struts.apache.org/2.0.14/docs/file-upload.html
  3. http://struts . Apache . org/2 . 0 . 14/docs/how-do-we-upload-files . html
  4. http://commons . Apache . org/io/API-1.4/org/Apache/commons/io/fileutils . html
  5. http://www.mkyong.com/struts/struts-file-upload-example/

file upload struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190210101631/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Struts 2 生成器标记示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-generator-tag-example/

Download It – Struts2-Generator-Tag-Example.zip

Struts 2 生成器标签用于根据页面中提供的“ val 属性生成一个迭代器。在本教程中,您将使用 Struts 2 生成器标签来完成以下任务:

  1. 用生成器标签创建一个迭代器。
  2. 创建一个带有生成器标签的迭代器,并用“转换器对象修改迭代器值。

1.行动

一个操作类,其方法返回一个“转换器对象。

生成或动作

 package com.mkyong.common.action;

import org.apache.struts2.util.IteratorGenerator.Converter;
import com.opensymphony.xwork2.ActionSupport;

public class GeneratorTagAction extends ActionSupport{

	public String execute() {

		return SUCCESS;
	}

	public Converter getLanguageConverter(){
		return new Converter() {
	         public Object convert(String value) throws Exception {

	        	 if("java".equals(value)){
	        		 return "[java value in converter] - " + value;
	        	 }else{
	        		 return value;
	        	 }

	         }
	     };
	}
} 

2.生成器标签示例

一个 JSP 页面,展示了如何使用生成器标签来动态创建迭代器。“分隔符属性是必需的,它将 val 分隔成迭代器的条目。

转换器属性是可选的,允许您修改该值。在这种情况下,它将调用 GeneratorTagAction 的 getLanguageConverter() 方法,如果值等于“java”字符串,则修改该值。

generator.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
 <html>
<head>
</head>

<body>
<h1>Struts 2 Generator tag example</h1>

1\. Generator tag example.
<s:generator val="%{'java|.net|c|python|shell'}" separator="|">
<ol>
<s:iterator>
  <li><s:property /></li>
</s:iterator>
</s:generator>
</ol>  

2\. Generator tag with converter example
<s:generator val="%{'java|.net|c|python|shell'}" separator="|" 
converter="%{languageConverter}">
<ol>
<s:iterator>
  <li><s:property /></li>
</s:iterator>
</s:generator>
</ol>  

</body>
</html> 

Can’t find any use case of this generator tag, as i don’t recommend to hardcore the iterator values in the page. ## 3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 	<constant name="struts.devMode" value="true" />

	<package name="default" namespace="/" extends="struts-default">

		<action name="appendTagAction" 
			class="com.mkyong.common.action.AppendTagAction" >
			<result name="success">pages/appendIterator.jsp</result>
		</action>

	</package>

</struts> 

4.演示

http://localhost:8080/struts 2 example/generatortagaction . action

Struts 2 generator tag

参考

  1. Struts 2 生成器文档
  2. Struts 2 转换器文档

struts2

Struts 2 Hello World 注释示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-hello-world-annotation-example/

在本教程中,它将重用之前的 Strust 2 Hello World (XML 版本)示例,并将其转换为注释版本。

Download It – Struts2-Hello-World-Annotation-Example.zip

Struts 2 注释概念

Struts 2 注释是由 Struts 2 约定插件支持的,因此,您必须理解其“扫描方法学”和“命名转换器”机制背后的魔力。

1.扫描方法

许多 Struts 2 的文章或书籍指出,您可以配置过滤器的" init-param 或"Struts . conventi on . action . packages"来告诉 Struts 2 在哪里扫描带注释的类。举个例子,

web.xml

 <filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
  <init-param>
	<param-name>actionPackages</param-name>
	<param-value>com.mkyong.common</param-value>
  </init-param>
</filter> 

从我的测试(Struts2 版本 2.1.6 和 2.1.8)来看,这是不正确的,无论你在" param-value 或"Struts . conventi on . action . packages中放什么,struts2 都会忽略它,只扫描指定的名为 struts、Struts 2、action 或 actions 的文件夹。

这是扫描的工作原理

  1. 扫描位于名为“ struts,struts2,action 或 actions ”的包中的注释类。
  2. 接下来,扫描符合以下任一标准的文件:
    • 实现了com . open symphony . xwork 2 . action接口。
    • 扩展com . open symphony . xwork 2 . action support类。
    • 文件名以动作结尾(如用户动作、登录动作)。

参见这个 Struts 2 约定插件文档

2.命名转换器

Struts 2 约定插件将把所有带注释的动作文件名转换成指定的格式。

比如:LoginAction.java

  1. 首先,删除文件名末尾的“操作”一词(如果有)。
  2. 其次,将文件名的第一个字母转换成小写。

因此,在删除结尾并转换第一个字母的大小写后, LoginAction.action 将变为 login.action

The Struts 2 convention plugin’s “scanning methodology” and “naming converter” features are really bring a lot of conveniences and benefits, only if your Struts 2 project is following the naming convention properly; otherwise it will be a total disaster.

Struts 2 注释示例

是时候开始转换过程了。

最终项目结构

Struts2 hello world annotation

1.更新 pom.xml

要使用 Struts 2 注释特性,您需要下载Struts 2-conventi on-plugin . jar
POM . XML

 ...
    <dependency>
          <groupId>org.apache.struts</groupId>
	  <artifactId>struts2-core</artifactId>
	  <version>2.1.8</version>
    </dependency>

    <dependency>
          <groupId>org.apache.struts</groupId>
	  <artifactId>struts2-convention-plugin</artifactId>
	  <version>2.1.8</version>
    </dependency>
... 

2.登录操作

创建一个 LoginAction 扩展 ActionSupport 并且什么都不做, ActionSupport 默认返回一个“success”字符串,它将匹配@Result 并重定向到“ pages/login.jsp ”。

注释版本

 package com.mkyong.user.action;

import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.ResultPath;

import com.opensymphony.xwork2.ActionSupport;

@Namespace("/User")
@ResultPath(value="/")
@Result(name="success",location="pages/login.jsp")
public class LoginAction extends ActionSupport{

} 

XML 等效物

 <package name="user" namespace="/User" extends="struts-default">
	<action name="Login">
		<result>pages/login.jsp</result>
	</action>
</package> 

3.WelcomeUserAction

重写 execute()方法并指定@Action 和@Result 批注。

注释版本

 package com.mkyong.user.action;

import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.ResultPath;

import com.opensymphony.xwork2.ActionSupport;

@Namespace("/User")
@ResultPath(value="/")
public class WelcomeUserAction extends ActionSupport{

	private String username;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	@Action(value="Welcome", results={
		@Result(name="success",location="pages/welcome_user.jsp")
	})
	public String execute() {

		return SUCCESS;

	}
} 

XML 等效物

 <package name="user" namespace="/User" extends="struts-default">
   <action name="Welcome" class="com.mkyong.user.action.WelcomeUserAction">
	<result name="SUCCESS">pages/welcome_user.jsp</result>
   </action>
</package> 

The Struts 2 annotations – @Action, @Result and @Namespace are self-explanatory, you can always compare it with the XML equivalent. The @ResultPath may need a bit explanation, see this @ResultPath example.

4.JSP 视图页面

普通 JSP 视图页面接受用户名和密码,并在单击提交按钮后重定向到欢迎页面。

login.jsp

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

Struts 2 Hello World 注释示例

**welcome_user.jsp**
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

Struts 2 Hello World 注释示例

你好

5.struts.xml

不需要创建 struts.xml 文件,所有类都有注释。

6.web.xml

只需创建一个经典的 web.xml 文件,并将 FilterDispatcher 过滤器声明为普通过滤器。

 <!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Struts 2 Web Application</display-name>

  <filter>
	<filter-name>struts2</filter-name>
	<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
  </filter>

  <filter-mapping>
	<filter-name>struts2</filter-name>
	<url-pattern>/*</url-pattern>
  </filter-mapping>

</web-app> 

7.运行它

登录动作改为登录动作,参见上面的“命名转换器”。
http://localhost:8080/struts 2 example/User/log in . action

Struts 2 annotation login screen

http://localhost:8080/struts 2 example/User/welcome . action

Struts 2 annotation welcome screen

参考

  1. Struts 2 约定插件文档
  2. Strust 2 Hello World (XML 版)

annotation hello world struts2

Struts 2 Hello World 示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-hello-world-example/

在这个例子中,我们向您展示了如何在 Struts 2 中创建一个 hello world 示例。

使用了以下库或工具:

  • maven3
  • Eclipse 3.7
  • struts 2.3.1.2

1.最终项目结构

让我们回顾一下本教程的最终项目结构,以防您在后面的步骤中迷路。

struts2 foder structurefreestar.config.enabled_slots.push({ placementName: "mkyong_incontent_1", slotId: "mkyong_incontent_1" });

2.Struts2 依赖关系

使用 Maven 下载整个 Struts2 依赖项。在pom.xml中增加struts2-core

文件:pom.xml

 <project  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.mkyong.common</groupId>
	<artifactId>Struts2Example</artifactId>
	<packaging>war</packaging>
	<version>com.mkyong.common</version>
	<name>Struts2Example Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.apache.struts</groupId>
			<artifactId>struts2-core</artifactId>
			<version>2.3.1.2</version>
		</dependency>
	</dependencies>
	<build>
		<finalName>Struts2Example</finalName>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project> 

3.转换为 Eclipse 项目

在命令提示符下编译并转换为 Eclipse web 项目:

 mvn eclipse:eclipse -Dwtpversion=2.0 

回顾日食。类路径文件中,将下载以下 Struts2 依赖项:

文件:。类路径

 <classpath>
  <classpathentry kind="src" path="src/main/java" including="**/*.java"/>
  <classpathentry kind="src" path="src/main/resources" excluding="**/*.java"/>
  <classpathentry kind="output" path="target/classes"/>
  <classpathentry kind="var" path="M2_REPO/asm/asm/3.3/asm-3.3.jar"/>
  <classpathentry kind="var" path="M2_REPO/asm/asm-commons/3.3/asm-commons-3.3.jar"/>
  <classpathentry kind="var" path="M2_REPO/asm/asm-tree/3.3/asm-tree-3.3.jar"/>
  <classpathentry kind="var" path="M2_REPO/commons-fileupload/commons-fileupload/1.2.2/commons-fileupload-1.2.2.jar" />
  <classpathentry kind="var" path="M2_REPO/commons-io/commons-io/2.0.1/commons-io-2.0.1.jar"/>
  <classpathentry kind="var" path="M2_REPO/commons-lang/commons-lang/2.5/commons-lang-2.5.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/freemarker/freemarker/2.3.18/freemarker-2.3.18.jar"/>
  <classpathentry kind="var" path="M2_REPO/javassist/javassist/3.11.0.GA/javassist-3.11.0.GA.jar"/>
  <classpathentry kind="var" path="M2_REPO/junit/junit/3.8.1/junit-3.8.1.jar"/>
  <classpathentry kind="var" path="M2_REPO/ognl/ognl/3.0.4/ognl-3.0.4.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/apache/struts/struts2-core/2.3.1.2/struts2-core-2.3.1.2.jar"/>
  <classpathentry kind="lib" path="C:/Program Files/Java/jdk1.6.0_13/lib/tools.jar"/>
  <classpathentry kind="var" path="M2_REPO/org/apache/struts/xwork/xwork-core/2.3.1.2/xwork-core-2.3.1.2.jar"/>
  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
</classpath> 

4.JSP 视图页面

一个 JSP 登录页面,使用 Struts 2 标记来显示用户名和密码输入字段以及提交按钮。

login.jsp

 <%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head></head>
<body>
	<h1>Struts 2 Hello World Example</h1>

	<s:form action="Welcome">
		<s:textfield name="username" label="Username" />
		<s:password name="password" label="Password" />
		<s:submit />
	</s:form>

</body>
</html> 

File:welcome _ user . jsp–向用户显示欢迎消息的 JSP 视图页面。

 <%@ page contentType="text/html; charset=UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<html>
<head></head>
<body>
	<h1>Struts 2 Hello World Example</h1>

	<h2>
		Hello
		<s:property value="username" />
	</h2>

</body>
</html> 

Struts 1 和 Struts 2 具有非常相似的 UI 标记语法,只是在命名 HTML 元素方面有一点不同,例如:

支柱 1

 <%@taglib uri="http://struts.apache.org/tags-html" prefix="html"%>
<html:form action="Welcome">
   <html:text property="username"/>
</html:form> 

支柱 2

 <%@ taglib prefix="s" uri="/struts-tags" %>
<s:form action="Welcome">
	<s:textfield name="username" label="Username"/>
</s:form> 

5.动作,把所有的业务逻辑放在这里

一个简单的 Struts2 Action 类,它用来声明里面所有的业务逻辑。

文件:WelcomeUserAction.java

 package com.mkyong.user.action;

public class WelcomeUserAction{

	private String username;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	// all struts logic here
	public String execute() {

		return "SUCCESS";

	}
} 

在 Struts2 中,Action 类不需要实现任何接口或扩展任何类,但需要创建一个execute()方法来将所有业务逻辑放入其中,并返回一个字符串值来告诉用户重定向到哪里。

Note
You may see some users implement the com.opensymphony.xwork2.Action class, but it’s totally optional, because the com.opensymphony.xwork2.Action is just provide some handy constant values only.Note
Struts1’s Action class is required to extends the org.apache.struts.action.Action. But Struts 2 Action class is optional, but you are still allow to implement the com.opensymphony.xwork2.Action for some handy constant values or extends the com.opensymphony.xwork2.ActionSupport for some common default Action implementation functions.

5.Struts 配置文件

一个 Strut 配置文件将所有的东西链接在一起。XML 文件名必须是“struts . XML”

文件:struts.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

	<package name="user" namespace="/User" extends="struts-default">
		<action name="Login">
			<result>pages/login.jsp</result>
		</action>
		<action name="Welcome" class="com.mkyong.user.action.WelcomeUserAction">
			<result name="SUCCESS">pages/welcome_user.jsp</result>
		</action>
	</package>

</struts> 

声明一个包,并将动作类放入其中,动作类是自解释的,但是您可能会对下面的新标签感兴趣:

1。package name = " user "
只是一个包名,不用太在意。

2。namespace="/User"
用于匹配"/User" URL 模式。参见本文—Struts 2 名称空间示例和解释

Note
Actually, the Struts2 Namespaces is equivalent to Struts 1 multiple modules

3。extends = " struts-default "
这意味着该包扩展了 struts-default 包的组件和拦截器,这在位于 struts2-core.jar 文件根目录下的 struts-default.xml 文件中声明。

6.web.xml

配置 web 应用程序部署描述符(web.xml)文件,将 Struts2 集成到您的 Web 项目中。

文件 web.xml

 <!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
	<display-name>Struts 2 Web Application</display-name>

	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>
                org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
                </filter-class>
	</filter>

	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app> 

7.运行它

在 Struts2 中,可以用后缀直接访问 action 类。动作

http://localhost:8080/struts 2 example/User/log in . action

struts2 hello world example1

http://localhost:8080/struts 2 example/User/welcome . action

struts2 hello world example2

下载它

Download it – Struts2-Hello-World-Example.zip (7 KB)Tags : hello world struts2freestar.config.enabled_slots.push({ placementName: "mkyong_leaderboard_btf", slotId: "mkyong_leaderboard_btf" });

Struts 2 + Hibernate 集成示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-hibernate-integration-example/

Download it – Struts2-Hibernate-Integration-Example.zip

在 Struts2 中,没有集成 Hibernate 框架的官方插件。但是,您可以通过以下步骤解决这个问题:

  1. 注册一个自定义的 ServletContextListener
  2. ServletContextListener 类中,初始化 Hibernate 会话,并将其存储到 servlet 上下文中。
  3. 在 action 类中,从 servlet 上下文中获取 Hibernate 会话,并正常执行 Hibernate 任务。

查看关系:

 Struts 2 <-- (Servlet Context) ---> Hibernate <-----> Database 

在本教程中,展示了一个简单的客户模块(添加和列表功能),用 Struts 2 开发,用 Hibernate 执行数据库操作。集成部分使用上述机制(在 servlet 上下文中存储和检索 Hibernate 会话)。

This workaround is very similar with the classic Struts 1.x and Hibernate integration, just the classic Struts 1.x is using the Struts’s plugins; While the Struts 2 is using the generic servlet listener.

1.项目结构

查看完整的项目文件夹结构。

Struts 2 Hibernate integration example ## 2.MySQL 表脚本

为演示创建一个客户表。下面是 SQL 表脚本。

 DROP TABLE IF EXISTS `mkyong`.`customer`;
CREATE TABLE  `mkyong`.`customer` (
  `CUSTOMER_ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `NAME` varchar(45) NOT NULL,
  `ADDRESS` varchar(255) NOT NULL,
  `CREATED_DATE` datetime NOT NULL,
  PRIMARY KEY (`CUSTOMER_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8; 

3.属国

获取 Struts2、Hibernate 和 MySQL 依赖库。

 //...
	<!-- Struts 2 -->
	<dependency>
	        <groupId>org.apache.struts</groupId>
		<artifactId>struts2-core</artifactId>
		<version>2.1.8</version>
        </dependency>

	<!-- MySQL database driver -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.9</version>
	</dependency>

	<!-- Hibernate core -->
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate</artifactId>
		<version>3.2.7.ga</version>
	</dependency>

	<!-- Hibernate core library dependency start -->
	<dependency>
		<groupId>dom4j</groupId>
		<artifactId>dom4j</artifactId>
		<version>1.6.1</version>
	</dependency>

	<dependency>
		<groupId>commons-logging</groupId>
		<artifactId>commons-logging</artifactId>
		<version>1.1.1</version>
	</dependency>

	<dependency>
		<groupId>commons-collections</groupId>
		<artifactId>commons-collections</artifactId>
		<version>3.2.1</version>
	</dependency>

	<dependency>
		<groupId>cglib</groupId>
		<artifactId>cglib</artifactId>
		<version>2.2</version>
	</dependency>
	<!-- Hibernate core library dependency end -->

	<!-- Hibernate query library dependency start -->
	<dependency>
		<groupId>antlr</groupId>
		<artifactId>antlr</artifactId>
		<version>2.7.7</version>
	</dependency>
	<!-- Hibernate query library dependency end -->
//... 

4.冬眠的东西

Hibernate 模型和配置材料。

Customer.java–为客户表创建一个类。

 package com.mkyong.customer.model;

import java.util.Date;

public class Customer implements java.io.Serializable {

	private Long customerId;
	private String name;
	private String address;
	private Date createdDate;

	//getter and setter methods
} 

customer . hbm . XML–客户的 Hibernate 映射文件。

 <?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.mkyong.customer.model.Customer" 
	table="customer" catalog="mkyong">

        <id name="customerId" type="java.lang.Long">
            <column name="CUSTOMER_ID" />
            <generator class="identity" />
        </id>
        <property name="name" type="string">
            <column name="NAME" length="45" not-null="true" />
        </property>
        <property name="address" type="string">
            <column name="ADDRESS" not-null="true" />
        </property>
        <property name="createdDate" type="timestamp">
            <column name="CREATED_DATE" length="19" not-null="true" />
        </property>
    </class>
</hibernate-mapping> 

Hibernate . CFG . XML–Hibernate 数据库配置文件。

 <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
    <property name="hibernate.bytecode.use_reflection_optimizer">false</property>
    <property name="hibernate.connection.password">password</property>
    <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mkyong</property>
    <property name="hibernate.connection.username">root</property>
    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="show_sql">true</property>
    <property name="format_sql">true</property>
    <property name="use_sql_comments">false</property>
    <mapping resource="com/mkyong/customer/hibernate/Customer.hbm.xml" />
  </session-factory>
</hibernate-configuration> 

5.hibernate 上下文侦听器

创建一个 ServletContextListener ,初始化 Hibernate 会话,并将其存储到 servlet context 中。

冬眠监听器。java

 package com.mkyong.listener;

import java.net.URL;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateListener implements ServletContextListener{

    private Configuration config;
    private SessionFactory factory;
    private String path = "/hibernate.cfg.xml";
    private static Class clazz = HibernateListener.class;

    public static final String KEY_NAME = clazz.getName();

	public void contextDestroyed(ServletContextEvent event) {
	  //
	}

	public void contextInitialized(ServletContextEvent event) {

	 try { 
	        URL url = HibernateListener.class.getResource(path);
	        config = new Configuration().configure(url);
	        factory = config.buildSessionFactory();

	        //save the Hibernate session factory into serlvet context
	        event.getServletContext().setAttribute(KEY_NAME, factory);
	  } catch (Exception e) {
	         System.out.println(e.getMessage());
	   }
	}
} 

在 web.xml 文件中注册侦听器。
web.xml

 <!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Struts 2 Web Application</display-name>

  <filter>
	<filter-name>struts2</filter-name>
	<filter-class>
	  org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
	</filter-class>
  </filter>

  <filter-mapping>
	<filter-name>struts2</filter-name>
	<url-pattern>/*</url-pattern>
  </filter-mapping>

  <listener>
    <listener-class>
	  com.mkyong.listener.HibernateListener
    </listener-class>
  </listener>

</web-app> 

6.行动

在 Action 类中,从 servlet 上下文中获取 Hibernate 会话,并正常执行 Hibernate 任务。

CustomerAction.java

 package com.mkyong.customer.action;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.struts2.ServletActionContext;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

import com.mkyong.customer.model.Customer;
import com.mkyong.listener.HibernateListener;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

public class CustomerAction extends ActionSupport 
	implements ModelDriven{

	Customer customer = new Customer();
	List<Customer> customerList = new ArrayList<Customer>();

	public String execute() throws Exception {
		return SUCCESS;
	}

	public Object getModel() {
		return customer;
	}

	public List<Customer> getCustomerList() {
		return customerList;
	}

	public void setCustomerList(List<Customer> customerList) {
		this.customerList = customerList;
	}

	//save customer
	public String addCustomer() throws Exception{

		//get hibernate session from the servlet context
		SessionFactory sessionFactory = 
	         (SessionFactory) ServletActionContext.getServletContext()
                     .getAttribute(HibernateListener.KEY_NAME);

		Session session = sessionFactory.openSession();

		//save it
		customer.setCreatedDate(new Date());

		session.beginTransaction();
		session.save(customer);
		session.getTransaction().commit();

		//reload the customer list
		customerList = null;
		customerList = session.createQuery("from Customer").list();

		return SUCCESS;

	}

	//list all customers
	public String listCustomer() throws Exception{

		//get hibernate session from the servlet context
		SessionFactory sessionFactory = 
	         (SessionFactory) ServletActionContext.getServletContext()
                     .getAttribute(HibernateListener.KEY_NAME);

		Session session = sessionFactory.openSession();

		customerList = session.createQuery("from Customer").list();

		return SUCCESS;

	}	
} 

7.JSP 页面

添加和列出客户的 JSP 页面。

customer.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 + Hibernate integration example</h1>

<h2>Add Customer</h2>
<s:form action="addCustomerAction" >
  <s:textfield name="name" label="Name" value="" />
  <s:textarea name="address" label="Address" value="" cols="50" rows="5" />
  <s:submit />
</s:form>

<h2>All Customers</h2>

<s:if test="customerList.size() > 0">
<table border="1px" cellpadding="8px">
	<tr>
		<th>Customer Id</th>
		<th>Name</th>
		<th>Address</th>
		<th>Created Date</th>
	</tr>
	<s:iterator value="customerList" status="userStatus">
		<tr>
			<td><s:property value="customerId" /></td>
			<td><s:property value="name" /></td>
			<td><s:property value="address" /></td>
			<td><s:date name="createdDate" format="dd/MM/yyyy" /></td>
		</tr>
	</s:iterator>
</table>
</s:if>
<br/>
<br/>

</body>
</html> 

8.struts.xml

全部链接起来~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <constant name="struts.devMode" value="true" />

  <package name="default" namespace="/" extends="struts-default">

    <action name="addCustomerAction" 
	class="com.mkyong.customer.action.CustomerAction" method="addCustomer" >
       <result name="success">pages/customer.jsp</result>
    </action>

    <action name="listCustomerAction" 
	class="com.mkyong.customer.action.CustomerAction" method="listCustomer" >
        <result name="success">pages/customer.jsp</result>
    </action>		

  </package>	
</struts> 

9.演示

访问客户模块:http://localhost:8080/struts 2 example/listcustomeraction . action

Struts 2 Hibernate Add Customer

填写姓名和地址字段,点击提交按钮,插入的客户详细信息将立即列出。

Struts2 Hibernate List Customer

参考

  1. Struts 2 + Hibernate 与“全 Hibernate 插件”的集成
  2. ServletContextListener 文档
  3. Struts + Hibernate 集成示例

hibernate integration struts2

Struts 2 + Hibernate 与“完全 Hibernate 插件”的集成

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-hibernate-integration-with-full-hibernate-plugin/

在最后一个 Struts 2 + Hibernate 集成的例子中,它使用 servlet 上下文监听器来处理 Hibernate 会话,并且很好地将 Struts 2 与 Hibernate 框架集成在一起。

但是,总有需要改进的地方:)在本教程中,我们将向您展示如何通过使用由 jyoshiriro 创建的名为“ Full Hibernate Plugin ”的 2.2GA 版本的 Struts2 插件来集成 Struts2 和 Hibernate。

查看集成步骤摘要:

  1. 将“ Full Hibernate 插件jar”放在项目类路径中。
  2. 使用批注" @SessionTarget "注入 Hibernate 会话;而“ @TransactionTarget ”则注入 Hibernate 事务。
  3. struts.xml 中,使包扩展“ hibernate-default ”,而不是默认栈。

查看关系:

 Struts 2 <-- (Full Hibernate Plugin) ---> Hibernate <-----> Database 

Note
This tutorial is an update version from the previous Struts 2 + Hibernate integration example (servlet context listener), So the JSP and Hibernate configuration are almost same, just the integration part is a bit different, try to compare both to spot the different.

1.项目结构

查看完整的项目文件夹结构。

Struts 2 Hibernate plugin folder structure ## 2.MySQL 表脚本

客户的表脚本。

 DROP TABLE IF EXISTS `mkyong`.`customer`;
CREATE TABLE  `mkyong`.`customer` (
  `CUSTOMER_ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `NAME` varchar(45) NOT NULL,
  `ADDRESS` varchar(255) NOT NULL,
  `CREATED_DATE` datetime NOT NULL,
  PRIMARY KEY (`CUSTOMER_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8; 

3.获取“完整的 Hibernate 插件”和依赖项

获取所有 Struts2、Hibernate、 Full Hibernate 插件和 MySQL 依赖库。由于“全 Hibernate 插件”不支持 Maven,您需要从官网下载并手动包含到您的 Maven 本地库。

  1. 下载“全休眠插件”。

  2. 将下载的 jar 放到 c: drive 中,并使用 Maven 的命令将其包含到 Maven 本地存储库中。

     mvn install:install-file -Dfile=c:\struts2-fullhibernatecore-plugin-2.2-GA.jar 
    -DgroupId=com.google.code -DartifactId=struts2-fullhibernatecore-plugin 
    -Dversion=2.2 -Dpackaging=jar 
    
  3. 用跟随 Maven 坐标链接它。

     <dependency>
    		<groupId>com.google.code</groupId>
    		<artifactId>struts2-fullhibernatecore-plugin</artifactId>
    		<version>2.2</version>
    	</dependency> 
    

以下是本教程中的所有依赖库:

文件:pom.xml

 //...
	<!-- Struts 2 -->
	<dependency>
	        <groupId>org.apache.struts</groupId>
		<artifactId>struts2-core</artifactId>
		<version>2.1.8</version>
        </dependency>

	<!-- MySQL database driver -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.9</version>
	</dependency>

	<!-- Struts 2 Hibernate Plugins -->
	<dependency>
		<groupId>com.google.code</groupId>
		<artifactId>struts2-fullhibernatecore-plugin</artifactId>
		<version>2.2</version>
	</dependency>

	<!-- Log4j logging (Struts 2 Hibernate Plugins dependency) -->
	<dependency>
                <groupId>log4j</groupId>
	        <artifactId>log4j</artifactId>
	        <version>1.2.9</version>
        </dependency>

	<!-- Hibernate validator (Struts 2 Hibernate Plugins dependency) -->
	<dependency>
               <groupId>org.hibernate</groupId>
	       <artifactId>hibernate-validator</artifactId>
	       <version>3.1.0.GA</version>
        </dependency>

	<!-- slf4j logging (Struts 2 Hibernate Plugins dependency) -->
	<dependency>
               <groupId>org.slf4j</groupId>
	       <artifactId>slf4j-api</artifactId>
	       <version>1.6.1</version>
        </dependency>

	<!-- Hibernate core -->
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate</artifactId>
		<version>3.2.7.ga</version>
	</dependency>

	<!-- Hibernate core library dependency start -->
	<dependency>
		<groupId>dom4j</groupId>
		<artifactId>dom4j</artifactId>
		<version>1.6.1</version>
	</dependency>

	<dependency>
		<groupId>commons-logging</groupId>
		<artifactId>commons-logging</artifactId>
		<version>1.1.1</version>
	</dependency>

	<dependency>
		<groupId>commons-collections</groupId>
		<artifactId>commons-collections</artifactId>
		<version>3.2.1</version>
	</dependency>

	<dependency>
		<groupId>cglib</groupId>
		<artifactId>cglib</artifactId>
		<version>2.2</version>
	</dependency>
	<!-- Hibernate core library dependency end -->

	<!-- Hibernate query library dependency start -->
	<dependency>
		<groupId>antlr</groupId>
		<artifactId>antlr</artifactId>
		<version>2.7.7</version>
	</dependency>
	<!-- Hibernate query library dependency end -->
//... 

The “Full Hibernate Plugin” is required the Hibernate validator and SLF4j dependency, which is not really make sense, as most of the Java developers still do not use it.

4.冬眠的东西

所有的 Hibernate 模型和配置。

Customer.java–为客户表创建一个类。

 package com.mkyong.customer.model;

import java.util.Date;

public class Customer implements java.io.Serializable {

	private Long customerId;
	private String name;
	private String address;
	private Date createdDate;

	//getter and setter methods
} 

customer . hbm . XML–客户的 Hibernate 映射文件。

 <?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 20 Julai 2010 11:40:18 AM by Hibernate Tools 3.2.5.Beta -->
<hibernate-mapping>
    <class name="com.mkyong.customer.model.Customer" 
		table="customer" catalog="mkyong">
        <id name="customerId" type="java.lang.Long">
            <column name="CUSTOMER_ID" />
            <generator class="identity" />
        </id>
        <property name="name" type="string">
            <column name="NAME" length="45" not-null="true" />
        </property>
        <property name="address" type="string">
            <column name="ADDRESS" not-null="true" />
        </property>
        <property name="createdDate" type="timestamp">
            <column name="CREATED_DATE" length="19" not-null="true" />
        </property>
    </class>
</hibernate-mapping> 

文件:hibernate.cfg.xml,hibernate 数据库配置文件。

 <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
    <property name="hibernate.bytecode.use_reflection_optimizer">false</property>
    <property name="hibernate.connection.password">password</property>
    <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mkyong</property>
    <property name="hibernate.connection.username">root</property>
    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="show_sql">true</property>
    <property name="format_sql">true</property>
    <property name="use_sql_comments">false</property>
    <mapping resource="com/mkyong/customer/hibernate/Customer.hbm.xml" />
  </session-factory>
</hibernate-configuration> 

5.数据访问对象(Data Access Object)

实现 DAO 设计模式来执行数据库操作。在 CustomerDAOImpl 类中,将 Hibernate 会话和事务都声明为类成员。在 Struts 2 项目初始化过程中,全 Hibernate 插件会分别使用 @SessionTarget@TransactionTarget 注释将相应的 Hibernate 会话和事务注入到类成员中。

CustomerDAO.java

 package com.mkyong.customer.dao;

import java.util.List;

import com.mkyong.customer.model.Customer;

public interface CustomerDAO{

	void addCustomer(Customer customer);

	List<Customer> listCustomer();

} 

CustomerDAOImpl.java

 package com.mkyong.customer.dao.impl;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.Transaction;

import com.googlecode.s2hibernate.struts2.plugin.annotations.SessionTarget;
import com.googlecode.s2hibernate.struts2.plugin.annotations.TransactionTarget;
import com.mkyong.customer.dao.CustomerDAO;
import com.mkyong.customer.model.Customer;

public class CustomerDAOImpl implements CustomerDAO{

	@SessionTarget
	Session session;

	@TransactionTarget
	Transaction transaction;

	//add the customer
	public void addCustomer(Customer customer){

		session.save(customer);

	}

	//return all the customers in list
	public List<Customer> listCustomer(){

		return session.createQuery("from Customer").list();

	}

} 

6.行动

在 Action 类中,调用 DAO 类来执行数据库操作。

CustomerAction.java

 package com.mkyong.customer.action;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.mkyong.customer.dao.CustomerDAO;
import com.mkyong.customer.dao.impl.CustomerDAOImpl;
import com.mkyong.customer.model.Customer;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

public class CustomerAction extends ActionSupport 
	implements ModelDriven{

	Customer customer = new Customer();
	List<Customer> customerList = new ArrayList<Customer>();
	CustomerDAO customerDAO = new CustomerDAOImpl();

	public String execute() throws Exception {
		return SUCCESS;
	}

	public Object getModel() {
		return customer;
	}

	public List<Customer> getCustomerList() {
		return customerList;
	}

	public void setCustomerList(List<Customer> customerList) {
		this.customerList = customerList;
	}

	//save customer
	public String addCustomer() throws Exception{

		//save it
		customer.setCreatedDate(new Date());
		customerDAO.addCustomer(customer);

		//reload the customer list
		customerList = null;
		customerList = customerDAO.listCustomer();

		return SUCCESS;

	}

	//list all customers
	public String listCustomer() throws Exception{

		customerList = customerDAO.listCustomer();

		return SUCCESS;

	}

} 

7.JSP 页面

添加和列出客户的 JSP 页面。

customer.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 Full Hibernate Plugin example</h1>

<h2>Add Customer</h2>
<s:form action="addCustomerAction" >
  <s:textfield name="name" label="Name" value="" />
  <s:textarea name="address" label="Address" value="" cols="50" rows="5" />
  <s:submit />
</s:form>

<h2>All Customers</h2>

<s:if test="customerList.size() > 0">
<table border="1px" cellpadding="8px">
	<tr>
		<th>Customer Id</th>
		<th>Name</th>
		<th>Address</th>
		<th>Created Date</th>
	</tr>
	<s:iterator value="customerList" status="userStatus">
		<tr>
			<td><s:property value="customerId" /></td>
			<td><s:property value="name" /></td>
			<td><s:property value="address" /></td>
			<td><s:date name="createdDate" format="dd/MM/yyyy" /></td>
		</tr>
	</s:iterator>
</table>
</s:if>
<br/>
<br/>

</body>
</html> 

8.struts.xml

Link it all ~ make 包扩展了“ hibernate-default ”而不是“ struts-default ”。

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
  <constant name="struts.devMode" value="true" />

  <package name="default" namespace="/" extends="hibernate-default">

    <action name="addCustomerAction" 
	class="com.mkyong.customer.action.CustomerAction" method="addCustomer" >
       <result name="success">pages/customer.jsp</result>
    </action>

    <action name="listCustomerAction" 
	class="com.mkyong.customer.action.CustomerAction" method="listCustomer" >
        <result name="success">pages/customer.jsp</result>
    </action>

</package>	
</struts> 

9.演示

访问它:http://localhost:8080/struts 2 example/listcustomeraction . action

Struts 2 full hibernate plugin exampleStruts 2 full hibernate plugin example

下载源代码

Download it – Struts2-Hibernate-FullHibernatePluginExample.zip (12 KB)

参考

  1. Struts 2 全休眠插件文档
  2. Struts 2 + Hibernate 集成示例(servlet 上下文监听器)
  3. 将库安装到 Maven 本地存储库中

hibernate integration struts2

struts 2–i18n 或本地化示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-i18n-or-localization-example/

一个 Struts 2 国际化(i18n)、本地化(i10n)或多语言示例,展示了资源包的使用,以显示来自不同语言的消息。在本例中,您将创建一个简单的登录屏幕,通过 Struts 2 UI 组件显示资源包中的消息,并根据所选的语言选项更改区域设置。

Download It – Struts2-i18-localization-Example.zip

1.项目结构

本例的项目结构

Struts2 localization folder structure ## 2.属性文件

确保属性文件被命名为国家指定代码。

In some “non-Europe” or “non-English” like characters, you should always encode the content with native2ascii tool.

global.properties

 #Global messages
global.username = Username
global.password = Password
global.submit = Submit 

全球 _zh_CN.properties

 #Global messages
global.username = \u7528\u6237\u540d
global.password = \u5bc6\u7801
global.submit=\u63d0\u4ea4 

全局 _fr .属性

 #Global messages
global.username = Nom d'utilisateur
global.password = Mot de passe
global.submit = Soumettre 

global_de.properties

 #Global messages
global.username = Benutzername
global.password = Kennwort
global.submit = Einreichen 

Please read this Struts 2 resource bundle example to understand how Struts 2 search the properties file automatically. ## 3.行动

两个 action 类, LocaleAction 基本上什么都不做, LoginAction 会通过 getText() 做一个简单的验证并显示资源包的错误信息。

local action . Java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class LocaleAction extends ActionSupport{

	//business logic
	public String execute() {
		return "SUCCESS";
	}
} 

LoginAction.java

 package com.mkyong.user.action;

import com.opensymphony.xwork2.ActionSupport;

public class LoginAction extends ActionSupport{

	private String username;
	private String password;

	//...getter and setter methods

	//business logic
	public String execute() {
		return "SUCCESS";
	}

	//simple validation
	public void validate(){
		if("".equals(getUsername())){
			addFieldError("username", getText("username.required"));
		}
		if("".equals(getPassword())){
			addFieldError("password", getText("password.required"));
		}
	}
} 

4.查看页面

带有文本框、密码和提交 UI 组件的登录页面。

To support Struts 2 localization, you HAVE TO declared the <%@ page contentType=”text/html;charset=UTF-8″ %> in your view page, else you will have problem to display the “UTF-8 data” correctly, especially the Chinese characters. Read this article about Struts 2 Chinese localization issue.

login.jsp

 <%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 localization example</h1>

<s:form action="validateUser" namespace="/user">

	<s:textfield key="global.username" name="username" />
	<s:password key="global.password" name="password"/>	
	<s:submit key="global.submit" name="submit" />

</s:form>

<s:url id="localeEN" namespace="/" action="locale" >
   <s:param name="request_locale" >en</s:param>
</s:url>
<s:url id="localezhCN" namespace="/" action="locale" >
   <s:param name="request_locale" >zh_CN</s:param>
</s:url>
<s:url id="localeDE" namespace="/" action="locale" >
   <s:param name="request_locale" >de</s:param>
</s:url>
<s:url id="localeFR" namespace="/" action="locale" >
   <s:param name="request_locale" >fr</s:param>
</s:url>

<s:a href="%{localeEN}" >English</s:a>
<s:a href="%{localezhCN}" >Chinese</s:a>
<s:a href="%{localeDE}" >German</s:a>
<s:a href="%{localeFR}" >France</s:a>

</body>
</html> 

To change the default locale, you just need to declared a “request_locale” parameter, set your prefer language code and pass to an Action class. In Struts 2 the com.opensymphony.xwork2.interceptor.I18nInterceptor interceptor, which declared in the struts-default.xml, will hijack your Action class and handle the locale stuff accordingly.

5.是否显示资源包消息?

在 Struts 2 中,有许多方法可以根据所选的语言或地区显示资源包消息。举个例子,

 <s:textfield key="global.username" name="username" />
<s:text name="global.username" />	
<s:property value="getText('global.username')" />
<s:text name="global.password" /> 

In Struts 1, there are one standard bean:message to display the resource bundle message, which is more prefer. But in Struts 2, there are so many equivalent ways to display the resource bundle message (even internal work is different), it’s quite confuse at the first glance. Basically, no matter what you choose, Struts 2 also will display the resource bundle message correctly.

6.struts.xml

Struts 2 配置文件,将它们链接在一起。

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

	<constant name="struts.custom.i18n.resources" value="global" />
 	<constant name="struts.devMode" value="true" />

	<package name="user" namespace="/user" extends="struts-default">
	   <action name="login">
		<result>pages/login.jsp</result>
	   </action>
	   <action name="validateUser" class="com.mkyong.user.action.LoginAction">
		<result name="SUCCESS">pages/welcome.jsp</result>
		<result name="input">pages/login.jsp</result>
	   </action>
	</package>

	<package name="default" namespace="/" extends="struts-default">
	   <action name="locale" class="com.mkyong.common.action.LocaleAction">
		<result name="SUCCESS">user/pages/login.jsp</result>
	   </action>
	</package>

</struts> 

7.演示

http://localhost:8080/struts 2 example/user/log in . action
http://localhost:8080/struts 2 example/locale . action?request_locale=en

Struts2 localization english

http://localhost:8080/struts 2 example/locale . action?request_locale=zh_CN

Struts2 localization chinese

http://localhost:8080/struts 2 example/locale . action?request_locale=de

Struts2 localization German

http://localhost:8080/struts 2 example/locale . action?request_locale=fr

Struts2 localization France

参考

  1. http://struts.apache.org/2.1.8/docs/localization.html
  2. http://www . mkyong . com/Java/Java-convert-Chinese-character-to-unicode-with-native 2 ascii/
  3. http://www . mkyong . com/struts 2/struts-2-resource-bundle-example/
  4. http://www . mkyong . com/struts/struts-国际化-or-本地化-example/

multiple languages struts2

Struts 2 i18n 标签示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-i18n-tag-example/

Download It – Struts2-I18n-Tag-Example.zip

Struts 2 " i18n "标签用于从任何声明的资源包中获取消息,而不仅仅是与当前动作相关联的资源包。参见下面一个完整的“ i18n 标记示例:

1.行动

转发请求的操作类。

I18nTagAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class I18nTagAction extends ActionSupport{

	public String execute() throws Exception {

		return SUCCESS;
	}
} 

2.属性文件

用于演示的两个属性文件。

I18nTagAction.properties

 i18n.msg = "This is a message from I18nTagAction.properties" 

自定义属性

 i18n.msg = "This is a message from Custom.properties" 

3.i18n 标签示例

它展示了“ i18n ”标签的使用。

i18n.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 i18n tag example</h1>

<h2>1.Get message from I18nTagAction.properties</h2> 
Output : 
<s:text name="i18n.msg" />

<h2>2.Get message from Custom.properties</h2> 
Output : 
<s:i18n name="com/mkyong/common/action/Custom">
	<s:text name="i18n.msg" />
</s:i18n>

</body>
</html> 

它是如何工作的?

1.在示例 1 中,它将从与当前操作类(【I18nTagAction.properties】)关联的资源包( I18nTagAction.properties )中获取消息。

2.在示例 2 中,它将从位于com/mkyong/common/action/文件夹中的“ Custom.properties ”属性文件中获取消息。

DO NOT PUT .properties suffix
A very common mistake in the i18n tag, if you declared the properties file with a .properties suffix, Struts 2 will failed to get the message from the declared resource bundle.
WRONG WAY :

 <s:i18n name="com/mkyong/common/action/Custom.properties">
	<s:text name="i18n.msg" />
</s:i18n> 

正确方式 :
声明属性文件不带. properties 后缀

 <s:i18n name="com/mkyong/common/action/Custom">
	<s:text name="i18n.msg" />
</s:i18n> 

4.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />
	<package name="default" namespace="/" extends="struts-default">

		<action name="i18nTagAction" 
			class="com.mkyong.common.action.I18nTagAction" >
			<result name="success">pages/i18n.jsp</result>
		</action>

	</package>
</struts> 

5.演示

http://localhost:8080/struts 2 example/i18 ntagaction . action

输出

Struts 2 i18n tag example

参考

  1. Struts 2 i18n 标签文档

struts2

Struts 2 If,ElseIf,Else 标签示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-if-elseif-else-tag-example/

Download It – Struts2-If-ElseIf-Else-Tag-Example.zip

Struts 2 If、ElseIf 和 Else 标签用于执行基本条件检查。

' If '标签可以单独使用

 <s:if test="%{#variable=='String 1'}">
	This is String 1
</s:if> 

或者用' Else If 标记

 <s:if test="%{#variable=='String 1'}">
	This is String 1
</s:if>
<s:elseif test="%{#variable=='String 2'}">
    This is String 2
</s:elseif> 

和/或单个/多个' Else 标签。

 <s:if test="%{#variable=='String 1'}">
	This is String 1
</s:if>
<s:elseif test="%{#variable=='String 2'}">
    This is String 2
</s:elseif>
<s:else>
    Other Strings
</s:else> 

以上说法都是对的。让我们看一个例子来展示 Struts 2 ' If,ElseIf 和 Else 标签的使用。

1.行动

一个带有字符串属性的 Action 类,它包含一个“ Struts 2 值。

IfTagAction

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class IfTagAction extends ActionSupport{

	private String framework = "Struts 2";

	public String getFramework() {
		return framework;
	}

	public void setFramework(String framework) {
		this.framework = framework;
	}

	public String execute() {
		return SUCCESS;
	}
} 

2.If、ElseIf 和 Else 标签示例

一个 JSP 页面,展示了使用 If、ElseIf 和 Else 标签对“框架变量执行条件检查。

if.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
 <html>
<head>
</head>

<body>
<h1>Struts 2 If, Else, ElseIf tag example</h1>

<s:set name="webFramework" value="framework"/>

<s:if test="%{#webFramework=='Struts 2'}">
	This is Struts 2
</s:if>
<s:elseif test="%{#webFramework=='Struts 1'}">
    This is Struts 1
</s:elseif>
<s:else>
    Other framework
</s:else>

</body>
</html> 

3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 	<constant name="struts.devMode" value="true" />

	<package name="default" namespace="/" extends="struts-default">

		<action name="ifTagAction" 
			class="com.mkyong.common.action.IfTagAction" >
			<result name="success">pages/if.jsp</result>
		</action>

	</package>

</struts> 

4.演示

http://localhost:8080/struts 2 example/iftagaction . action

Struts 2 If, ElseIf, Else Tag

参考

  1. Struts 2 If 标签文档
  2. Struts 2 ElseIf 标签文档
  3. Struts 2 Else 文档

struts2

Struts 2–包含多个 Struts 配置文件

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-include-multiple-struts-configuration-files/

Struts 2 带有“ include file ”特性,将多个 Struts 配置文件包含到一个单元中。

单个 Struts 配置文件

让我们看一个糟糕的 Struts 2 配置示例。

struts.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

<package name="default" namespace="/" extends="struts-default">
</package>

<package name="audit" namespace="/audit" extends="struts-default">
	<action name="WelcomeAudit">
		<result>pages/welcome_audit.jsp</result>
	</action>
</package>

<package name="user" namespace="/user" extends="struts-default">
	<action name="WelcomeUser">
		<result>pages/welcome_user.jsp</result>
	</action>
</package>

</struts> 

在上面的 Struts 配置文件中,它将所有的“用户”和“审计”、设置放在一个文件中,这是不推荐的,必须避免。您应该将这个 struts.xml 文件分成更多更小的模块相关部分。

Do not think this is a case study, it did happened in real life. I seen many Struts 1 or 2 developers just group everything in a single Struts configuration file. In fact, many are still don’t aware of the Struts’s include file feature. ## 多个 Struts 配置文件

在 Struts 2 中,您应该总是为每个模块分配一个 Struts 配置文件。在这种情况下,您可以创建三个文件:

  1. struts-audit . XML–将所有审计模块设置放在这里。
  2. struts-user . XML–将所有用户模块设置放在这里。
  3. struts . XML–使用默认设置并包含 struts-audit.xml 和 struts-user.xml。

struts-audit.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

<package name="audit" namespace="/audit" extends="struts-default">
	<action name="WelcomeAudit">
		<result>pages/welcome_audit.jsp</result>
	</action>
</package>

</struts> 

struts-user.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

<package name="user" namespace="/user" extends="struts-default">
	<action name="WelcomeUser">
		<result>pages/welcome_user.jsp</result>
	</action>
</package>

</struts> 

struts.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

<package name="default" namespace="/" extends="struts-default">
</package>

<include file="user/struts-user.xml"></include>
<include file="audit/struts-audit.xml"></include>

</struts> 

看看文件夹结构是什么样的

Struts 2 multiple config file folder structureDownload this example – Struts2-Multiple-Struts-Config-Files-Example.zip ## 参考

  1. http://www . mkyong . com/struts/struts-multiple-configuration-files-example/
  2. http://www . mkyong . com/struts 2/struts-2-namespace-configuration-example-and-explain/

configuration file struts2

Struts 2 包含标签示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-include-tag-example/

Download It – Struts2-Include-Tag-Example.zip

Struts 2 " include 标签用于将 JSP 或 HTML 页面直接包含到当前页面中。关于“包含标签的演示,请参见下面的示例。

1.行动

仅执行转发任务的简单操作类。

IncludeTagAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class IncludeTagAction extends ActionSupport{

	public String execute() throws Exception {

		return SUCCESS;
	}
} 

2.包括标签示例

它展示了如何使用 include 标签将一个【mkyong.jsp】的页面包含到当前页面中。

include.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 include tag example</h1>

<s:include value="/pages/mkyong.jsp"></s:include>

</body>
</html> 

mkyong.jsp

 <html>
<head>
</head>

<body>
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script><h2>Message from mkyong.jsp</h2>

</body>
</html> 

3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />
	<package name="default" namespace="/" extends="struts-default">

		<action name="includeTagAction" 
			class="com.mkyong.common.action.IncludeTagAction" >
			<result name="success">pages/include.jsp</result>
		</action>

	</package>
</struts> 

4.演示

http://localhost:8080/struts 2 example/include tagaction . action

输出

Struts 2 include tag example

参考

  1. Struts 2 包含标签文档

struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190304032715/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Struts 2 拦截器堆栈示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-interceptor-stack-example/

通常,同一组拦截器可以应用于不同的动作类,例如,

 <package name="default" namespace="/" extends="struts-default">

    <action name="checkInAction" 
	class="com.mkyong.common.action.CheckInAction" >
	<interceptor-ref name="timer"/>
        <interceptor-ref name="logger"/>
	<interceptor-ref name="defaultStack" />
	<result name="success">pages/checkIn.jsp</result>
    </action>

    <action name="checkOutAction" 
	class="com.mkyong.common.action.CheckOutAction" >
	<interceptor-ref name="timer"/>
        <interceptor-ref name="logger"/>
	<interceptor-ref name="defaultStack" />
	<result name="success">pages/checkOut.jsp</result>
    </action>

</package> 

在上述情况下,它有许多重复的工作,根本不能重用。

幸运的是,Struts 2 附带了拦截器栈,允许开发人员将一组拦截器组合成一个名为“栈名的单元,动作可以通过“栈名引用它。

Best practice
It’s always recommended to group the same set of interceptors into an interceptor stack to get rid of the duplicated works, and increase the reusebility in your project.

 <package name="default" namespace="/" extends="struts-default">

     <interceptors>
       	<interceptor-stack name="defaultStackWithLog">
             <interceptor-ref name="timer"/>
             <interceptor-ref name="logger"/>
	     <interceptor-ref name="defaultStack" />
        </interceptor-stack>
    </interceptors>

    <action name="checkInAction" 
	class="com.mkyong.common.action.CheckInAction" >
	<interceptor-ref name="defaultStackWithLog"/>
	<result name="success">pages/checkIn.jsp</result>
    </action>

    <action name="checkOutAction" 
	class="com.mkyong.common.action.CheckOutAction" >
	<interceptor-ref name="defaultStackWithLog"/>
	<result name="success">pages/checkOut.jsp</result>
    </action>

</package> 

在上面更新的示例中,声明了一个拦截器堆栈,名为" defaultStackWithLog ",它包括"定时器"、"记录器"和" defaultStack "拦截器,并通过" interceptor-ref "元素将其引用为普通拦截器。

参考

  1. Struts 2 拦截器文档

interceptor struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190214233721/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Struts 2 迭代器标签示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-iterator-tag-example/

Download It – Struts2-Iterator-tag-Example.zip

Struts 2 迭代器标签用于迭代一个值,可以是 java.util.Collectionjava.util.Iterator 中的任意一个。在本教程中,您将创建一个列表变量,使用 Iterator 标记对其进行循环,并使用 IteratorStatus 获取迭代器状态。

1.行动

一个带有列表属性的动作类,包含各种美味的“肯德基套餐”。

迭代器函数

 package com.mkyong.common.action;

import java.util.ArrayList;
import java.util.List;

import com.opensymphony.xwork2.ActionSupport;

public class IteratorKFCAction extends ActionSupport{

	private List<String> comboMeals;

	public List<String> getComboMeals() {
		return comboMeals;
	}

	public void setComboMeals(List<String> comboMeals) {
		this.comboMeals = comboMeals;
	}

	public String execute() {

		comboMeals = new ArrayList<String>();
		comboMeals.add("Snack Plate");
		comboMeals.add("Dinner Plate");
		comboMeals.add("Colonel Chicken Rice Combo");
		comboMeals.add("Colonel Burger");
		comboMeals.add("O.R. Fillet Burger");
		comboMeals.add("Zinger Burger");

		return SUCCESS;
	}
} 

2.迭代器示例

一个 JSP 页面,展示了如何使用迭代器标签来遍历“KFC comboMeals”列表。在迭代器标签中,包含一个“状态属性,用于声明迭代器状态类的名称。

The IteratorStatus class is used to get information about the status of the iteration. Supported properties are index, count, first, last, odd, even and etc..Make sure you visit this IteratorStatus documentation to know more details of it.

 <%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head>
</head>

<body>
<h1>Struts 2 Iterator tag example</h1>

<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script><h2>Simple Iterator</h2>
<ol>
<s:iterator value="comboMeals">
  <li><s:property /></li>
</s:iterator>
</ol>

<h2>Iterator with IteratorStatus</h2>
<table>
<s:iterator value="comboMeals" status="comboMealsStatus">
  <tr>
  	<s:if test="#comboMealsStatus.even == true">
      <td style="background: #CCCCCC"><s:property/></td>
    </s:if>
    <s:elseif test="#comboMealsStatus.first == true">
      <td><s:property/> (This is first value) </td>
    </s:elseif>
    <s:else>
      <td><s:property/></td>
    </s:else>
  </tr>
</s:iterator>
</table>

</body>
</html> 

3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 	<constant name="struts.devMode" value="true" />

	<package name="default" namespace="/" extends="struts-default">

		<action name="iteratorKFCAction" 
			class="com.mkyong.common.action.IteratorKFCAction" >
			<result name="success">pages/iterator.jsp</result>
		</action>

	</package>

</struts> 

4.演示

http://localhost:8080/struts 2 example/iteratorkfcaction . action

Struts 2 Iterator tag

参考

  1. Struts 2 迭代器标签示例
  2. 迭代器状态文档

iterator struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190304032825/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Struts 2 关键属性示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-key-attribute-example/

Download It – Struts2-Key-Attribute-Example.zip

在 Struts 2 中,UI 组件中的" key "属性是处理本地化的一种常见方式,也是编码 UI 标签的一种非常有效的方式。参见下面两个案例:

1.属性文件

属性文件包含一条消息。

global.properties

 global.username = Username 

2.案例 1

如果您将一个“属性分配给一个文本字段。key 属性将从资源包中获取消息,并基于默认的 xhtml text.tfl 模板呈现它。

 <s:form action="validateUser">
	<s:textfield key="global.username" />
</s:form> 

现在,它将从 global.properties 文件中获取“global . Username {左侧} ”和“Username {右侧} ”,并与下面的 xhtml text.tfl 模板匹配。

 <td class="tdLabel">
   <label for="validateUser_{left-side}" class="label">{right-side}:</label>
</td>
<td>
   <input type="text" name="{left-side}" value="" id="validateUser_{left-side}"/>
</td> 

最终 HTML

 <td class="tdLabel">
   <label for="validateUser_global_username" class="label">Username:</label>
</td>
<td>
   <input type="text" name="global.username" value="" id="validateUser_global_username"/>
</td> 

key 属性将以{左侧} 作为文本框名称和 id;{右侧} 为标签值。

3.案例 2

在某些情况下,您可能需要为文本框显式声明一个不同的名称。

 <s:form action="validateUser">
	<s:textfield key="global.username" name="username"/>
</s:form> 

现在,键属性将使用“用户名{右侧} ”来匹配标签值,文本框名称和 id 将被显式覆盖。

最终 HTML

 <td class="tdLabel">
   <label for="validateUser_username" class="label">Username:</label>
</td>
<td>
   <input type="text" name="username" value="" id="validateUser_username"/>
</td> 

The key attribute may increase your development speed and make your code more efficient, it’s worth to learn it.struts2

Struts 2 + Log4j 集成示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-log4j-integration-example/

struts2 log4j

在本教程中,我们将向您展示如何将 log4j 框架与 Struts 2 web 应用程序集成。你需要做的就是

  1. 包含log4j.jar作为项目依赖项
  2. 创建一个 log4j.properties 文件,放入类路径的根目录,用 Maven 放入resources文件夹。

使用的技术和工具:

  1. Log4j 1.2.17
  2. struts 2.3.16.3
  3. maven3
  4. tomcat6
  5. 日食开普勒 4.3

1.项目目录

审查最终的项目结构。

strruts2-log4j-directory

2.项目相关性

声明 Struts 2 和 log4j 依赖关系:

pom.xml

 <project  
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
	http://maven.apache.org/maven-v4_0_0.xsd">

	<modelVersion>4.0.0</modelVersion>
	<groupId>com.mkyong.common</groupId>
	<artifactId>Struts2</artifactId>
	<packaging>war</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>Struts + Log4j Webapp</name>
	<url>http://maven.apache.org</url>

	<properties>
		<jdk.version>1.7</jdk.version>
		<struts.version>2.3.16.3</struts.version>
		<log4j.version>1.2.17</log4j.version>
	</properties>

	<dependencies>

		<!-- Struts 2 -->
		<dependency>
			<groupId>org.apache.struts</groupId>
			<artifactId>struts2-core</artifactId>
			<version>${struts.version}</version>
		</dependency>

		<!-- Log4j -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>${log4j.version}</version>
		</dependency>

	</dependencies>

	<build>
	  <finalName>Struts2</finalName>
	  <plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-eclipse-plugin</artifactId>
			<version>2.9</version>
			<configuration>
				<downloadSources>true</downloadSources>
				<downloadJavadocs>false</downloadJavadocs>
				<wtpversion>2.0</wtpversion>
			</configuration>
		</plugin>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>2.3.2</version>
			<configuration>
				<source>${jdk.version}</source>
				<target>${jdk.version}</target>
			</configuration>
		</plugin>
	  </plugins>
	</build>
</project> 

3.log4j.properties

创建一个 log4j 属性文件,放入resources文件夹,参考步骤#1。

log4j.properties

 # Root logger option
log4j.rootLogger=ERROR, stdout, file

# Redirect log messages to console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

# Redirect log messages to a log file, support rolling backup file.
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=${catalina.home}/logs/mystruts2app.log
log4j.appender.file.MaxFileSize=5MB
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n 

4.Struts 2 动作和日志记录

一个返回页面的简单操作,并向您展示了如何使用 log4j 进行消息日志记录。

WelcomeAction.java

 package com.mkyong.common.action;

import org.apache.log4j.Logger;
import com.opensymphony.xwork2.ActionSupport;

public class WelcomeAction extends ActionSupport {

	private static final long serialVersionUID = 1L;

	//get log4j
	private static final Logger logger = Logger.getLogger(WelcomeAction.class);

	public String execute() throws Exception {

		// logs debug message
		if (logger.isDebugEnabled()) {
			logger.debug("execute()!");
		}

		// logs exception
		logger.error("This is Error message", new Exception("Testing"));

		return SUCCESS;

	}
} 

5.Struts 2 配置

Struts 2 配置和 JSP 页面,如果您感兴趣的话。

struts.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
	<constant name="struts.devMode" value="true" />

	<package name="welcome" namespace="/" extends="struts-default">

		<action name="welcome" class="com.mkyong.common.action.WelcomeAction">
			<result name="success">pages/success.jsp</result>
		</action>

	</package>

</struts> 

web.xml

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

	<display-name>Struts 2 Web Application</display-name>

	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>
			org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
		</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app> 

pages/success.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 + Log4j integration example</h1>

</body>
</html> 

6.演示

运行 Struts 2 web 应用程序,并访问欢迎操作。

网址:http://localhost:8888/log 4 jandstruts 2/welcome

struts 2 log4j demo

6.1 所有的记录信息都会显示在控制台上。

strruts2-log4j-demo-eclipse-console

图:Eclipse 控制台

此外,将在 Tomcat 的 logs 文件夹中创建一个日志文件。

strruts2-log4j-demo-file

图:D:\ Apache-Tomcat-6 . 0 . 37 \ logs \ mystruts 2 app . log

下载源代码

Download it – Log4jAndStruts2Example.zip (20 KB)

参考

  1. 创建 Struts 2 Web 应用示例
  2. log4j 1.2 官方页面
  3. log4j hello world 示例
  4. Struts 2 异常和日志记录

Struts 2 将拦截器映射到动作

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-mapping-interceptors-to-action/

Struts 2 开发者用来声明动作属于一个扩展了" struts-default "的包,其中包含了默认的拦截器集合。

 <package name="default" namespace="/" extends="struts-default">
	<action name="testingAction" 
		class="com.mkyong.common.action.TestingAction" >
		<result name="success">pages/result.jsp</result>
	</action>
</package> 

默认的拦截器集合在 struts-default.xml 文件中被分组为“ defaultStack ,该文件位于 struts2-core.jar 文件中。“ defaultStack ”提供了 Struts 2 的所有核心功能,满足了大多数应用程序的需求。

Try study the struts-default.xml file, it’s always the best interceptors reference.

将拦截器映射到操作

要将其他拦截器映射到动作,可以使用" interceptor-ref "元素。

 <package name="default" namespace="/" extends="struts-default">
	<action name="testingAction" 
		class="com.mkyong.common.action.TestingAction" >
		<interceptor-ref name="timer"/>
		<interceptor-ref name="logger"/>
		<result name="success">pages/result.jsp</result>
	</action>
</package> 

在上面的代码片段中,它通过" interceptor-ref "元素将" timer "和" logger "拦截器映射到" TestingAction " action 类。

The interceptors will fire in the order they’re declared.

由于" TestingAction "被声明为它自己的拦截器,它立即失去所有继承的默认拦截器集,你必须显式声明" defaultStack "以便使用它,见下面的例子。

 <package name="default" namespace="/" extends="struts-default">
	<action name="testingAction" 
		class="com.mkyong.common.action.TestingAction" >
		<interceptor-ref name="timer"/>
		<interceptor-ref name="logger"/>
		<interceptor-ref name="defaultStack"/>
		<result name="success">pages/result.jsp</result>
	</action>
</package> 

参考

  1. Struts 2 拦截器文档

interceptor struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190224162049/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Struts 2 合并标签示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-merge-tag-example/

Download It – Struts2-Merge-Tag-Example.zip

Struts 2 merge 标签用于将几个迭代器(由 List 或 Map 创建)合并成一个迭代器。在本教程中,你将使用 Struts 2 的合并标签来完成以下任务:

  1. 将三个数组列表合并成一个迭代器。
  2. 将三个散列表合并成一个迭代器。
  3. ArrayListHashMap 合并成一个迭代器。

Assume 2 iterators, each has two entries, after merge with merge tag into a single iterator, the order of the entries will look like following :

  1. 第一个迭代器的第一个条目。
  2. 第二个迭代器的第一个条目。
  3. 第一个迭代器的第二个条目。
  4. 第二个迭代器的第二个条目。

这只适用于列表迭代器;地图迭代器,顺序会随机。

1.行动

一个具有 3 个 ArrayList 和 3 个 HashMap 属性的 Action 类。

MergeTagAction

 package com.mkyong.common.action;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.opensymphony.xwork2.ActionSupport;

public class MergeTagAction extends ActionSupport{

	private List<String> list1 = new ArrayList<String>();
	private List<String> list2 = new ArrayList<String>();
	private List<String> list3 = new ArrayList<String>();

	private Map<String,String> map1 = new HashMap<String,String>();
	private Map<String,String> map2 = new HashMap<String,String>();
	private Map<String,String> map3 = new HashMap<String,String>();

	public String execute() {

		list1.add("List1 - 1");
		list1.add("List1 - 2");
		list1.add("List1 - 3");

		list2.add("List2 - 1");
		list2.add("List2 - 2");
		list2.add("List2 - 3");

		list3.add("List3 - 1");
		list3.add("List3 - 2");
		list3.add("List3 - 3");

		map1.put("map1-key1", "map1-value1");
		map1.put("map1-key2", "map1-value2");
		map1.put("map1-key3", "map1-value3");

		map2.put("map2-key1", "map2-value1");
		map2.put("map2-key2", "map2-value2");
		map2.put("map2-key3", "map2-value3");

		map3.put("map3-key1", "map3-value1");
		map3.put("map3-key2", "map3-value2");
		map3.put("map3-key3", "map3-value3");

		return SUCCESS;
	}

	//getter methods...
} 

2.附加标签示例

一个 JSP 页面,展示了如何使用 merge 标签将 3 ArrayList/3 HashMap/1 ArrayList+1 HashMap 组合成一个迭代器,并循环遍历其值并打印出来。

merge.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
 <html>
<head>
</head>

<body>
<h1>Struts 2 Merge tag example</h1>

1\. Merge 3 ArrayList into a single iterator.
<s:merge var="customListIterator">
     <s:param value="%{list1}" />
     <s:param value="%{list2}" />
     <s:param value="%{list3}" />
</s:merge>
<ol>
<s:iterator value="%{#customListIterator}">
     <li><s:property /></li>
</s:iterator>
</ol>

2\. Merge 3 HashMap into a single iterator.
<s:merge var="customMapIterator">
     <s:param value="%{map1}" />
     <s:param value="%{map2}" />
     <s:param value="%{map3}" />
</s:merge>
<ol>
<s:iterator value="%{#customMapIterator}">
     <li><s:property /></li>
</s:iterator>
</ol>

3\. Merge ArrayList and HashMap into a single iterator.
<s:merge var="customMixedIterator">
     <s:param value="%{list1}" />
     <s:param value="%{map1}" />
</s:merge>
<ol>
<s:iterator value="%{#customMixedIterator}">
     <li><s:property /></li>
</s:iterator>
</ol>

</body>
</html> 

3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 	<constant name="struts.devMode" value="true" />

	<package name="default" namespace="/" extends="struts-default">

		<action name="mergeTagAction" 
			class="com.mkyong.common.action.MergeTagAction" >
			<result name="success">pages/merge.jsp</result>
		</action>

	</package>

</struts> 

4.演示

http://localhost:8080/struts 2 example/mergetagaction . action

输出

 Struts 2 Merge tag example

1\. Merge 3 ArrayList into a single iterator.

  1\. List1 - 1
  2\. List2 - 1
  3\. List3 - 1
  4\. List1 - 2
  5\. List2 - 2
  6\. List3 - 2
  7\. List1 - 3
  8\. List2 - 3
  9\. List3 - 3

2\. Merge 3 HashMap into a single iterator.

  1\. map1-key3=map1-value3
  2\. map2-key2=map2-value2
  3\. map3-key3=map3-value3
  4\. map1-key1=map1-value1
  5\. map2-key3=map2-value3
  6\. map3-key1=map3-value1
  7\. map1-key2=map1-value2
  8\. map2-key1=map2-value1
  9\. map3-key2=map3-value2

3\. Merge ArrayList and HashMap into a single iterator.

  1\. List1 - 1
  2\. map1-key3=map1-value3
  3\. List1 - 2
  4\. map1-key1=map1-value1
  5\. List1 - 3
  6\. map1-key2=map1-value2 

参考

  1. Struts 2 合并标签文档

struts2

Struts 2 模型驱动示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-modeldriven-example/

Download it – Struts2-ModelDriven-Example.zip

如果一个动作实现了" ModelDriven "接口,它将获得额外的能力将表单数据自动转移到对象中。请参见下面的完整示例:

1.域对象

一个客户对象,带有 setter 和 getter 方法。

Customer.java

 package com.mkyong.common;

public class Customer{

	String name;
	int age;

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}

} 

2.行动

Action 类,实现了 ModelDriven 接口,声明了 getModel() 方法返回客户的对象。当表单数据提交给此操作时,它会自动将表单数据传输到客户属性中。

The customer object have to be initialize manually.

CustomerAction.java

 package com.mkyong.common.action;

import com.mkyong.common.Customer;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;

public class CustomerAction extends ActionSupport 
	implements ModelDriven{

	//have to initialize it
	Customer customer = new Customer();

	public String execute() throws Exception {

		return SUCCESS;

	}

	public Object getModel() {

		return customer;

	}
} 

3.JSP 页面

模型驱动的演示的 JSP 页面。

addCustomer.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 ModelDriven example</h1>

<h2>Add Customer</h2>
<s:form  action="customerAction" >
  <s:textfield name="name" label="Name" />
  <s:textfield name="age" label="Age" value=""/>
  <s:submit />
</s:form>

</body>
</html> 

success.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 ModelDriven example</h1>

<h2>Customer Details</h2>
Name : <s:property value="name" /><br>
Age : <s:property value="age" /><br>

</body>
</html> 

4.struts.xml

全部链接起来~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />

	<package name="default" namespace="/" extends="struts-default">

		<action name="addCustomerAction" 
			class="com.mkyong.common.action.CustomerAction" >
		    <result name="success">pages/addCustomer.jsp</result>
		</action>

		<action name="customerAction" 
			class="com.mkyong.common.action.CustomerAction" >
		    <result name="success">pages/success.jsp</result>
		</action>

	</package>

</struts> 

5.演示

进入客户表格,填写表格(姓名:【mkyong】,年龄:【123456】)点击提交按钮,表格数据(姓名:&年龄)将自动转入客户房产(姓名:&年龄)(按房产名称匹配)。

http://localhost:8080/struts 2 example/addcustomeraction . action

Struts2 model driven example

http://localhost:8080/struts 2 example/customer action . action

Struts 2 model driven example

参考

  1. 模型驱动文档

Tags : struts2

Struts 2 名称空间配置示例和说明

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-namespace-configuration-example-and-explanation/

Struts 2 名称空间是一个新概念,通过给每个模块一个名称空间来处理多个模块。此外,它可以用来避免位于不同模块的相同动作名之间的冲突。

Download It – Struts2-NameSpace-Configuration-Example.zipStruts 2 Namespaces are the equivalent of Struts 1 multiple modules

请查看此图片,了解 URL 如何匹配 Struts 2 操作名称空间。

namespace map url

1.命名空间配置

让我们通过一个 Struts 2 namescape 配置示例来了解它如何与 URL 和文件夹相匹配。

P.S 包“名字”不会影响结果,只要给个有意义的名字就行。

struts.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

<package name="default" namespace="/" extends="struts-default">
	<action name="SayWelcome">
		<result>pages/welcome.jsp</result>
	</action>
</package>

<package name="common" namespace="/common" extends="struts-default">
	<action name="SayWelcome">
		<result>pages/welcome.jsp</result>
	</action>
</package>

<package name="user" namespace="/user" extends="struts-default">
	<action name="SayWelcome">
		<result>pages/welcome.jsp</result>
	</action>
</package>

</struts> 

Struts 2 动作名称空间映射到文件夹结构。

namespace map folder ## 2.JSP 视图页面

3 个 JSP 视图页面具有相同的文件名,但是位于不同的模块。

Root–web app/pages/welcome . JSP


Struts 2 名称空间示例

 ## Welcome - namespace = "root " 

通用模块–web app/Common/pages/welcome . JSP


Struts 2 名称空间示例

Welcome - namespace = "常见"

用户模块–web app/User/pages/welcome . JSP


Struts 2 名称空间示例

Welcome - namespace = "用户"

3.映射–它是如何工作的?

例 1
URL:http://localhost:8080/struts 2 Example/say welcome . action
将匹配根命名空间。

 <package name="default" namespace="/" extends="struts-default">
	<action name="SayWelcome">
		<result>pages/welcome.jsp</result>
	</action>
</package> 

并显示 webapp/pages/welcome.jsp 的内容。

例 2
URL:http://localhost:8080/struts 2 Example/common/say welcome . action
将匹配通用名称空间。

 <package name="common" namespace="/common" extends="struts-default">
	<action name="SayWelcome">
		<result>pages/welcome.jsp</result>
	</action>
</package> 

并显示web app/common/pages/welcome . JSP的内容。

例 3
URL:http://localhost:8080/struts 2 Example/user/say welcome . action
将匹配用户命名空间。

 <package name="user" namespace="/user" extends="struts-default">
	<action name="SayWelcome">
		<result>pages/welcome.jsp</result>
	</action>
</package> 

并显示web app/user/pages/welcome . JSP的内容。

参考

  1. Struts 2 名称空间配置参考

namespace struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190225093132/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

GAE 上的 struts 2–错误:未找到结果“null”

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/google-app-engine/struts-2-on-gae-error-result-null-not-found/

问题

在以下环境中,在 Google App Engine 上开发 Struts2。

  1. struts 2.3.1.2
  2. JDK 1.6
  3. Eclipse 3.7+Eclipse 的 Google 插件
  4. 谷歌应用引擎 Java SDK 1.6.3.1

刚开始一个简单的 Struts2 hello world 例子,当访问 action 类时,在本地开发和实际生产的 GAE 环境中都出现错误“Error: result 'null' not found”。

struts2 on gae - result null error ## 解决办法

OGNL 正在执行一些安全检查,这在 GAE 是不支持的。为了让 Struts 2 在 GAE 环境中工作,您需要在web.xml中创建一个监听器,并将 OGNL 安全管理器设置为空。

 OgnlRuntime.setSecurityManager(null); 

完整的例子。

 package com.mkyong.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import ognl.OgnlRuntime;

public class Struts2ListenerOnGAE implements ServletContextListener,
		HttpSessionListener, HttpSessionAttributeListener {

	public void contextInitialized(ServletContextEvent sce) {
		OgnlRuntime.setSecurityManager(null);
	}

	@Override
	public void contextDestroyed(ServletContextEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void sessionCreated(HttpSessionEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void sessionDestroyed(HttpSessionEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void attributeAdded(HttpSessionBindingEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void attributeRemoved(HttpSessionBindingEvent arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void attributeReplaced(HttpSessionBindingEvent arg0) {
		// TODO Auto-generated method stub

	}

} 

文件:web.xml

 <?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

	<filter>
	   <filter-name>struts2</filter-name>
	   <filter-class>
             org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
           </filter-class>
	</filter>

	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<listener>
		<listener-class>com.mkyong.listener.Struts2ListenerOnGAE</listener-class>
	</listener>

</web-app> 

参考

  1. 在 GAE 部署 Struts 2 时出现的问题

gae null struts2

GAE 上的 struts 2–Java . security . accesscontrolexception:拒绝访问

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/google-app-engine/struts-2-on-gae-java-security-accesscontrolexception-access-denied/

问题

在 Google App Engine (SDK v1.6.3.1)上开发 struts 2(v 2.3.1.2),本地开发,打“java.security.AccessControlException: access denied”错误?

struts 2 on gae access denied error

解决办法

通常,这是因为你在struts.xml文件中打开了上的 devMode。

文件:struts.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
	<constant name="struts.devMode" value="true" /> 
	//...
</struts> 

当你打开devMode时,Struts2 将试图写入磁盘进行日志记录,这被 GAE 否认,它只能写入谷歌数据存储。

要解决这个问题,您必须删除您的struts.xml文件中的devMode语句。

Note
In short, Struts 2 devMode is not support in GAE environment.

参考

  1. struts 2.1.8 在 app engine 的本地服务器上遇到了 AccessControlException】

Struts 2 选项转移选择示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-optiontransferselect-example/

Download It – Struts2-OptionTransferSelect-Example.zip

在 Struts 2 中,选项传递选择组件是两个" updownselect "选择组件左右对齐,在它们的中间,包含在它们之间移动选择选项的按钮。这可以通过<s:optiontransferselect>标签创建。

 <s:optiontransferselect
     label="Lucky Numbers"
     name="leftNumber"
     list="{'1 - One ', '2 - Two', '3 - Three', '4 - Four', '5 - Five'}"
     doubleName="rightNumber"
     doubleList="{'10 - Ten','20 - Twenty','30 - Thirty','40 - Forty','50 - Fifty'}"
 /> 

The “name” and “list” is refer to the left select component; while the “doubleName” and “doubleList” is refer to the right select component.

结果是下面的 HTML,两个" updownselect "组件,按钮和 JavaScript 在它们之间移动选择选项(默认的 xhtml 主题)。

 <tr> 
<td class="tdLabel">
<label for="resultAction_leftNumber" class="label">Lucky Numbers:</label>
</td> 
<td>
<script type="text/javascript" src="/Struts2Example/struts/optiontransferselect.js">
</script> 
<table border="0"> 
<tr> 
<td> 
<select name="leftNumber" size="15" 
id="resultAction_leftNumber" multiple="multiple"> 
    <option value="1 - One ">1 - One </option> 
    <option value="2 - Two">2 - Two</option> 
    <option value="3 - Three">3 - Three</option> 
    <option value="4 - Four">4 - Four</option> 
    <option value="5 - Five">5 - Five</option> 
</select> 
<input type="hidden" id="__multiselect_resultAction_leftNumber" 
name="__multiselect_leftNumber" value="" /> 
<input type="button"
	onclick="moveOptionDown(
        document.getElementById('resultAction_leftNumber'), 'key', '');"
	value="v"
/> 
<input type="button"
	onclick="moveOptionUp(
        document.getElementById('resultAction_leftNumber'), 'key', '');"
	value="^"
/> 
</td> 
<td valign="middle" align="center"> 
<input type="button"
    value="<-" onclick="moveSelectedOptions(
    document.getElementById('resultAction_rightNumber'), 
    document.getElementById('resultAction_leftNumber'), false, '');" />
<input type="button"
    value="->" onclick="moveSelectedOptions(
    document.getElementById('resultAction_leftNumber'), 
    document.getElementById('resultAction_rightNumber'), false, '');" /> 
<input type="button"
    value="<<--" onclick="moveAllOptions(
    document.getElementById('resultAction_rightNumber'), 
    document.getElementById('resultAction_leftNumber'), false, '');" />
<input type="button"
    value="-->>" onclick="moveAllOptions(
    document.getElementById('resultAction_leftNumber'), 
    document.getElementById('resultAction_rightNumber'), false, '');" />
<input type="button"
    value="<*>" onclick="selectAllOptions(
    document.getElementById('resultAction_leftNumber'));
    selectAllOptions(document.getElementById('resultAction_rightNumber'));" />
</td> 
<td> 
<select 
	name="rightNumber"
	size="15"
	multiple="multiple"
	id="resultAction_rightNumber"
> 
    	<option value="10 - Ten">10 - Ten</option> 
    	<option value="20 - Twenty">20 - Twenty</option> 
    	<option value="30 - Thirty">30 - Thirty</option> 
    	<option value="40 - Forty">40 - Forty</option> 
    	<option value="50 - Fifty">50 - Fifty</option> 
</select> 
<input type="hidden" id="__multiselect_resultAction_rightNumber" 
name="__multiselect_rightNumber" value="" /> 
<input type="button"
   onclick="moveOptionDown(
   document.getElementById('resultAction_rightNumber'), 'key', '');"
   value="v"
/> 
<input type="button"
   onclick="moveOptionUp(
   document.getElementById('resultAction_rightNumber'), 'key', '');"
   value="^"
/> 
</td> 
</tr> 
</table> 

<script type="text/javascript"> 
var containingForm = document.getElementById("resultAction");
StrutsUtils.addEventListener(containingForm, "submit", 
  function(evt) {
	var selectObj = document.getElementById("resultAction_leftNumber");
		selectAllOptionsExceptSome(selectObj, "key", "");
  }, true);
var containingForm = document.getElementById("resultAction");
StrutsUtils.addEventListener(containingForm, "submit", 
  function(evt) {
	var selectObj = document.getElementById("resultAction_rightNumber");
		selectAllOptionsExceptSome(selectObj, "key", "");
	}, true);
</script> 

Struts 2 示例

<s:optiontransferselect>标签的完整示例,展示了使用 OGNL 和 Java 列表将数据填充到“optiontransferselect”组件中。

1.动作类

Action 类来生成和存储左右选择选项。

OptionTransferSelectAction.java

 package com.mkyong.common.action;

import java.util.ArrayList;
import java.util.List;

import com.opensymphony.xwork2.ActionSupport;

public class OptionTransferSelectAction extends ActionSupport{

	private List<String> leftAntivirusList = new ArrayList<String>();
	private List<String> rightAntivirusList = new ArrayList<String>();

	private String leftAntivirus;
	private String rightAntivirus;

	private String leftNumber;
	private String rightNumber;

	public OptionTransferSelectAction(){

		leftAntivirusList.add("Norton 360 Version 4.0");
		leftAntivirusList.add("McAfee Total Protection 2010");
		leftAntivirusList.add("Trend Micro IS Pro 2010");
		leftAntivirusList.add("BitDefender Total Security 2010");

		rightAntivirusList.add("Norton Internet Security 2010");
		rightAntivirusList.add("Kaspersky Internet Security 2010");
		rightAntivirusList.add("McAfee Internet Security 2010");
		rightAntivirusList.add("AVG Internet Security 2010");
		rightAntivirusList.add("Trend Micro Internet Security 2010");
		rightAntivirusList.add("F-Secure Internet Security 2010");

	}

	public String getLeftNumber() {
		return leftNumber;
	}

	public void setLeftNumber(String leftNumber) {
		this.leftNumber = leftNumber;
	}

	public String getRightNumber() {
		return rightNumber;
	}

	public void setRightNumber(String rightNumber) {
		this.rightNumber = rightNumber;
	}

	public List<String> getLeftAntivirusList() {
		return leftAntivirusList;
	}

	public void setLeftAntivirusList(List<String> leftAntivirusList) {
		this.leftAntivirusList = leftAntivirusList;
	}

	public List<String> getRightAntivirusList() {
		return rightAntivirusList;
	}

	public void setRightAntivirusList(List<String> rightAntivirusList) {
		this.rightAntivirusList = rightAntivirusList;
	}

	public String getLeftAntivirus() {
		return leftAntivirus;
	}

	public void setLeftAntivirus(String leftAntivirus) {
		this.leftAntivirus = leftAntivirus;
	}

	public String getRightAntivirus() {
		return rightAntivirus;
	}

	public void setRightAntivirus(String rightAntivirus) {
		this.rightAntivirus = rightAntivirus;
	}

	public String execute() throws Exception{

		return SUCCESS;
	}

	public String display() {
		return NONE;
	}

} 

2.结果页面

通过“<s:optiontransferselect>”标签渲染选项转移选择组件,通过 Java 和 OGNL 列表生成左右选择选项。

option transfer elect . JSP

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<s:head />
</head>

<body>
<h1>Struts 2 optiontransferselect example</h1>

<s:form action="resultAction" namespace="/" method="POST" >

<s:optiontransferselect
     label="Lucky Numbers"
     name="leftNumber"
     list="{'1 - One ', '2 - Two', '3 - Three', '4 - Four', '5 - Five'}"
     doubleName="rightNumber"
     doubleList="{'10 - Ten','20 - Twenty','30 - Thirty','40 - Forty','50 - Fifty'}"
 />

<s:optiontransferselect
     label="Favourite Antivirus"
     name="leftAntivirus"
     leftTitle="Left Antivirus Title"
     rightTitle="Right Antivirus Title"
     list="leftAntivirusList"
     multiple="true"
     headerKey="-1"
     headerValue="--- Please Select ---"
     doubleList="rightAntivirusList"
     doubleName="rightAntivirus"
     doubleHeaderKey="-1"
     doubleHeaderValue="--- Please Select ---"
 />

<s:submit value="submit" name="submit" />

</s:form>

</body>
</html> 

result.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>

<body>
<h1>Struts 2 optiontransferselect example</h1>

<h2>
   Left AntiVirus : <s:property value="leftAntivirus"/> 
</h2> 

<h2>
   Right AntiVirus : <s:property value="rightAntivirus"/> 
</h2> 

<h2>
   Left Numbers : <s:property value="leftNumber"/> 
</h2> 

<h2>
   Right Numbers : <s:property value="rightNumber"/> 
</h2> 

</body>
</html> 

3.struts.xml

全部链接起来~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 <constant name="struts.devMode" value="true" />

<package name="default" namespace="/" extends="struts-default">

  <action name="optionTransferSelectAction" 
	class="com.mkyong.common.action.OptionTransferSelectAction" 
        method="display">
	<result name="none">pages/optiontransferselect.jsp</result>
  </action>

  <action name="resultAction" 
        class="com.mkyong.common.action.OptionTransferSelectAction" >
	<result name="success">pages/result.jsp</result>
  </action>
</package>

</struts> 

4.演示

http://localhost:8080/struts 2 example/optiontransferselectaction . action

Struts 2 Option Transfer Select exampleStruts 2 Option Transfer Select example

参考

  1. Struts 2 updownselect 文档
  2. Struts 2 updownselect 示例
  3. Struts 2 双重选择示例

struts2

Struts 2 覆盖了拦截器参数

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-override-the-interceptor-parameters/

在 Struts 2 中,您可以通过通用的 < param > 标记来设置或覆盖拦截器参数。参见下面的例子

 <package name="default" namespace="/" extends="struts-default">
   <action name="whateverAction" 
	class="com.mkyong.common.action.WhateverAction" >
	<interceptor-ref name="workflow">
		<param name="excludeMethods">whateverMethod</param>
	</interceptor-ref>
	<result name="success">pages/whatever.jsp</result>
   </action>		
</package> 

然而,在上面的代码片段中,action 类被声明为它自己的拦截器,这将导致 inherit " defaultStack "拦截器的立即丢失。

如果您想保留" defaultStack "拦截器,并覆盖工作流的 excludeMethods 参数,该怎么办?没问题,试试这个

 <package name="default" namespace="/" extends="struts-default">
   <action name="whateverAction" 
	class="com.mkyong.common.action.WhateverAction" >
	<interceptor-ref name="defaultStack">
		<param name="workflow.excludeMethods">whateverMethod</param>
	</interceptor-ref>
	<result name="success">pages/whatever.jsp</result>
   </action>		
</package> 

上面的代码片段将保留“ defaultStack ”拦截器,并覆盖“工作流”参数。

参考

  1. Struts 2 拦截器文档
  2. Struts 2 工作流拦截器文档

interceptor struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190214225208/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Struts 2 参数标记示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-param-tag-example/

Download It – Struts2-Param-Tag-Example.zip

Struts 2 " param "标签用于参数化其他标签。但是,当您声明了“ param ”标记时,可以用两种方式定义“参数值”:

  1. 属性。
  2. param 标记的开始和结束之间的文本。

举个例子,

 <param name="fruit">Banana</param>  {Case 1}
<param name="fruit" value="Banana"/> {Case 2} 

In Struts 2 , both behaves a totally different meaning. In “Case 1”, the value is considered as a java.lang.String object; While “Case 2”, the value is considered as a java.lang.Object object.

上面的陈述最好用一些例子来说明:

示例 1

通过“ param 标记将一个“字符串值设置到 bean 属性中,您必须这样声明

 <s:bean name="com.mkyong.common.Person" var="personBean">
	<s:param name="nickName">mkyong</s:param>
</s:bean> 

Not

 <s:bean name="com.mkyong.common.Person" var="personBean">
	<s:param name="nickName" value="mkyong"></s:param>
</s:bean> 

如果您在“”属性中声明了“字符串值,Struts 2 将会忽略它。

示例 2

通过“参数”标签将一个 java.lang.Object 对象设置到 bean 属性中,声明如下

 <s:bean name="com.mkyong.common.Fruit" var="fruitBean">
	<s:param name="fruitName">Banana</s:param>
</s:bean>

<s:bean name="com.mkyong.common.Person" var="personBean">
	<s:param name="favorFruit" value="#fruitBean"></s:param>
</s:bean> 

示例 3

对于原始类型,你可以自由地在任何地方声明它,它就像一个魔咒一样起作用。

 <s:bean name="com.mkyong.common.Person" var="personBean">
	<s:param name="age" value="99"></s:param>
</s:bean>
// or
<s:bean name="com.mkyong.common.Person" var="personBean">
	<s:param name="age">99</s:param>
</s:bean> 

The “param” tag is not only available for the bean tag, it’s apply to almost all of the other tags that need parametrize.Struts 2, “param” tag is a really confusing tag, many new Struts 2 developers are fall into this silly trap, and wonder why a simple “String” setter method is not work. Hope the Struts 2 team can design more user-friendly tag in future.

Struts 2 参数标记示例

一个完整的 Struts 2 " param "标签示例。

1.行动

转发请求的操作类。

ParamTagAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class ParamTagAction extends ActionSupport{

	public String execute() {
		return SUCCESS;
	}

} 

2.豆

两个 beans,稍后用" param "标签初始化。

Person.java

 package com.mkyong.common;

public class Person{

	private String nickName;
	private int age;
	private Fruit favorFruit;

	public String getNickName() {
		return nickName;
	}
	public void setNickName(String nickName) {
		this.nickName = nickName;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Fruit getFavorFruit() {
		return favorFruit;
	}
	public void setFavorFruit(Fruit favorFruit) {
		this.favorFruit = favorFruit;
	}

	public String getFruitName(){
		return this.favorFruit.getFruitName();
	}

} 

Fruit.java

 package com.mkyong.common;

public class Fruit{

	private String fruitName;

	public String getFruitName() {
		return fruitName;
	}

	public void setFruitName(String fruitName) {
		this.fruitName = fruitName;
	}

} 

3.param 标签示例

一个 JSP 页面显示了" param "标签的使用。

我的钱. jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
 <html>
<head>
</head>

<body>
<h1>Struts 2 param tag example</h1>

<s:bean name="com.mkyong.common.Fruit" var="fruitBean">
	<s:param name="fruitName">Banana</s:param>
</s:bean>

<s:bean name="com.mkyong.common.Person" var="personBean">
	<s:param name="nickName">ah pig ah dog</s:param>
	<s:param name="age">99</s:param>
	<s:param name="favorFruit" value="#fruitBean"></s:param>
</s:bean>

<h2>PersonBean</h2>
<ol>
<li>NickName property : <s:property value="#personBean.nickName" /></li>
<li>Age property : <s:property value="#personBean.age" /></li>
<li>Fruit property : <s:property value="#personBean.fruitName" /></li>
</ol>
</body>
</html> 

4.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />
	<package name="default" namespace="/" extends="struts-default">

		<action name="paramTagAction" 
			class="com.mkyong.common.action.ParamTagAction" >
			<result name="success">pages/param.jsp</result>
		</action>

	</package>
</struts> 

5.演示

http://localhost:8080/struts 2 example/paramtagaction . action

输出

Struts 2 param tag example

参考

  1. Struts 2 参数标签文档

struts2

Struts 2 属性标记示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-property-tag-example/

Download It – Struts2-Property-Tag-Example.zip

Struts 2 " property "标记用于从一个类中获取属性值,如果没有指定,它将默认为当前操作类(堆栈顶部)的属性。在本教程中,它展示了如何使用“属性标签从当前的 Action 类和其他 bean 类中获取属性值。

1.行动

一个 Action 类,有一个“ name 属性。

PropertyTagAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class PropertyTagAction extends ActionSupport{

	private String name = "Name from PropertyTagAction.java"; 

	public String getName() {
		return name;
	}

	public String execute() throws Exception {

		return SUCCESS;
	}
} 

2.豆

一个简单的 Java 类,有一个“ name 属性。

Person.java

 package com.mkyong.common;

public class Person {

	private String name = "Name from Person.java"; 

	public String getName() {
		return name;
	}

} 

3.属性标签示例

它展示了如何使用" property 标记从" PropertyTagAction 和" Person "类中获取" name 属性值。

property.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 property tag example</h1>

<h2>1\. Call getName() from propertyTagAction.java</h2> 
<s:property value="name" />

<h2>2\. Call getName() from Person.java</h2> 
<s:bean name="com.mkyong.common.Person" var="personBean" />
<s:property value="#personBean.name" />

</body>
</html> 

The “property.jsp” page is a success result page returned by the “PropertyTagAction” action. If you specified a <s:property value=”name” /> in “property.jsp” page, it will default to the current Action class “PropertyTagAction.getName()” property.

4.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />
	<package name="default" namespace="/" extends="struts-default">

		<action name="propertyTagAction" 
			class="com.mkyong.common.action.PropertyTagAction" >
			<result name="success">pages/property.jsp</result>
		</action>

	</package>
</struts> 

5.演示

http://localhost:8080/struts 2 example/propertytagaction . action

输出

Struts 2 property tag example

参考

  1. Struts 2 属性标签文档

struts2

Struts 2 推送标签示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-push-tag-example/

Download It – Struts2-Push-Tag-Example.zip

Struts 2 " push "标签用于将值推到栈顶,以便于访问或引用。参见一个完整的“标签示例:

1.行动

操作类仅转发请求。

PushTagAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class PushTagAction extends ActionSupport{

	public String execute() throws Exception {

		return SUCCESS;
	}
} 

2.豆

一个简单的 Person 类,稍后会将它放入堆栈以便于访问。

Person.java

 package com.mkyong.common;

public class Person{

	private String firstName = "This is firstName";
	private String lastName = "This is lastName";

	public String getFirstName() {
		return firstName;
	}
	public String getLastName() {
		return lastName;
	}
} 

3.推送标签示例

它显示了" push 标签的使用。

push.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 push tag example</h1>

<h2>1\. Normal way</h2>
<s:bean name="com.mkyong.common.Person" var="personBean" />
First name : <s:property value="#personBean.firstName" /><br/>
Last name: <s:property value="#personBean.lastName" /><br/>

<h2>2\. Push way</h2>
<s:push value="#personBean" >
First name : <s:property value="firstName" /><br/>
Last name: <s:property value="lastName" /><br/>
</s:push>

</body>
</html> 

它是如何工作的?
正常情况下,如果你想得到豆子的属性,可以像<s:property value = " # person bean . first name "/>一样引用。使用“ push 标签,可以将“ #personBean ”推到栈顶,直接访问属性<s:property value = " first name "/>。两者返回相同的结果,只是访问机制不同。

The “push” tag is saving you to type few characters, don’t see any real value behind.

4.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />
	<package name="default" namespace="/" extends="struts-default">

		<action name="pushTagAction" 
			class="com.mkyong.common.action.PushTagAction" >
			<result name="success">pages/push.jsp</result>
		</action>

	</package>
</struts> 

5.演示

http://localhost:8080/struts 2 example/pushtagaction . action

输出

Struts 2 push tag example

参考

  1. Struts 2 推送标签文档

struts2

Struts 2 + Quartz 2 调度器集成示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-quartz-scheduler-integration-example/

Updated 2012-07-24
Article is updated to use latest Struts 2 and Quartz 2, and tested on Tomcat 6 and 7.

Struts 2 没有附带任何现成的"Struts2-Quartz.jar"插件,黑客使用一个标准的 Servlet 监听器将两个框架链接在一起。看到关系了吗

 Struts 2 <-- (Listener)--> Quartz <---> Scheduler task 

在本教程中,我们将向您展示如何将 Struts 2 和 Quartz scheduler 框架集成在一起。

使用的工具:

  1. 支柱 2.3.4
  2. 石英 2.1.5
  3. Tomcat 6 或 7
  4. maven3
  5. Eclipse 4.2

1.依赖库

没有太多的依赖,你只需要 Struts 2 和 Quartz jar 文件。

文件:pom.xml

 ...
   <dependencies>

	<!-- Struts 2 -->
	<dependency>
		<groupId>org.apache.struts</groupId>
		<artifactId>struts2-core</artifactId>
		<version>2.3.4</version>
	</dependency>

	<!-- Quartz framework -->
	<dependency>
		<groupId>org.quartz-scheduler</groupId>
		<artifactId>quartz</artifactId>
		<version>2.1.5</version>
	</dependency>

        <!-- for javax.servlet.* classes -->
	<dependency>
		<groupId>org.apache.tomcat</groupId>
		<artifactId>servlet-api</artifactId>
		<version>6.0.35</version>
	</dependency>

  </dependencies> 
  ... 

2.调度程序作业

创建一个 Quartz 作业并打印出一行。

文件:SchedulerJob.java

 package com.mkyong.quartz;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class SchedulerJob implements Job {
	public void execute(JobExecutionContext context)
		throws JobExecutionException {

		System.out.println("Struts 2.3.4 + Quartz 2.1.5");

	}
} 

3.Servlet 监听器

创建标准的 servlet 监听器类,以完成集成工作。它在contextInitialized()方法中调用 Quartz 调度程序框架。在 Servlet 容器初始化期间,这个contextInitialized()方法将被自动执行。

文件:QuartzSchedulerListener.java

 package com.mkyong.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.quartz.CronScheduleBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import com.mkyong.quartz.SchedulerJob;

public class QuartzSchedulerListener implements ServletContextListener {

	public void contextDestroyed(ServletContextEvent arg0) {
		//
	}

	public void contextInitialized(ServletContextEvent arg0) {

		JobDetail job = JobBuilder.newJob(SchedulerJob.class)
			.withIdentity("anyJobName", "group1").build();

		try {

			Trigger trigger = TriggerBuilder
			  .newTrigger()
			  .withIdentity("anyTriggerName", "group1")
			  .withSchedule(
			     CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
			  .build();

			Scheduler scheduler = new StdSchedulerFactory().getScheduler();
			scheduler.start();
			scheduler.scheduleJob(job, trigger);

		} catch (SchedulerException e) {
			e.printStackTrace();
		}

	}
} 

4.web.xml

将监听器类QuartzSchedulerListener.java放到web.xml文件中。

文件:web.xml

 <!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
	<display-name>Struts 2 Web Application</display-name>

	<filter>
	  <filter-name>struts2</filter-name>
	  <filter-class>
            org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
          </filter-class>
	</filter>

	<filter-mapping>
	  <filter-name>struts2</filter-name>
	  <url-pattern>/*</url-pattern>
	</filter-mapping>

	<listener>
	  <listener-class>
            com.mkyong.listener.QuartzSchedulerListener
          </listener-class>
	</listener>

</web-app> 

5.演示

Struts 2 项目启动后,注册的监听器类QuartzSchedulerListener.java将被触发,每隔 10 秒调用 Quartz 调度作业执行SchedulerTask.printSchedulerMessage()方法。

 INFO: Overriding property struts.i18n.reload - old value: false new value: true
Jul 23, 2012 4:56:47 PM com.opensymphony.xwork2.util.logging.jdk.JdkLogger info
INFO: Overriding property struts.configuration.xml.reload - old value: false new value: true
Jul 23, 2012 4:56:48 PM org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
Jul 23, 2012 4:56:48 PM org.apache.jk.common.ChannelSocket init
INFO: JK: ajp13 listening on /0.0.0.0:8009
Jul 23, 2012 4:56:48 PM org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=0/38  config=null
Jul 23, 2012 4:56:48 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 1215 ms
Struts 2.3.4 + Quartz 2.1.5
Struts 2.3.4 + Quartz 2.1.5 

下载源代码

Download it –Struts2-Quartz-Example.zip (22 KB)

参考

  1. 石英调度器示例
  2. Struts + Quartz 集成示例
  3. 支柱 2 +弹簧+石英集成示例
  4. 支柱+弹簧+石英集成示例

integration quartz scheduler struts2

struts 2–资源包示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-resource-bundle-example/

要使用资源包从属性文件中检索消息,您必须理解 Struts 2 资源包的搜索顺序:

资源包搜索顺序

资源包按以下顺序搜索:

  1. ActionClass.properties
  2. 接口.属性
  3. BaseClass.properties
  4. 模型驱动的模型
  5. 包.属性
  6. 向上搜索 i18n 消息键层次结构本身
  7. 全局资源属性

Refer to Struts 2 Resource Bundle documentation for detail explanation.Hi Struts 2, you search too much, there are too many search orders involved and cost performance if the properties file is not found.

实际上,按照上面的顺序组织属性文件是不可能的。所以,只要了解几个常用的搜索顺序应该就够了: ActionClass.propertiespackage.properties全局资源属性。见下图:

Struts 2 resource bundle

如果一个com . mkyong . user . action . loginaction想要通过资源包获得一条消息,它会搜索

  1. com . mkyong . user . action . loginaction . properties(找到,退出,否则下一个)
  2. com . mkyong . user . action . package . properties(找到,退出,否则下一个)
  3. com . mkyong . user . package . properties(find exit,else next)
    …将 find package.properties 保存在每个父目录中,一直到根目录
  4. 找到全局资源属性,如果您在应用程序中配置了它

Understand this search order can give you more confident to decide the correct folder for properties file. ## 获取资源包

访问资源包的几个示例:

P . S’username . required和’username是属性文件中的关键字。

1.动作类

在 Action 类中,您可以扩展 ActionSupport 并通过 getText('key ')函数获得资源包。

 ...
public class LoginAction extends ActionSupport{
	...
	public void validate(){
		if("".equals(getUsername())){
			addFieldError("username", getText("username.required"));
		}
	}
} 

2.属性标签

在属性标记中,使用 getText('key ')。

 <s:property value="getText('username')" /> 

3.文本标签

在文本标签中,设置“名称”属性中的键。

 <s:text name="username" /> 

4.信息标号属性

UI 组件的 Key 属性有特殊的功能,详见本 key 属性示例

 <s:textfield key="username" /> 

5.I18n 标签

这个 i18n 标记可以从在“name”属性中声明的指定资源包中获取消息。在本例中,它要求从com/mkyong/user/package . properties文件中获取“用户名”消息。

 <s:i18n name="com.mkyong.user.package" >
     <s:text name="username" />
</s:i18n> 

Download full project for practice – Struts2-Resource-Bundle-Example.zip

参考

  1. struts 2 中的全局资源包
  2. Struts 2 关键属性示例
  3. Struts 2 资源包搜索订单文档

resource bundle struts2

Struts 2 @ResultPath 注释示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-resultpath-annotation-example/

在 Struts 2 中, @ResultPath 注释用于控制 Struts 2 将在哪里找到存储的结果或 JSP 页面。默认情况下,它会从“ WEB-INF/content/ ”文件夹中找到结果页面。

No idea why the Struts 2 annotation set the “WEB-INF/content/” as default folder, but most applications will not put the result pages in this “WEB-INF/content/” folder. It’s just a Struts 2 convention not a standard folder structure. I rather Struts 2 put the root path as the default folder.

@ResultPath 示例

1.默认结果路径

一个登录动作类,设置为“/用户”命名空间,重定向到“页面/login.jsp ”页面。

假设 Struts2Example 是您的上下文 servlet 名称

 @Namespace("/User")
@Result(name="success",location="pages/login.jsp")
public class LoginAction extends ActionSupport{
} 

访问它

 http://localhost:8080/Struts2Example/User/login.action 

Struts 2 将从默认位置找到“login.jsp”结果页面

 /Struts2Example/WEB-INF/content/User/pages/login.jsp 

2.自定义结果路径

如果您 JSP 结果页面存储在其他位置,您可以使用 @ResultPath 注释对其进行更改。

 @Namespace("/User")
@ResultPath(value="/")
@Result(name="success",location="pages/login.jsp")
public class LoginAction extends ActionSupport{
} 

再次访问它

 http://localhost:8080/Struts2Example/User/login.action 

现在,Struts 2 将从不同的位置找到“login.jsp”结果页面

 /Struts2Example/User/pages/login.jsp 

全局@结果路径

@ResultPath 只适用于类级别。要全局应用它,您可以在 struts.xml 文件中配置它。

struts.xml

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
	<constant name="struts.convention.result.path" value="/"/>
</struts> 

参考

  1. Struts 2 @ResultPath 注释文档

struts2

Struts 2 复选框示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-scheckbox-checkbox-example/

Download It – Struts2-CheckBox-Example.zip

在 Struts 2 中,可以使用 < s:checkbox > 标签创建一个 HTML 复选框。 fieldValue="true" 是复选框将提交的实际值。

 <s:checkbox name="checkMe" fieldValue="true" label="Check Me for testing"/> 

In common, you do not need to declared the fieldValue=”true”, because true is the default value.

它将生成下面的 HTML。

 <input type="checkbox" name="checkMe" value="true" id="xx_checkMe"/>
<input type="hidden" id="__checkbox_xx_checkMe" name="__checkbox_checkMe" value="true"/>
<label for="resultAction_checkMe" class="checkboxLabel">Check Me for testing</label> 

预先选择复选框

如果您想要预先选择一个复选框,只需添加一个 value 属性并将其设置为 true。

 <s:checkbox name="checkMe" fieldValue="true" value="true" label="Check Me for testing"/> 

它将生成下面的 HTML。

 <input type="checkbox" name="checkMe" value="true" checked="checked" id="xx_checkMe"/>
<input type="hidden" id="__checkbox_xx_checkMe" name="__checkbox_checkMe" value="true" />
<label for="resultAction_checkMe" class="checkboxLabel">Check Me for testing</label> 

Struts 2 < s:复选框> 示例

一个完整的例子,通过 Struts 2<s:checkbox>创建复选框,并将提交的复选框值赋给 Action 类并显示。

1.行动

带有用于保存复选框值的 checkMe 布尔属性的操作类。CheckBoxAction.java
T3

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class CheckBoxAction extends ActionSupport{

	private boolean checkMe;

	public boolean isCheckMe() {
		return checkMe;
	}

	public void setCheckMe(boolean checkMe) {
		this.checkMe = checkMe;
	}

	public String execute() {

		return SUCCESS;

	}

	public String display() {

		return NONE;

	}

} 

2.结果页面

使用 Struts 2 " s:checkbox "标记创建复选框的结果页。

checkBox.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 check box example</h1>

<s:form action="resultAction" namespace="/">

<h2>
	<s:checkbox name="checkMe" fieldValue="true" label="Check Me for testing"/>
</h2> 

<s:submit value="submit" name="submit" />

</s:form>

</body>
</html> 

result.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>

<body>
<h1>Struts 2 check box example</h1>

<h2>
  CheckBox (CheckMe) value : <s:property value="checkMe"/>
</h2> 

</body>
</html> 

3.struts.xml

链接在一起~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 <constant name="struts.devMode" value="true" />

<package name="default" namespace="/" extends="struts-default">

   <action name="checkBoxAction" 
         class="com.mkyong.common.action.CheckBoxAction" method="display">
	<result name="none">pages/checkBox.jsp</result>
   </action>

   <action name="resultAction" class="com.mkyong.common.action.CheckBoxAction">
	<result name="success">pages/result.jsp</result>
   </action>
  </package>

</struts> 

5.演示

http://localhost:8080/struts 2 example/checkbox action . action

Struts2 check box

http://localhost:8080/struts 2 example/result action . action

Struts2 check box

参考

  1. Struts 2 复选框文档

checkbox struts2

Struts 2 多个复选框示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-scheckboxlist-multiple-check-boxes-example/

Download It – Struts2-multiple-checkboxes-example.zip

在 Struts 2 中,可以使用 < s:checkboxlist > 标签来创建多个同名的复选框。唯一关心的是如何在一个变量中保存多个检查值?举个例子,

 public List<String> getColors() {
	colors = new ArrayList<String>();
	colors.add("red");
	colors.add("yellow");
	colors.add("blue");
	colors.add("green");
	return colors;
} 
 <s:checkboxlist label="What's your favor color" list="colors" 
name="yourColor" value="defaultColor" /> 

带有“红色”、“黄色”、“蓝色”和“绿色”选项的多个复选框。如果选中了多个选项,您可以通过一个字符串对象来存储它。

例如,如果选中了“红色”和“黄色”选项,则选中的值将与逗号 yourColor = "red,yellow" 组合。

 private String yourColor;

public void setYourColor(String yourColor) {
	this.yourColor = yourColor;
} 

Read this article about how to set the default value for multiple check boxes.

struts 2<s:checkbox list>示例

一个完整的 Struts 2 示例,通过 < s:checkboxlist > 创建多个同名的复选框,存储选中的值并显示在另一个页面中。

1.行动

操作类来生成和保存多个复选框值。
CheckBoxListAction.java

 package com.mkyong.common.action;

import java.util.ArrayList;
import java.util.List;

import com.opensymphony.xwork2.ActionSupport;

public class CheckBoxListAction extends ActionSupport{

	private List<String> colors;

	private String yourColor;

	public String getYourColor() {
		return yourColor;
	}

	public void setYourColor(String yourColor) {
		this.yourColor = yourColor;
	}

	public CheckBoxListAction(){
		colors = new ArrayList<String>();
		colors.add("red");
		colors.add("yellow");
		colors.add("blue");
		colors.add("green");
	}

	public String[] getDefaultColor(){
		return new String [] {"red", "green"};
	}

	public List<String> getColors() {
		return colors;
	}

	public void setColors(List<String> colors) {
		this.colors = colors;
	}

	public String execute() {
		return SUCCESS;
	}

	public String display() {
		return NONE;
	}
} 

2.结果页面

通过“ s:checkboxlist ”标签呈现多个复选框。checkBoxlist.jsp
T3

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 multiple check boxes example</h1>

<s:form action="resultAction" namespace="/">

<h2>
	<s:checkboxlist label="What's your favor color" list="colors" 
	   name="yourColor" value="defaultColor" />
</h2> 

<s:submit value="submit" name="submit" />

</s:form>

</body>
</html> 

result.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>

<body>
<h1>Struts 2 multiple check boxes example</h1>

<h2>
  Favor colors : <s:property value="yourColor"/>
</h2> 

</body>
</html> 

3.struts.xml

链接在一起~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 <constant name="struts.devMode" value="true" />

<package name="default" namespace="/" extends="struts-default">

   <action name="checkBoxListAction" 
         class="com.mkyong.common.action.CheckBoxListAction" method="display">
	<result name="none">pages/checkBoxlist.jsp</result>
   </action>

   <action name="resultAction" class="com.mkyong.common.action.CheckBoxListAction">
	<result name="success">pages/result.jsp</result>
   </action>
  </package>

</struts> 

5.演示

http://localhost:8080/struts 2 example/checkboxlistaction . action

Struts 2 checkboxlist example

http://localhost:8080/struts 2 example/result action . action

Struts 2 checkboxlist example

参考

  1. Struts 2 检查列表文档

checkbox struts2

Struts 2 示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-sdoubleselect-example/

Download It – Struts2-Double-Select-Example.zip

在 Struts 2 中, < s:doubleselect > 标签用于创建两个 HTML 下拉框,一旦第一个下拉列表被选中,第二个下拉列表就会相应地改变。一个非常经典的例子是“国家”和“”下拉选择,其中不同的“”列表都取决于所选的“国家”。

A < s:doubleselect >标签

 <s:doubleselect label="Fruits (OGNL) " 
name="fruit1" list="{'fruit','meat'}" 
doubleName="fruit2" 
doubleList="top == 'fruit' ? {'apple', 'orange','banana'} : {'chicken', 'pig'}" /> 

产生以下 HTML 代码…

 <tr> 
<td class="tdLabel">
  <label for="resultAction_fruit1" class="label">
     Fruits (OGNL) :
  </label>
</td> 
<td> 
<select name="fruit1" id="resultAction_fruit1" 
    onchange="resultAction_fruit1Redirect(this.options.selectedIndex)"> 
    <option value="fruit">fruit</option> 
    <option value="meat">meat</option> 
</select> 
<br /> 
<select name="fruit2" id="resultAction_fruit2"> 
</select> 
<script type="text/javascript"> 
    var resultAction_fruit1Group = new Array(2 + 0);
    for (i = 0; i < (2 + 0); i++)
    resultAction_fruit1Group[i] = new Array();

    resultAction_fruit1Group[0][0] = new Option("apple", "apple");

    resultAction_fruit1Group[0][1] = new Option("orange", "orange");

    resultAction_fruit1Group[0][2] = new Option("banana", "banana");

    resultAction_fruit1Group[1][0] = new Option("chicken", "chicken");

    resultAction_fruit1Group[1][1] = new Option("pig", "pig");

    var resultAction_fruit1Temp = document.resultAction.resultAction_fruit2;
    resultAction_fruit1Redirect(0);
    function resultAction_fruit1Redirect(x) {
    	var selected = false;
        for (m = resultAction_fruit1Temp.options.length - 1; m >= 0; m--) {
            resultAction_fruit1Temp.options[m] = null;
        }

        for (i = 0; i < resultAction_fruit1Group[x].length; i++) {
            resultAction_fruit1Temp.options[i] = 
	     new Option(
              resultAction_fruit1Group[x][i].text, resultAction_fruit1Group[x][i].value
             );
         }

        if ((resultAction_fruit1Temp.options.length > 0) && (! selected)) {
           	resultAction_fruit1Temp.options[0].selected = true;
        }
    }
</script>   
</td> 
</tr> 

嗯, < s:doubleselect > 标签确实是生成了很多代码。它将创建两个下拉列表,许多 JavaScript 的代码来完成后面的魔术(第一次选择,第二次更改),并且还为第一个下拉框附加了一个“ onchange() ”行为。

Look complicated, but simple!
It’s just two <s:select> tags combine in a single tag, see clearly again.

 <s:doubleselect
name="" list="" 
doubleName="" doubleList="" /> 

名称”和“列表是指第一个下拉列表;“双重名称”和“双重列表是指第二个下拉列表。数据填充同“ < s:选择> ”标签。

struts 2<s:double select>示例

一个完整的 < s:doubleselect > 标签的例子,展示了使用 OGNL 或 Java 列表来填充下拉列表中的数据。

1.行动

操作类来生成和保存两个下拉列表。
DoubleSelectAction.java

 package com.mkyong.common.action;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.opensymphony.xwork2.ActionSupport;

public class DoubleSelectAction extends ActionSupport{

	private String fruit1;
	private String fruit2;

	private String server1;
	private String server2;

	private String language1;
	private String language2;

	Map languageMap;

	public String getFruit1() {
		return fruit1;
	}

	public void setFruit1(String fruit1) {
		this.fruit1 = fruit1;
	}

	public String getFruit2() {
		return fruit2;
	}

	public void setFruit2(String fruit2) {
		this.fruit2 = fruit2;
	}

	public String getServer1() {
		return server1;
	}

	public void setServer1(String server1) {
		this.server1 = server1;
	}

	public String getServer2() {
		return server2;
	}

	public void setServer2(String server2) {
		this.server2 = server2;
	}

	public String getLanguage1() {
		return language1;
	}

	public void setLanguage1(String language1) {
		this.language1 = language1;
	}

	public String getLanguage2() {
		return language2;
	}

	public void setLanguage2(String language2) {
		this.language2 = language2;
	}

	public Map getLanguageMap() {
		return languageMap;
	}

	public void setLanguageMap(Map languageMap) {
		this.languageMap = languageMap;
	}

	public DoubleSelectAction(){
	  languageMap =new HashMap();		

          languageMap.put("Java", 
            new ArrayList<String>(Arrays.asList("Spring", "Hibernate", "Struts 2")));
          languageMap.put(".Net", new ArrayList<String>(Arrays.asList("VB.Net", "C#")));
          languageMap.put("JavaScript", new ArrayList<String>(Arrays.asList("jQuery")));
	}

	public String execute() {
		return SUCCESS;
	}

	public String display() {
		return NONE;
	}

} 

2.结果页面

通过“ < s:doubleselect > ”标签呈现两个下拉框,通过 Java list 和 OGNL list 填充列表

doubleselect.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 <s:doubleselect> example</h1>

<s:form action="resultAction" namespace="/">

<s:doubleselect label="Fruits (OGNL) " 
name="fruit1" list="{'fruit','meat'}" 
doubleName="fruit2" 
doubleList="top == 'fruit' ? {'apple', 'orange','banana'} : {'chicken', 'pig'}" />

<s:set name="serverList" 
     value="#{
         'AppServer': {'Apache', 'Tomcat', 'JBoss'},
         'Database': {'Oracle', 'MySQL'}
         }" />
<s:doubleselect label="Server (OGNL) " 
name="server1" list="#serverList.keySet()" 
doubleName="server2" doubleList="#serverList[top]" />

<s:doubleselect label="Language (Java List) " 
name="language1" list="languageMap.keySet()" 
doubleName="language2" doubleList="languageMap.get(top)" />

<s:submit value="submit" name="submit" />

</s:form>

</body>
</html> 

result.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>

<body>
<h1>Struts 2 <s:doubleselect> example</h1>

<h2>
  Test #1 : <s:property value="fruit1"/> , <s:property value="fruit2"/>
</h2> 

<h2>
  Test #2 : <s:property value="server1"/> , <s:property value="server2"/>
</h2> 

<h2>
  Test #2 : <s:property value="language1"/> , <s:property value="language2"/>
</h2> 

</body>
</html> 

3.struts.xml

链接在一起~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

 <constant name="struts.devMode" value="true" />

<package name="default" namespace="/" extends="struts-default">

   <action name="doubleSelectAction" 
         class="com.mkyong.common.action.DoubleSelectAction" method="display">
	<result name="none">pages/doubleselect.jsp</result>
   </action>

   <action name="resultAction" class="com.mkyong.common.action.DoubleSelectAction">
	<result name="success">pages/result.jsp</result>
   </action>
  </package>

</struts> 

5.演示

http://localhost:8080/struts 2 example/doubleselectaction . action

Struts 2 double select example

http://localhost:8080/struts 2 example/result action . action

Struts 2 double select example

参考

  1. Struts 2 双击文档
  2. 支柱 2 < s:选择>示例
  3. http://chuanliang2007.spaces.live.com/blog/cns!E5B7AB2851A4C9D2!393 .条目

dropdown struts2

Struts 2 设置标签示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-set-tag-example/

Download It – Struts2-Set-Tag-Example.zip

Struts 2 " set 标签用于在指定的作用域(应用、会话、请求、页面或动作)中给变量赋值,动作是默认的作用域。参见一个完整的“设置标记示例:

The “value” means any hard-coded String, property value or just anything you can reference.

1.行动

带有“msg”属性的操作类。

SetTagAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class SetTagAction extends ActionSupport{

	private String msg = "Struts 2 is a funny framework";

	public String getMsg() {
		return msg;
	}

	public String execute() throws Exception {

		return SUCCESS;
	}
} 

2.设置标签示例

它显示了使用“ set 标签。

set.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 set tag example</h1>
<script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>

<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script><h2>1\. <s:set var="varMsg" value="msg" /></h2>

<s:set var="varMsg" value="msg" />
<s:property value="varMsg" />

<h2>2\. <s:set var="varUrl" value="%{'http://www.mkyong.com'}" /></h2> 

<s:set var="varUrl" value="%{'http://www.mkyong.com'}" />
<s:property value="varUrl" />

</body>
</html> 

它是如何工作的?

1。<s:set var = " varMsg " value = " msg "/>
调用动作的 getMsg() 方法,将返回值赋给一个名为" varMsg "的变量。

2。<s:set var = " varUrl " value = " % { ' http://www . mkyong . com ' } "/>
硬编码一个字符串,赋给一个名为" varUrl 的变量。

Assign value to a variable, not property value.

举个例子,

 public class SetTagAction extends ActionSupport{

	private String msg;

	public String setMsg(String msg) {
		this.msg = msg;
	}
	... 
 <s:set var="msg" value="%{'this is a message'}" /> 

许多 Struts 2 开发人员认为" set 标签 var="msg" 会通过 setMsg() 方法将值赋给关联的 action 类。

这是错误的,set 标签不会调用 setMsg() 方法,它只会将“值”赋给一个名为“ msg 的变量,而不是动作的属性值。

3.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />
	<package name="default" namespace="/" extends="struts-default">

		<action name="setTagAction" 
			class="com.mkyong.common.action.SetTagAction" >
			<result name="success">pages/set.jsp</result>
		</action>

	</package>
</struts> 

5.演示

http://localhost:8080/struts 2 example/setta action . action

输出

Struts 2 set tag example

参考

  1. Struts 2 设置标签文档

struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190304030901/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Struts 2 示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-shead-example/

< s:head > 标签用于输出编码、CSS 或 JavaScript 文件等 HTML 头信息。请参见以下片段:

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<s:head />
</head>
<body>
.. 

假设您使用默认的 xhtml 主题,它将根据" template\xhtml\head.ftl "文件呈现输出

 <html>
<head>
<link rel="stylesheet" href="/your_project/struts/xhtml/styles.css" type="text/css"/> 
<script src="/your_project/struts/utils.js" type="text/javascript"></script> 
</head>
<body>
.. 

要包含新的 js 或 css 文件,只需将其添加到“ template\xhtml\head.ftl 模板文件中,并通过 < s:head > 标签输出即可。

实际上,这个 < s:head > 标签并不需要放在 HTML < head >标签的上面并变形,例如

 <head>
<s:head />
</head> 

你可以把它放在任何地方,它只是输出 CSS 和 js 文件的路径(默认在 xhtml 主题中)。

 <head>
</head>
<body>
<s:head />
... 

Good Practice

为了提高网站的性能,最好的做法是将 CSS 文件放在页面的顶部;而 js 文件在页面底部。所以, < s:head > 标签可能不合适,一个好的做法应该是创建新的标签来分别输出 CSS 和 js 文件,例如 < s:css >< s:javascript >

参考

  1. 支柱 2 < s:头部>示例

struts2 (function (i,d,s,o,m,r,c,l,w,q,y,h,g) { var e=d.getElementById(r);if(e=null){ var t = d.createElement(o); t.src = g; t.id = r; t.setAttribute(m, s);t.async = 1;var n=d.getElementsByTagName(o)[0];n.parentNode.insertBefore(t, n); var dt=new Date().getTime(); try{i[l]w+y;}catch(er){i[h]=dt;} } else if(typeof i[c]!'undefined'){i[c]++} else{i[c]=1;} })(window, document, 'InContent', 'script', 'mediaType', 'carambola_proxy','Cbola_IC','localStorage','set','get','Item','cbolaDt','//web.archive.org/web/20190222111847/http://route.carambo.la/inimage/getlayer?pid=myky82&did=112239&wid=0')

Struts 2 隐藏值示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-shidden-hidden-value-example/

Download It – Struts2-Hidden-Example.zip

在 Struts 2 中,你可以使用 < s:hidden > 标签来创建一个 HTML 隐藏字段。

 <s:hidden name="url" value="http://www.mkyong.com" /> 

它将呈现为以下 HTML 代码。

 <input type="hidden" name="url" value="http://www.mkyong.com" /> 

Struts 2 示例

带有 url 隐藏值的页面,并在表单提交后显示隐藏值。

1.行动

HiddenAction.java

 package com.mkyong.common.action;

import com.opensymphony.xwork2.ActionSupport;

public class HiddenAction extends ActionSupport{

	private String url;

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String execute() {
		return SUCCESS;
	}

} 

2.查看页面

Struts 2 " s:hidden "标记创建一个隐藏的值字段。

hidden.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 - hidden value example</h1>

<s:form action="helloHidden" namespace="/">

	<h2>This page has a hidden value (view source): 
	<s:hidden name="url" value="http://www.mkyong.com" /></h2> 

	<s:submit value="submit" name="submit" />

</s:form>

</body>
</html> 

welcome.jsp

 <%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>

<body>
<h1>Struts 2 - hidden value example</h1>

<h2>
  The hidden value :
  <s:property value="url"/>
</h2> 

</body>
</html> 

3.struts.xml

链接在一起~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

   <constant name="struts.devMode" value="true" />

   <package name="" namespace="/" extends="struts-default">
	<action name="hidden">
		<result>pages/hidden.jsp</result>
	</action>
	<action name="helloHidden" class="com.mkyong.common.action.HiddenAction">
		<result name="success">pages/welcome.jsp</result>
	</action>
   </package>

</struts> 

4.演示

http://localhost:8080/struts 2 example/hidden . action

Struts2 hidden value exampleStruts2 hidden value example

参考

  1. Struts 2 隐藏字段文档

hidden value struts2

Struts 2 排序标签示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-sort-tag-example/

Download It – Struts2-Sort-Tag-Example.zip

Struts 2 sort 标签用于使用 java.util.Comparator 对列表进行排序。在本教程中,您将创建 6 个 Person 对象并将所有对象添加到一个 ArrayList 中,并使用 sort 标签根据 Person 的属性对 ArrayList 进行排序。

To use this Struts 2 sort tag, you have to understand how java.util.Comparator work, please read this article – Java object sorting with Comparator.

1.人对象

创建一个具有“名字”、“姓氏”和“年龄”属性的 Person 对象。

Person.java

 package com.mkyong.common;

public class Person{

	private String firstName;
	private String lastName;
	private int age;

	public Person(String firstName, String lastName, int age) {
		super();
		this.firstName = firstName;
		this.lastName = lastName;
		this.age = age;
	}

	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
} 

2.比较仪

创建 2 个比较器类,对人的“名字”和“年龄”属性进行排序。

FirstNameComparator.java

 package com.mkyong.common;

import java.util.Comparator;
import com.mkyong.common.Person;

public class FirstNameComparator implements Comparator<Person> {

	public int compare(Person p1, Person p2) {

		String firstName1 = p1.getFirstName().toUpperCase();
		String firstName2 = p2.getFirstName().toUpperCase();

		return firstName1.compareTo(firstName2);
	}
} 

AgeComparator.java

 package com.mkyong.common;

import java.util.Comparator;
import com.mkyong.common.Person;

public class AgeComparator implements Comparator<Person> {

	public int compare(Person p1, Person p2) {

		return p1.getAge() - p2.getAge();

	}	
} 

3.行动

Action 类,初始化 6 个 Person 对象,并将其添加到一个 ArrayList 中。

 package com.mkyong.common.action;

import java.util.ArrayList;
import java.util.List;

import com.mkyong.common.Person;
import com.opensymphony.xwork2.ActionSupport;

public class SortTagAction extends ActionSupport{

	private List<Person> persons = new ArrayList<Person>();

	public String execute() {

		Person p1 = new Person("C-First", "C-Last", 40);
		Person p2 = new Person("A-First", "A-Last", 20);
		Person p3 = new Person("B-First", "B-Last", 10);
		Person p4 = new Person("F-First", "F-Last", 33);
		Person p5 = new Person("E-First", "E-Last", 22);
		Person p6 = new Person("D-First", "D-Last", 11);

		persons.add(p1);
		persons.add(p2);
		persons.add(p3);
		persons.add(p4);
		persons.add(p5);
		persons.add(p6);

		return SUCCESS;
	}

	public List<Person> getPersons() {
		return persons;
	}
	public void setPersons(List<Person> persons) {
		this.persons = persons;
	}
} 

4.排序标签示例

一个 JSP 页面,展示了如何使用 sort 标签对带有 FirstNameComparatorAgeComparator 的列表进行排序。

sort.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
 <html>
<head>
</head>

<body>
<h1>Struts 2 Sort tag example</h1>

<s:bean name="com.mkyong.common.FirstNameComparator" var="firstNameComparator" />
<s:bean name="com.mkyong.common.AgeComparator" var="ageComparator" />

<table>
<tr>
<td>
1\. Display all persons (unsort).
<ol>
<s:iterator value="persons">
     <li><s:property value="firstName" />, 
         <s:property value="lastName" />, 
         <s:property value="age" />
     </li>
</s:iterator>
</ol>
</td>

<td>
2\. Display all persons (sort with firstName).
<s:sort comparator="#firstNameComparator" source="persons">
<ol>
<s:iterator>
     <li><s:property value="firstName" />, 
         <s:property value="lastName" />, 
         <s:property value="age" />
     </li>
</s:iterator>
</ol>
</s:sort>
</td>
</tr>

<tr>
<td colspan="2">
3\. Display all persons (sort with age).
<s:sort comparator="#ageComparator" source="persons">
<ol>
<s:iterator>
     <li><s:property value="firstName" />, 
         <s:property value="lastName" />, 
         <s:property value="age" />
     </li>
</s:iterator>
</ol>
</s:sort>
</td>
</tr>
</table>

</body>
</html> 

5.struts.xml

链接一下~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
 	<constant name="struts.devMode" value="true" />
	<package name="default" namespace="/" extends="struts-default">
		<action name="sortTagAction" 
			class="com.mkyong.common.action.SortTagAction" >
			<result name="success">pages/sort.jsp</result>
		</action>
	</package>
</struts> 

4.演示

http://localhost:8080/struts 2 example/sorttagaction . action

输出

Struts 2 Sort tag example

参考

  1. Struts 2 排序标签文档
  2. Struts 2 Bean 标签文档
  3. Java 对象排序示例

struts2

Struts 2 密码示例

原文:http://web.archive.org/web/20230101150211/http://www.mkyong.com/struts2/struts-2-spassword-password-example/

Download It – Struts2-Password-Example.zip

在 Struts 2 中,你可以使用 < s:password > 来创建一个 HTML 密码字段。例如,您可以用一个 key 属性或 label 和 name 属性声明“ s:password ”。

 <s:password key="password" />
//or
<s:textfield label="Password" name="password" /> 

两者都生成相同的 HTML 输出(默认的 xhtml 主题)。

 <td class="tdLabel">
  <label for="registerUser_password" class="label">Password:</label>
</td> 
<td>
  <input type="password" name="password" id="registerUser_password"/>
</td> 

Struts 2 示例

带有“密码”和“确认密码”字段的页面,并进行验证以确保“确认密码”与“密码”匹配。

1.属性文件

global.properties

 #Global messages
username = Username
password = Password
confirmPassword = Confirm Password
submit = Submit 

register action . properties

 #error message
username.required = Username is required
password.required = Password is required
cpassword.required = Confirm password is required
cpassword.notmatch = Confirm password is not match 

2.行动

RegisterAction.java

 package com.mkyong.user.action;

import com.opensymphony.xwork2.ActionSupport;

public class RegisterAction extends ActionSupport{

	private String username;
	private String password;
	private String confirmPassword;

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getConfirmPassword() {
		return confirmPassword;
	}

	public void setConfirmPassword(String confirmPassword) {
		this.confirmPassword = confirmPassword;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	//business logic
	public String execute() {

		return "SUCCESS";

	}

	//simple validation
	public void validate(){
		if("".equals(getUsername())){
			addFieldError("username", getText("username.required"));
		}
		if("".equals(getPassword())){
			addFieldError("password", getText("password.required"));
		}
		if("".equals(getConfirmPassword())){
			addFieldError("confirmPassword", getText("cpassword.required"));
		}

		if(!(getPassword().equals(getConfirmPassword()))){
			addFieldError("confirmPassword", getText("cpassword.notmatch"));
		}
	}

} 

3.查看页面

使用 Struts 2 " s:password "标记创建 HTML 密码字段的结果页面。

register.jsp

 <%@ taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
</head>

<body>
<h1>Struts 2 - password example</h1>

<s:form action="registerUser" namespace="/user">

	<s:textfield key="username" />
	<s:password key="password" />
	<s:password key="confirmPassword" />

	<s:submit key="submit" name="submit" />

</s:form>

</body>
</html> 

welcome.jsp

 <%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<html>

<body>
<h1>Struts 2 - password example</h1>

<h2>Password : <s:property value="password"/></h2>
<h2>Confirm Password : <s:property value="%{confirmPassword}"/></h2> 

</body>
</html> 

4.struts.xml

链接在一起~

 <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

   <constant name="struts.custom.i18n.resources" value="global" />
   <constant name="struts.devMode" value="true" />

   <package name="user" namespace="/user" extends="struts-default">
	<action name="register">
		<result>pages/register.jsp</result>
	</action>
	<action name="registerUser" 
                class="com.mkyong.user.action.RegisterAction">
		<result name="SUCCESS">pages/welcome.jsp</result>
		<result name="input">pages/register.jsp</result>
	</action>
   </package>

</struts> 

5.演示

http://localhost:8080/struts 2 example/user/register . action

Struts 2 password example

参考

  1. Struts 2 密码文档

password struts2

Struts 2 + Spring 3 + Quartz 1.8 调度器示例

原文:http://web.archive.org/web/20230101150211/https://mkyong.com/struts2/struts-2-spring-3-quartz-1-8-scheduler-example/

在本教程中,我们将向您展示如何集成Struts 2+Spring 3+Quartz 1 . 8 . 6

Why NOT Quartz 2?
Currently, Spring 3 is still NOT support Quartz 2 APIs, see this SPR-8581 bug report. Will update this article again once bug fixed is released.

使用的工具

  1. 弹簧 3.1.5 释放
  2. 石英
  3. 支柱 2.3.4
  4. 支柱 2-弹簧-插件 2.3.4
  5. maven3
  6. Eclipse 4.2

1.项目文件夹

这是最终的项目文件夹结构。

## 2.依赖库

集成与这个 Spring 2.5.6 + Quartz 1.6 类似,只是 Spring3 的依赖关系有点混乱,阅读下面的 Maven pom.xml 了解开发本教程所需的所有依赖关系。

查看 XML 注释可以理解为什么需要这个 jar。

文件:pom.xml

 ...
<dependencies>

  <!-- Struts 2 -->
  <dependency>
	<groupId>org.apache.struts</groupId>
	<artifactId>struts2-core</artifactId>
	<version>2.3.4</version>
  </dependency>

  <!-- Quartz framework -->
  <dependency>
	<groupId>org.quartz-scheduler</groupId>
	<artifactId>quartz</artifactId>
	<version>1.8.6</version>
  </dependency>

  <!-- Spring 3 dependencies -->
  <dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-core</artifactId>
	<version>3.1.2.RELEASE</version>
  </dependency>

  <dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>3.1.2.RELEASE</version>
  </dependency>

  <!-- QuartzJobBean in spring-context-support.jar -->
  <dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context-support</artifactId>
	<version>3.1.2.RELEASE</version>
  </dependency>

  <!-- Struts 2 + Spring 3 need this jar, ContextLoaderListener -->
  <dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-web</artifactId>
	<version>3.1.2.RELEASE</version>
  </dependency>

  <!-- Spring + Quartz need transaction -->
  <dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-tx</artifactId>
	<version>3.1.2.RELEASE</version>
  </dependency>

  <!-- Struts 2 + Spring integration plugins -->
  <dependency>
	<groupId>org.apache.struts</groupId>
	<artifactId>struts2-spring-plugin</artifactId>
	<version>2.3.4</version>
  </dependency>

</dependencies>
  ... 

3.弹簧 3 +石英

要集成 Spring3 和 Quartz,需要创建一个扩展了org.springframework.scheduling.quartz.QuartzJobBean的类,并实现executeInternal()方法,就像在 Quartz 中创建调度作业一样。

文件:SchedulerJob.java

 package com.mkyong.quartz;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

public class SchedulerJob extends QuartzJobBean
{

	protected void executeInternal(JobExecutionContext context)
	throws JobExecutionException {

		System.out.println("Struts 2.3.4 + Quartz 1.8.6 + Spring 3.1.2");

	}
} 

File:application context . XML–将整个 Quartz 的调度程序和 Spring 细节放在applicationContext.xml中。有关详细信息,请参见 XML 注释。

 <beans 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <!-- Spring Quartz Scheduler job -->
  <bean name="schedulerJob" class="org.springframework.scheduling.quartz.JobDetailBean">
	<property name="jobClass" value="com.mkyong.quartz.SchedulerJob" />
  </bean>

  <!-- Cron Trigger, run every 10 seconds -->
  <bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
	<property name="jobDetail" ref="schedulerJob" />
	<property name="cronExpression" value="0/10 * * * * ?" />
  </bean>

  <!-- DI -->
  <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
	<property name="jobDetails">
		<list>
			<ref bean="schedulerJob" />
		</list>
	</property>

	<property name="triggers">
		<list>
			<ref bean="cronTrigger" />
		</list>
	</property>
  </bean>

</beans> 

4.支柱 2 +弹簧 3

要集成 Struts 2 + Spring ,只需将org.springframework.web.context.ContextLoaderListener监听器类放在web.xml文件中。

Note
For detail, please read this Struts 2 + Spring integration example.

文件:web.xml

 <!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
	<display-name>Struts 2 Web Application</display-name>

	<filter>
	  <filter-name>struts2</filter-name>
	  <filter-class>
		org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
	  </filter-class>
	</filter>

	<filter-mapping>
	  <filter-name>struts2</filter-name>
	  <url-pattern>/*</url-pattern>
	</filter-mapping>

	<listener>
	  <listener-class>
		org.springframework.web.context.ContextLoaderListener
	  </listener-class>
	</listener>

</web-app> 

5.演示

完成,当 Strut2 启动时,它调用 Spring 并运行定义的 Quartz 的任务。

 Jul 24, 2012 4:49:07 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-8080"]
Jul 24, 2012 4:49:07 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["ajp-bio-8009"]
Jul 24, 2012 4:49:07 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 3430 ms
Struts 2.3.4 + Quartz 1.8.6 + Spring 3.1.2  //run this every 10 seconds.
Struts 2.3.4 + Quartz 1.8.6 + Spring 3.1.2
Struts 2.3.4 + Quartz 1.8.6 + Spring 3.1.2 

下载源代码

Download it – Struts2-Spring3-Quartz-Example.zip (20 KB).

参考

  1. Struts 2 + Spring 集成示例
  2. Struts 2 Spring 插件文档
  3. QuartzJobBean JavaDoc
  4. Spring 应支持石英 2.0 CronTrigger 接口
  5. 支柱 2 +弹簧 2.5.6 +石英集成示例

quartz scheduler spring3 struts2

posted @ 2024-11-01 16:31  绝不原创的飞龙  阅读(5)  评论(0编辑  收藏  举报