├── modules ├── utils │ ├── src │ │ ├── test │ │ │ ├── resources │ │ │ │ └── test.txt │ │ │ └── java │ │ │ │ └── org │ │ │ │ └── springside │ │ │ │ └── modules │ │ │ │ ├── utils │ │ │ │ ├── IdentitiesTest.java │ │ │ │ ├── Collecitons3Test.java │ │ │ │ ├── EncodesTest.java │ │ │ │ ├── ExceptionsTest.java │ │ │ │ ├── CryptosTest.java │ │ │ │ ├── DigestsTest.java │ │ │ │ ├── ThreadsTest.java │ │ │ │ └── ReflectionsTest.java │ │ │ │ └── test │ │ │ │ ├── rule │ │ │ │ └── TestProgress.java │ │ │ │ ├── data │ │ │ │ └── RandomData.java │ │ │ │ └── log │ │ │ │ ├── LogbackListAppenderTest.java │ │ │ │ └── LogbackListAppender.java │ │ └── main │ │ │ └── java │ │ │ └── org │ │ │ └── springside │ │ │ └── modules │ │ │ ├── constants │ │ │ ├── Charsets.java │ │ │ └── MediaTypes.java │ │ │ └── utils │ │ │ ├── Numbers.java │ │ │ ├── ThreadLocalContext.java │ │ │ ├── StringBuilderHolder.java │ │ │ ├── Ids.java │ │ │ ├── Clock.java │ │ │ ├── Exceptions.java │ │ │ ├── ConcurrentHashSet.java │ │ │ ├── Encodes.java │ │ │ ├── Threads.java │ │ │ ├── Cryptos.java │ │ │ ├── Collections3.java │ │ │ ├── ThreadPoolBuilder.java │ │ │ ├── Digests.java │ │ │ └── Reflections.java │ └── pom.xml ├── core │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── org │ │ │ │ │ └── springside │ │ │ │ │ └── modules │ │ │ │ │ └── mapper │ │ │ │ │ ├── BeanMapper.java │ │ │ │ │ ├── JsonMapper.java │ │ │ │ │ └── JaxbMapper.java │ │ │ └── resources │ │ │ │ └── META-INF │ │ │ │ └── springside-shiro.tld │ │ └── test │ │ │ └── java │ │ │ └── org │ │ │ └── springside │ │ │ └── modules │ │ │ └── mapper │ │ │ ├── JaxbMapperTest.java │ │ │ └── JsonMapperTest.java │ └── pom.xml └── pom.xml ├── support ├── h2 │ ├── h2-console.bat │ ├── h2-console.sh │ ├── README.txt │ ├── .h2.server.properties │ └── pom.xml └── .gitignore ├── examples ├── boot-api │ ├── refresh-db.sh │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── .h2.server.properties │ │ │ │ ├── db │ │ │ │ │ └── migration │ │ │ │ │ │ ├── V1.1__add_table_message.sql │ │ │ │ │ │ └── V1.0__init.sql │ │ │ │ ├── application.properties │ │ │ │ ├── data.sql │ │ │ │ ├── schema.sql │ │ │ │ └── application-prod.properties │ │ │ ├── java │ │ │ │ └── org │ │ │ │ │ └── springside │ │ │ │ │ └── examples │ │ │ │ │ └── bootapi │ │ │ │ │ ├── api │ │ │ │ │ ├── support │ │ │ │ │ │ ├── ErrorResult.java │ │ │ │ │ │ ├── ErrorPageController.java │ │ │ │ │ │ └── CustomExceptionHandler.java │ │ │ │ │ ├── AccountEndPoint.java │ │ │ │ │ └── BookEndpoint.java │ │ │ │ │ ├── dto │ │ │ │ │ ├── AccountDto.java │ │ │ │ │ └── BookDto.java │ │ │ │ │ ├── config │ │ │ │ │ ├── Profiles.java │ │ │ │ │ └── H2ConsoleConfiguration.java │ │ │ │ │ ├── repository │ │ │ │ │ ├── MessageDao.java │ │ │ │ │ ├── AccountDao.java │ │ │ │ │ └── BookDao.java │ │ │ │ │ ├── service │ │ │ │ │ ├── exception │ │ │ │ │ │ ├── ServiceException.java │ │ │ │ │ │ └── ErrorCode.java │ │ │ │ │ ├── BookAdminService.java │ │ │ │ │ ├── AccountService.java │ │ │ │ │ └── BookBorrowService.java │ │ │ │ │ ├── BootApiApplication.java │ │ │ │ │ └── domain │ │ │ │ │ ├── Account.java │ │ │ │ │ ├── Message.java │ │ │ │ │ └── Book.java │ │ │ └── webapp │ │ │ │ └── index.html │ │ └── test │ │ │ └── java │ │ │ └── org │ │ │ └── springside │ │ │ └── examples │ │ │ └── bootapi │ │ │ ├── service │ │ │ ├── AccountServiceTest.java │ │ │ └── BookBorrowServiceTest.java │ │ │ ├── functional │ │ │ ├── BaseFunctionalTest.java │ │ │ └── BookEndpointTest.java │ │ │ └── repository │ │ │ └── BookDaoTest.java │ ├── start.sh │ └── pom.xml ├── boot-showcase │ ├── README.txt │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── logback.xml │ │ │ ├── application.properties │ │ │ └── application-prod.properties │ │ │ └── java │ │ │ └── org │ │ │ └── springside │ │ │ └── examples │ │ │ └── showcase │ │ │ ├── BootShowcaseApplication.java │ │ │ ├── web │ │ │ └── HelloController.java │ │ │ ├── mail │ │ │ └── MailService.java │ │ │ └── config │ │ │ └── JavaSimonConfig.java │ └── pom.xml └── pom.xml ├── .gitignore ├── quick-start.bat ├── quick-start.sh ├── README.md └── pom.xml /modules/utils/src/test/resources/test.txt: -------------------------------------------------------------------------------- 1 | ABCDEFG 2 | ABC -------------------------------------------------------------------------------- /support/h2/h2-console.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | call mvn exec:java 3 | pause -------------------------------------------------------------------------------- /support/h2/h2-console.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "[INFO] Open a H2 web console." 4 | 5 | mvn exec:java 6 | -------------------------------------------------------------------------------- /examples/boot-api/refresh-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "cleanup the production/qa db,just for demo" 4 | 5 | mvn flyway:clean -------------------------------------------------------------------------------- /support/.gitignore: -------------------------------------------------------------------------------- 1 | jmeter/*.jtl 2 | jmeter/*.log 3 | local-script/ 4 | logstash/logstash-*-flatjar.jar 5 | logstash/*/data 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse project files 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | 7 | # Intellij project files 8 | *.iml 9 | .idea/ 10 | 11 | # Others 12 | target/ 13 | logs/ 14 | -------------------------------------------------------------------------------- /examples/boot-showcase/README.txt: -------------------------------------------------------------------------------- 1 | What is more? 2 | 3 | 定义: 4 | 5 | * 不把Spring boot的pom.xml作为parent 6 | * 自定义Logback输出格式 7 | * 禁止无用的SpringBoot Endpoint 8 | * 使用JavaSimon, 演示自定义 AOP切面与Servlet映射 9 | 10 | 功能: 11 | 12 | * email -------------------------------------------------------------------------------- /examples/boot-api/src/main/resources/.h2.server.properties: -------------------------------------------------------------------------------- 1 | #H2 Server Properties 2 | #Sat Nov 07 10:12:13 CST 2015 3 | 0=TestDB(Embedded)|org.h2.Driver|jdbc\:h2\:mem\:testdb|sa 4 | webAllowOthers=false 5 | webPort=8090 6 | webSSL=false 7 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/resources/db/migration/V1.1__add_table_message.sql: -------------------------------------------------------------------------------- 1 | create table message ( 2 | id bigint generated by default as identity, 3 | receiver_id bigint null, 4 | message varchar(256), 5 | receive_date timestamp, 6 | primary key (id) 7 | ); -------------------------------------------------------------------------------- /examples/boot-showcase/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /support/h2/README.txt: -------------------------------------------------------------------------------- 1 | Command to open a H2 web console. 2 | 3 | If the maven script doesn't work, get the h2 jar file and type below command: 4 | 5 | java -jar h2-1.4.xxx.jar -web -webPort 8090 -browser 6 | 7 | See the wiki for details: 8 | 9 | https://github.com/springside/springside4/wiki/H2database 10 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/constants/Charsets.java: -------------------------------------------------------------------------------- 1 | package org.springside.modules.constants; 2 | 3 | import java.nio.charset.Charset; 4 | 5 | /** 6 | * JDK7可直接使用java.nio.charset.StandardCharsets. 7 | * 8 | * @author calvin 9 | * 10 | */ 11 | public class Charsets { 12 | 13 | public static final Charset UTF8 = Charset.forName("UTF-8"); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /examples/boot-api/src/test/java/org/springside/examples/bootapi/service/AccountServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.service; 2 | 3 | import org.junit.Test; 4 | 5 | public class AccountServiceTest { 6 | 7 | @Test 8 | public void hash() throws Exception { 9 | System.out.println("hashPassword:" + AccountService.hashPassword("springside")); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /support/h2/.h2.server.properties: -------------------------------------------------------------------------------- 1 | #H2 Server Properties 2 | #Tue Nov 10 01:20:28 CST 2015 3 | 0=Generic H2 (Server)|org.h2.Driver|jdbc\:h2\:tcp\://localhost/~/test|sa 4 | 1=Generic H2 (Embedded)|org.h2.Driver|jdbc\:h2\:mem\:testdb|sa 5 | 2=H2 Boot API DB|org.h2.Driver|jdbc\:h2\:file\:~/.h2/bootapi;AUTO_SERVER\=TRUE;DB_CLOSE_DELAY\=-1;|sa 6 | webAllowOthers=false 7 | webPort=8090 8 | webSSL=false 9 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorResult.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.api.support; 2 | 3 | public class ErrorResult { 4 | 5 | public int code; 6 | public String message; 7 | 8 | public ErrorResult() { 9 | } 10 | 11 | public ErrorResult(int code, String message) { 12 | this.code = code; 13 | this.message = message; 14 | } 15 | } -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/dto/AccountDto.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.dto; 2 | 3 | import org.apache.commons.lang3.builder.ToStringBuilder; 4 | 5 | public class AccountDto { 6 | public Long id; 7 | public String email; 8 | public String name; 9 | 10 | @Override 11 | public String toString() { 12 | return ToStringBuilder.reflectionToString(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/config/Profiles.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.config; 2 | 3 | /** 4 | * 定义项目中使用的Profiles 5 | * 6 | * @author calvin 7 | */ 8 | public class Profiles { 9 | 10 | public static final String PRODUCTION = "prod"; 11 | public static final String NOT_PRODUCTION = "!prod"; 12 | 13 | public static final String QA = "qa"; 14 | public static final String DEVELOPMENT = "dev"; 15 | } 16 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/MessageDao.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import org.springside.examples.bootapi.domain.Message; 5 | 6 | /** 7 | * 基于Spring Data JPA的Dao接口, 自动根据接口生成实现. 8 | * 9 | * CrudRepository默认有针对实体对象的CRUD方法. 10 | */ 11 | public interface MessageDao extends CrudRepository { 12 | } 13 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/service/exception/ServiceException.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.service.exception; 2 | 3 | public class ServiceException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -8634700792767837033L; 6 | 7 | public ErrorCode errorCode; 8 | 9 | public ServiceException(String message, ErrorCode errorCode) { 10 | super(message); 11 | this.errorCode = errorCode; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/boot-api/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "start applicaiton in production mode" 4 | 5 | java -Xmx1024m -XX:MaxPermSize=128M -Djava.security.egd=file:/dev/./urandom -jar target/boot-api-5.0.0-SNAPSHOT.war --spring.profiles.active=prod 6 | 7 | #set the server port 8 | #java -Xmx1024m -jar target/boot-api-5.0.0-SNAPSHOT.war --server.port=9090 9 | 10 | #set the properties file location 11 | #java -Xmx1024m -jar target/boot-api-5.0.0-SNAPSHOT.war --spring.config.location=/var/myapp/conf -------------------------------------------------------------------------------- /examples/boot-showcase/src/main/java/org/springside/examples/showcase/BootShowcaseApplication.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.showcase; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class BootShowcaseApplication { 8 | 9 | public static void main(String[] args) throws Exception { 10 | SpringApplication.run(BootShowcaseApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/AccountDao.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import org.springside.examples.bootapi.domain.Account; 5 | 6 | /** 7 | * 基于Spring Data JPA的Dao接口, 自动根据接口生成实现. 8 | * 9 | * CrudRepository默认有针对实体对象的CRUD方法. 10 | * 11 | * Spring Data JPA 还会解释新增方法名生成新方法的实现. 12 | */ 13 | public interface AccountDao extends CrudRepository { 14 | 15 | Account findByEmail(String email); 16 | } 17 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/service/exception/ErrorCode.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.service.exception; 2 | 3 | public enum ErrorCode { 4 | 5 | BAD_REQUEST(400, 400), UNAUTHORIZED(401, 401), FORBIDDEN(403, 403), INTERNAL_SERVER_ERROR(500, 500), 6 | 7 | BOOK_STATUS_WRONG(1100, 400), BOOK_OWNERSHIP_WRONG(1101, 403), NO_TOKEN(1102, 401); 8 | 9 | public int code; 10 | public int httpStatus; 11 | 12 | ErrorCode(int code, int httpStatus) { 13 | this.code = code; 14 | this.httpStatus = httpStatus; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # server settings 2 | server.port=8080 3 | management.port=7002 4 | 5 | # application settings 6 | app.loginTimeoutSecs=600 7 | 8 | # db init settings 9 | spring.jpa.hibernate.ddl-auto=validate 10 | spring.jpa.showSql=false 11 | spring.datasource.initialize=true 12 | spring.datasource.sqlScriptEncoding=UTF-8 13 | flyway.enabled=false 14 | 15 | # other settings 16 | spring.main.show-banner=false 17 | spring.jackson.serialization.INDENT_OUTPUT=true 18 | 19 | # /info endpoint 20 | info.app.name=Spring Boot WebService Example 21 | info.app.version=${project.version} -------------------------------------------------------------------------------- /quick-start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo [Pre-Requirement] Makesure install JDK 7.0+ and set the JAVA_HOME. 3 | echo [Pre-Requirement] Makesure install Maven 3.0.3+ and set the PATH. 4 | 5 | set MVN=mvn 6 | set MAVEN_OPTS=%MAVEN_OPTS% -XX:MaxPermSize=128M 7 | 8 | echo [Step 1] Install all springside modules to local maven repository. 9 | cd modules 10 | call %MVN% clean install 11 | if errorlevel 1 goto error 12 | 13 | echo [Step 2] run boot-api project in dev mode. 14 | cd ..\examples\boot-api 15 | call %MVN% spring-boot:run 16 | if errorlevel 1 goto error 17 | 18 | 19 | goto end 20 | :error 21 | echo Error Happen!! 22 | :end 23 | pause -------------------------------------------------------------------------------- /quick-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "[Pre-Requirement] Makesure install JDK 7.0+ and set the JAVA_HOME." 4 | echo "[Pre-Requirement] Makesure install Maven 3.0.3+ and set the PATH." 5 | 6 | export MAVEN_OPTS="$MAVEN_OPTS -Xmx1024m -XX:MaxPermSize=128M -Djava.security.egd=file:/dev/./urandom" 7 | 8 | echo "[Step 1] Install all springside modules to local maven repository." 9 | cd modules 10 | mvn clean install 11 | if [ $? -ne 0 ];then 12 | echo "Quit because maven install fail" 13 | exit -1 14 | fi 15 | 16 | echo "[Step 2] run boot-api project in dev mode." 17 | cd ../examples/boot-api 18 | mvn spring-boot:run 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/boot-showcase/src/main/java/org/springside/examples/showcase/web/HelloController.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.showcase.web; 2 | 3 | import org.javasimon.aop.Monitored; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestParam; 6 | import org.springframework.web.bind.annotation.RestController; 7 | import org.springside.modules.constants.MediaTypes; 8 | 9 | @RestController 10 | public class HelloController { 11 | 12 | @RequestMapping(value = "/hello", produces = MediaTypes.TEXT_PLAIN) 13 | @Monitored 14 | public String hello(@RequestParam("name") String name) { 15 | return "hello " + name; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/Numbers.java: -------------------------------------------------------------------------------- 1 | package org.springside.modules.utils; 2 | 3 | import com.google.common.primitives.Ints; 4 | import com.google.common.primitives.Longs; 5 | 6 | /** 7 | * 数字的工具类 8 | * 9 | * 1.基于Guava的原始类型与byte[]的转换. 10 | * 11 | */ 12 | public class Numbers { 13 | 14 | public static byte[] longToBytes(long value) { 15 | return Longs.toByteArray(value); 16 | } 17 | 18 | public static long bytesToLong(byte[] bytes) { 19 | return Longs.fromByteArray(bytes); 20 | } 21 | 22 | public static byte[] intToBytes(int value) { 23 | return Ints.toByteArray(value); 24 | } 25 | 26 | public static int bytesToInt(byte[] bytes) { 27 | return Ints.fromByteArray(bytes); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/utils/IdentitiesTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import org.junit.Test; 9 | 10 | public class IdentitiesTest { 11 | 12 | @Test 13 | public void demo() { 14 | System.out.println("uuid: " + Ids.uuid()); 15 | System.out.println("uuid2:" + Ids.uuid2()); 16 | System.out.println("randomLong: " + Ids.randomLong()); 17 | System.out.println("randomBase62:" + Ids.randomBase62(7)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/repository/BookDao.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.repository.PagingAndSortingRepository; 7 | import org.springside.examples.bootapi.domain.Book; 8 | 9 | /** 10 | * 基于Spring Data JPA的Dao接口, 自动根据接口生成实现. 11 | * 12 | * PagingAndSortingRepository默认有针对实体对象的CRUD与分页查询函数. 13 | * 14 | * Spring Data JPA 还会解释新增方法名生成新方法的实现. 15 | */ 16 | public interface BookDao extends PagingAndSortingRepository { 17 | 18 | List findByOwnerId(Long ownerId, Pageable pageable); 19 | 20 | List findByBorrowerId(Long borrowerId, Pageable pageable); 21 | } 22 | -------------------------------------------------------------------------------- /examples/boot-showcase/src/main/java/org/springside/examples/showcase/mail/MailService.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.showcase.mail; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.mail.SimpleMailMessage; 5 | import org.springframework.mail.javamail.JavaMailSender; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class MailService { 10 | 11 | @Autowired 12 | private JavaMailSender mailSender; 13 | 14 | public void sendMail(String to, String from, String subject, String content) { 15 | SimpleMailMessage mail = new SimpleMailMessage(); 16 | mail.setFrom(from); 17 | mail.setTo(to); 18 | mail.setSubject(subject); 19 | mail.setText(content); 20 | mailSender.send(mail); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/BootApiApplication.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | // SpringBoot 应用标识 7 | @SpringBootApplication 8 | public class BootApiApplication { 9 | 10 | /** 11 | * 启动嵌入式的Tomcat并初始化Spring环境. 12 | * 13 | * 无 applicationContext.xml 和 web.xml, 靠下述方式进行配置: 14 | * 15 | * 1. 扫描当前package下的class设置自动注入的Bean
16 | * 2. 也支持用@Bean标注的类配置Bean
17 | * 3. 根据classpath中的三方包Class及集中的application.properties条件配置三方包,如线程池
18 | * 4. 也支持用@Configuration标注的类配置三方包. 19 | */ 20 | public static void main(String[] args) throws Exception { 21 | SpringApplication.run(BootApiApplication.class, args); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.springside 7 | springside-parent 8 | 5.0.0-SNAPSHOT 9 | ../modules/parent/ 10 | 11 | io.springside.examples 12 | springside-examples 13 | Springside :: Examples 14 | pom 15 | 16 | 17 | boot-api 18 | boot-showcase 19 | 20 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/test/rule/TestProgress.java: -------------------------------------------------------------------------------- 1 | package org.springside.modules.test.rule; 2 | 3 | import org.junit.rules.TestWatcher; 4 | import org.junit.runner.Description; 5 | 6 | /** 7 | * 在Console里打印Case的开始与结束,更容易分清Console里的日志归属于哪个Case. 8 | * 9 | * @author calvin 10 | */ 11 | public class TestProgress extends TestWatcher { 12 | 13 | @Override 14 | protected void starting(Description description) { 15 | System.out.println("\n[Test Case starting] " + description.getTestClass().getSimpleName() + "." 16 | + description.getMethodName() + "()\n"); 17 | } 18 | 19 | @Override 20 | protected void finished(Description description) { 21 | System.out.println("\n[Test Case finished] " + description.getTestClass().getSimpleName() + "." 22 | + description.getMethodName() + "()\n"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/dto/BookDto.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.dto; 2 | 3 | import java.util.Date; 4 | 5 | import org.apache.commons.lang3.builder.ToStringBuilder; 6 | 7 | import com.fasterxml.jackson.annotation.JsonFormat; 8 | 9 | public class BookDto { 10 | 11 | public Long id; 12 | public String bookId; 13 | public String title; 14 | public String url; 15 | public String status; 16 | 17 | public AccountDto owner; 18 | 19 | @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+08:00") 20 | public Date onboardDate; 21 | 22 | public AccountDto borrower; 23 | 24 | @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+08:00") 25 | public Date borrowDate; 26 | 27 | @Override 28 | public String toString() { 29 | return ToStringBuilder.reflectionToString(this); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | 2 | insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(1,'25984046', 'Big Data日知录', 'http://book.douban.com/subject/25984046/','', 1,'idle','2015-01-01'); 3 | insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(2,'25900156', 'Redis设计与实现', 'http://book.douban.com/subject/25900156/','', 1,'idle','2015-01-02'); 4 | insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(3,'25741352', 'DSL实战', 'http://book.douban.com/subject/25741352/','', 2,'idle','2015-01-03'); 5 | 6 | insert into account (id,email,name,hash_password) values(1,'calvin.xiao@springside.io','Calvin','+2MunThvGcEfdYIFlT4NQQHt6z4='); 7 | insert into account (id,email,name,hash_password) values(2,'david.wang@springside.io','David','+2MunThvGcEfdYIFlT4NQQHt6z4='); 8 | 9 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Account.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.domain; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | 8 | import org.apache.commons.lang3.builder.ToStringBuilder; 9 | 10 | // JPA实体类的标识 11 | @Entity 12 | public class Account { 13 | 14 | // JPA 主键标识, 策略为由数据库生成主键 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | public Long id; 18 | 19 | public String email; 20 | public String name; 21 | public String hashPassword; 22 | 23 | public Account() { 24 | 25 | } 26 | 27 | public Account(Long id) { 28 | this.id = id; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return ToStringBuilder.reflectionToString(this); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | drop table if exists book; 2 | drop table if exists account; 3 | drop table if exists message; 4 | 5 | create table book ( 6 | id bigint generated by default as identity, 7 | douban_id varchar(64) not null, 8 | title varchar(128) not null, 9 | url varchar(255), 10 | description varchar(255), 11 | owner_id bigint not null, 12 | onboard_date timestamp, 13 | status varchar(32) not null, 14 | borrower_id bigint null, 15 | borrow_date timestamp, 16 | primary key (id) 17 | ); 18 | 19 | create table account ( 20 | id bigint generated by default as identity, 21 | name varchar(64) not null, 22 | email varchar(128), 23 | hash_password varchar(255), 24 | primary key (id) 25 | ); 26 | 27 | create table message ( 28 | id bigint generated by default as identity, 29 | receiver_id bigint null, 30 | message varchar(256), 31 | receive_date timestamp, 32 | primary key (id) 33 | ); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SpringSide是以Spring Framework为核心的,Pragmatic风格的JavaEE应用参考示例,是JavaEE世界中的主流技术选型,最佳实践的总结与演示。 2 | 3 | 1. BootApi - 基于Spring Boot的Web Service应用, 可以用于SOA服务,或Ajax页面的后台. 4 | 3. BootWeb - 基于Spring Boot的Web应用, 典型的增删改查管理. 5 | 3. Showcase - 更多的示例. 6 | 7 | 8 | ## 主要用例 9 | 10 | 全部示例以一个P2P图书馆展开,P2P图书馆避免了中央式图书馆所需的场地和图书管理员,大家把图书登记在应用里互相借阅。 11 | 12 | 13 | **图书借阅流程** 14 | 15 | 1. 用户浏览图书 16 | 17 | 2. 用户发起借阅请求,也可以取消借阅请求 18 | 19 | 3. 图书拥有者在交接图书后确认借阅,也可以拒绝借阅请求 20 | 21 | 4. 图书拥有者在收回图书后确认归还 22 | 23 | **图书管理流程** 24 | 25 | 用户可以自行上传,修改,删除自己的图书。 26 | 27 | **用户管理流程** 28 | 29 | 用户可以注册,登录与注销。 30 | 31 | ## 快速开始 32 | 33 | 1. 运行根目录下的quick-start.sh 或 quick-start.bat 34 | * 将modules安装到本地maven仓库 35 | * 以开发模式启动BootApi应用 36 | 37 | 2. 访问 http://localhost:8080/,按上面的提示体验。 38 | 39 | 40 | ------------------------------- 41 | Offical Site: http://springside.io 42 | 43 | Document: https://github.com/springside/springside4/wiki -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/ThreadLocalContext.java: -------------------------------------------------------------------------------- 1 | package org.springside.modules.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * 存储于ThreadLocal的上下文信息. 8 | */ 9 | public class ThreadLocalContext { 10 | 11 | private static ThreadLocal> context = new ThreadLocal>() { 12 | @Override 13 | protected Map initialValue() { 14 | return new HashMap(); 15 | } 16 | }; 17 | 18 | /** 19 | * 放入ThreadLocal的Context. 20 | */ 21 | public static void put(String key, Object value) { 22 | context.get().put(key, value); 23 | } 24 | 25 | /** 26 | * 取出ThreadLocal的Context. 27 | */ 28 | @SuppressWarnings("unchecked") 29 | public static T get(String key) { 30 | return (T) (context.get().get(key)); 31 | } 32 | 33 | /** 34 | * 清理ThreadLocal的Context内容. 35 | */ 36 | public static void reset() { 37 | context.get().clear(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/boot-showcase/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # server 2 | server.port=8080 3 | management.port=7002 4 | 5 | # db init settings 6 | spring.jpa.hibernate.ddl-auto=validate 7 | spring.jpa.showSql=false 8 | spring.datasource.initialize=true 9 | spring.datasource.sqlScriptEncoding=UTF-8 10 | 11 | # other 12 | spring.main.show-banner=false 13 | http.mappers.json-pretty-print=true 14 | http.mappers.json-sort-keys=true 15 | 16 | # disable useless endpoints 17 | endpoints.autoconfig.enabled=false 18 | endpoints.beans.enabled=false 19 | endpoints.configprops.enabled=false 20 | endpoints.mappings.enabled=false 21 | endpoints.trace.enabled=false 22 | #endpoints.shutdown.enabled=true 23 | 24 | # mail setting 25 | spring.mail.host=smtp.163.com 26 | spring.mail.username=springside@163.com 27 | spring.mail.password=springside123 28 | spring.mail.properties.mail.smtp.auth=true 29 | 30 | # /info endpoint 31 | info.app.name=Advanced Spring Boot Showcase Example 32 | info.app.version=${project.version} 33 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/StringBuilderHolder.java: -------------------------------------------------------------------------------- 1 | package org.springside.modules.utils; 2 | 3 | /** 4 | * 参考BigDecimal, 可重用的StringBuilder, 节约StringBuilder内部的char[] 5 | * 6 | * 参考下面的示例代码将其保存为ThreadLocal. 7 | * 8 | *
 9 |  * private static final ThreadLocal threadLocalStringBuilderHolder = new ThreadLocal() {
10 |  * 	@Override
11 |  * 	protected StringBuilderHelper initialValue() {
12 |  * 		return new StringBuilderHelper(256);
13 |  * 	}
14 |  * };
15 |  * 
16 |  * StringBuilder sb = threadLocalStringBuilderHolder.get().resetAndGetStringBuilder();
17 |  * 
18 |  * 
19 | * 20 | * @author calvin 21 | * 22 | */ 23 | public class StringBuilderHolder { 24 | 25 | private final StringBuilder sb; 26 | 27 | public StringBuilderHolder(int capacity) { 28 | sb = new StringBuilder(capacity); 29 | } 30 | 31 | /** 32 | * 重置StringBuilder内部的writerIndex, 而char[]保留不动. 33 | */ 34 | public StringBuilder resetAndGetStringBuilder() { 35 | sb.setLength(0); 36 | return sb; 37 | } 38 | } -------------------------------------------------------------------------------- /examples/boot-showcase/src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.database=H2 2 | 3 | spring.datasource.driverClassName=org.h2.Driver 4 | spring.datasource.url=jdbc:h2:file:~/.h2/bootmore;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1; 5 | spring.datasource.username=sa 6 | spring.datasource.password= 7 | 8 | spring.datasource.initialize=false 9 | spring.jpa.hibernate.ddl-auto=none 10 | 11 | spring.datasource.initial-size=10 12 | spring.datasource.max-active=100 13 | spring.datasource.min-idle=8 14 | spring.datasource.max-idle=8 15 | #spring.datasource.max-wait= 16 | #spring.datasource.time-between-eviction-runs-millis= 17 | #spring.datasource.min-evictable-idle-time-millis= 18 | 19 | 20 | # logging 21 | logging.file=/var/log/springside/boot-showcase.log 22 | #logging.level.org.hibernate=WARN 23 | 24 | # tomcat settings 25 | #server.contextPath=/ by default 26 | #server.tomcat.maxThreads=200 by default 27 | #server.tomcat.compression=on(off by default) 28 | #server.tomcat.compressableMimeTypes=application/json,application/xml (text/html, text/xml, and text/plain by default) 29 | #server.address 30 | #server.sessiontimeout 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Message.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.domain; 2 | 3 | import java.util.Date; 4 | 5 | import javax.persistence.Entity; 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.GenerationType; 8 | import javax.persistence.Id; 9 | import javax.persistence.JoinColumn; 10 | import javax.persistence.ManyToOne; 11 | 12 | import org.apache.commons.lang3.builder.ToStringBuilder; 13 | 14 | // JPA实体类的标识 15 | @Entity 16 | public class Message { 17 | 18 | // JPA 主键标识, 策略为由数据库生成主键 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | public Long id; 22 | 23 | @ManyToOne 24 | @JoinColumn(name = "receiver_id") 25 | public Account receiver; 26 | 27 | public String message; 28 | 29 | public Date receiveDate; 30 | 31 | public Message() { 32 | 33 | } 34 | 35 | public Message(Account receiver, String message, Date receiveDate) { 36 | this.receiver = receiver; 37 | this.message = message; 38 | this.receiveDate = receiveDate; 39 | } 40 | 41 | @Override 42 | public String toString() { 43 | return ToStringBuilder.reflectionToString(this); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | #set H2 in file mode as the production DB 2 | spring.jpa.database=H2 3 | spring.datasource.driverClassName=org.h2.Driver 4 | spring.datasource.url=jdbc:h2:file:~/.h2/bootapi;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1; 5 | spring.datasource.username=sa 6 | spring.datasource.password= 7 | 8 | #disable automatic initialize for embedded H2 9 | spring.jpa.hibernate.ddl-auto=none 10 | spring.datasource.initialize=false 11 | flyway.enabled=true 12 | 13 | #connection pool settings 14 | spring.datasource.initial-size=10 15 | spring.datasource.max-active=100 16 | spring.datasource.min-idle=8 17 | spring.datasource.max-idle=8 18 | #spring.datasource.max-wait= 19 | #spring.datasource.time-between-eviction-runs-millis= 20 | #spring.datasource.min-evictable-idle-time-millis= 21 | 22 | 23 | # logging settings 24 | logging.file=/var/log/springside/boot-api.log 25 | #logging.level.org.hibernate=WARN 26 | 27 | # optional tomcat settings 28 | #server.contextPath=/ by default 29 | #server.tomcat.maxThreads=200 by default 30 | #server.tomcat.compression=on(off by default) 31 | #server.tomcat.compressableMimeTypes=application/json,application/xml (text/html, text/xml, and text/plain by default) -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/config/H2ConsoleConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.config; 2 | 3 | import javax.servlet.ServletContext; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.ServletRegistration; 6 | 7 | import org.springframework.boot.context.embedded.ServletContextInitializer; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Profile; 10 | 11 | /** 12 | * 在非生产环境里,初始化H2Console管理嵌入式H2. 13 | * 14 | * @author calvin 15 | */ 16 | @Configuration 17 | @Profile(Profiles.NOT_PRODUCTION) 18 | public class H2ConsoleConfiguration implements ServletContextInitializer { 19 | 20 | @Override 21 | public void onStartup(ServletContext servletContext) throws ServletException { 22 | initH2Console(servletContext); 23 | } 24 | 25 | private void initH2Console(ServletContext servletContext) { 26 | ServletRegistration.Dynamic h2ConsoleServlet = servletContext.addServlet("H2Console", 27 | new org.h2.server.web.WebServlet()); 28 | h2ConsoleServlet.addMapping("/h2/*"); 29 | h2ConsoleServlet.setInitParameter("-properties", "src/main/resources"); 30 | h2ConsoleServlet.setLoadOnStartup(1); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/boot-api/src/test/java/org/springside/examples/bootapi/functional/BaseFunctionalTest.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.functional; 2 | 3 | import org.junit.Rule; 4 | import org.junit.rules.TestRule; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.test.SpringApplicationConfiguration; 8 | import org.springframework.boot.test.WebIntegrationTest; 9 | import org.springframework.test.annotation.DirtiesContext; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | import org.springside.examples.bootapi.BootApiApplication; 12 | import org.springside.modules.test.rule.TestProgress; 13 | 14 | @RunWith(SpringJUnit4ClassRunner.class) 15 | @SpringApplicationConfiguration(classes = BootApiApplication.class) 16 | // 定义为Web集成测试,并使用随机端口号 17 | @WebIntegrationTest("server.port=0") 18 | // 定义每执行完一个Test文件刷新一次Spring Application Context,避免Case间的数据影响. 19 | // 但Test文件内多个测试方法间的影响仍需注意 20 | @DirtiesContext 21 | public abstract class BaseFunctionalTest { 22 | 23 | // 注入启动server后的实际端口号 24 | @Value("${local.server.port}") 25 | protected int port; 26 | 27 | // 在Console里打印Case的开始与结束,更容易分清Console里的日志归属于哪个Case. 28 | @Rule 29 | public TestRule testProgress = new TestProgress(); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /modules/core/src/main/java/org/springside/modules/mapper/BeanMapper.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.mapper; 7 | 8 | import java.util.List; 9 | 10 | import ma.glasnost.orika.MapperFacade; 11 | import ma.glasnost.orika.MapperFactory; 12 | import ma.glasnost.orika.impl.DefaultMapperFactory; 13 | 14 | /** 15 | * 简单封装orika, 实现深度转换Bean<->Bean的Mapper. 16 | */ 17 | public class BeanMapper { 18 | 19 | private static MapperFacade mapper = null; 20 | 21 | static { 22 | MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); 23 | mapper = mapperFactory.getMapperFacade(); 24 | 25 | } 26 | 27 | /** 28 | * 基于Dozer转换对象的类型. 29 | */ 30 | public static D map(S source, Class destinationClass) { 31 | return mapper.map(source, destinationClass); 32 | } 33 | 34 | /** 35 | * 基于Dozer转换Collection中对象的类型. 36 | */ 37 | public static List mapList(Iterable sourceList, Class destinationClass) { 38 | return mapper.mapAsList(sourceList, destinationClass); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/Ids.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import java.security.SecureRandom; 9 | import java.util.UUID; 10 | 11 | /** 12 | * 封装各种生成唯一性ID算法的工具类. 13 | * 14 | * @author calvin 15 | */ 16 | public class Ids { 17 | 18 | private static SecureRandom random = new SecureRandom(); 19 | 20 | /** 21 | * 封装JDK自带的UUID, 通过Random数字生成, 中间有-分割. 22 | */ 23 | public static String uuid() { 24 | return UUID.randomUUID().toString(); 25 | } 26 | 27 | /** 28 | * 封装JDK自带的UUID, 通过Random数字生成, 中间无-分割. 29 | */ 30 | public static String uuid2() { 31 | return UUID.randomUUID().toString().replaceAll("-", ""); 32 | } 33 | 34 | /** 35 | * 使用SecureRandom随机生成Long. 36 | */ 37 | public static long randomLong() { 38 | return Math.abs(random.nextLong()); 39 | } 40 | 41 | /** 42 | * 基于Base62编码的SecureRandom随机生成bytes. 43 | */ 44 | public static String randomBase62(int length) { 45 | byte[] randomBytes = new byte[length]; 46 | random.nextBytes(randomBytes); 47 | return Encodes.encodeBase62(randomBytes); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/boot-api/src/test/java/org/springside/examples/bootapi/repository/BookDaoTest.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.repository; 2 | 3 | import static org.assertj.core.api.Assertions.*; 4 | 5 | import java.util.List; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.SpringApplicationConfiguration; 11 | import org.springframework.data.domain.PageRequest; 12 | import org.springframework.test.annotation.DirtiesContext; 13 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 14 | import org.springside.examples.bootapi.BootApiApplication; 15 | import org.springside.examples.bootapi.domain.Book; 16 | 17 | @RunWith(SpringJUnit4ClassRunner.class) 18 | @SpringApplicationConfiguration(classes = BootApiApplication.class) 19 | @DirtiesContext 20 | public class BookDaoTest { 21 | 22 | @Autowired 23 | private BookDao bookDao; 24 | 25 | @Test 26 | public void findByOwnerId() { 27 | List books = bookDao.findByOwnerId(1L, new PageRequest(0, 10)); 28 | assertThat(books).hasSize(2); 29 | assertThat(books.get(0).title).isEqualTo("Big Data日知录"); 30 | } 31 | 32 | @Test 33 | public void findByBorrowerId() { 34 | List books = bookDao.findByBorrowerId(1L, new PageRequest(0, 10)); 35 | assertThat(books).hasSize(0); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/boot-showcase/src/main/java/org/springside/examples/showcase/config/JavaSimonConfig.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.showcase.config; 2 | 3 | import org.javasimon.console.SimonConsoleServlet; 4 | import org.javasimon.spring.MonitoredMeasuringPointcut; 5 | import org.javasimon.spring.MonitoringInterceptor; 6 | import org.springframework.aop.support.DefaultPointcutAdvisor; 7 | import org.springframework.boot.context.embedded.ServletRegistrationBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * 演示配置能力,包括AOP配置与Servlet配置 13 | */ 14 | @Configuration 15 | public class JavaSimonConfig { 16 | 17 | // 定义AOP, 对标注了@Monitored的方法进行监控 18 | @Bean(name = "monitoringAdvisor") 19 | public DefaultPointcutAdvisor monitoringAdvisor() { 20 | DefaultPointcutAdvisor monitoringAdvisor = new DefaultPointcutAdvisor(); 21 | monitoringAdvisor.setAdvice(new MonitoringInterceptor()); 22 | monitoringAdvisor.setPointcut(new MonitoredMeasuringPointcut()); 23 | return monitoringAdvisor; 24 | } 25 | 26 | // 定义Servlet URL Mapping 27 | @Bean 28 | public ServletRegistrationBean dispatcherRegistration() { 29 | ServletRegistrationBean registration = new ServletRegistrationBean(new SimonConsoleServlet()); 30 | registration.addInitParameter("url-prefix", "/javasimon"); 31 | registration.addUrlMappings("/javasimon/*"); 32 | return registration; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/domain/Book.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.domain; 2 | 3 | import java.util.Date; 4 | 5 | import javax.persistence.Entity; 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.GenerationType; 8 | import javax.persistence.Id; 9 | import javax.persistence.JoinColumn; 10 | import javax.persistence.ManyToOne; 11 | 12 | import org.apache.commons.lang3.builder.ToStringBuilder; 13 | 14 | // JPA实体类的标识 15 | @Entity 16 | public class Book { 17 | 18 | public static final String STATUS_IDLE = "idle"; 19 | public static final String STATUS_REQUEST = "request"; 20 | public static final String STATUS_OUT = "out"; 21 | 22 | // JPA 主键标识, 策略为由数据库生成主键 23 | @Id 24 | @GeneratedValue(strategy = GenerationType.IDENTITY) 25 | public Long id; 26 | 27 | public String doubanId; 28 | 29 | public String title; 30 | 31 | public String url; 32 | 33 | public String status; 34 | 35 | @ManyToOne 36 | @JoinColumn(name = "owner_id") 37 | public Account owner; 38 | 39 | public Date onboardDate; 40 | 41 | @ManyToOne 42 | @JoinColumn(name = "borrower_id") 43 | public Account borrower; 44 | 45 | public Date borrowDate; 46 | 47 | public Book() { 48 | } 49 | 50 | public Book(Long id) { 51 | this.id = id; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return ToStringBuilder.reflectionToString(this); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/resources/db/migration/V1.0__init.sql: -------------------------------------------------------------------------------- 1 | create table book ( 2 | id bigint generated by default as identity, 3 | douban_id varchar(64) not null, 4 | title varchar(128) not null, 5 | url varchar(255), 6 | description varchar(255), 7 | owner_id bigint not null, 8 | onboard_date timestamp, 9 | status varchar(32) not null, 10 | borrower_id bigint null, 11 | borrow_date timestamp, 12 | primary key (id) 13 | ); 14 | 15 | create table account ( 16 | id bigint generated by default as identity, 17 | name varchar(64) not null, 18 | email varchar(128), 19 | hash_password varchar(255), 20 | primary key (id) 21 | ); 22 | 23 | 24 | insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(1,'25984046', 'Big Data日知录', 'http://book.douban.com/subject/25984046/','', 1,'idle','2015-01-01'); 25 | insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(2,'25900156', 'Redis设计与实现', 'http://book.douban.com/subject/25900156/','', 1,'idle','2015-01-02'); 26 | insert into book (id, douban_id, title, url, description, owner_id,status,onboard_date) values(3,'25741352', 'DSL实战', 'http://book.douban.com/subject/25741352/','', 2,'idle','2015-01-03'); 27 | 28 | insert into account (id,email,name,hash_password) values(1,'calvin.xiao@springside.io','Calvin','+2MunThvGcEfdYIFlT4NQQHt6z4='); 29 | insert into account (id,email,name,hash_password) values(2,'david.wang@springside.io','David','+2MunThvGcEfdYIFlT4NQQHt6z4='); -------------------------------------------------------------------------------- /support/h2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | io.springside 6 | h2-console 7 | 5.0.0-SNAPSHOT 8 | H2 Console 9 | pom 10 | 11 | 12 | 13 | com.h2database 14 | h2 15 | 1.4.190 16 | runtime 17 | 18 | 19 | 20 | 21 | 22 | 23 | org.codehaus.mojo 24 | exec-maven-plugin 25 | 26 | 27 | 28 | java 29 | 30 | 31 | 32 | 33 | org.h2.tools.Server 34 | 35 | -web 36 | -webPort 37 | 8090 38 | -browser 39 | -properties 40 | . 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/test/data/RandomData.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.test.data; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Random; 11 | 12 | /** 13 | * 随机测试数据生成工具类. 14 | * 15 | * @author calvin 16 | */ 17 | public class RandomData { 18 | 19 | private static Random random = new Random(); 20 | 21 | /** 22 | * 返回随机ID. 23 | */ 24 | public static long randomId() { 25 | return random.nextLong(); 26 | } 27 | 28 | /** 29 | * 返回随机名称, prefix字符串+5位随机数字. 30 | */ 31 | public static String randomName(String prefix) { 32 | return prefix + random.nextInt(10000); 33 | } 34 | 35 | /** 36 | * 从输入list中随机返回一个对象. 37 | */ 38 | public static T randomOne(List list) { 39 | Collections.shuffle(list); 40 | return list.get(0); 41 | } 42 | 43 | /** 44 | * 从输入list中随机返回n个对象. 45 | */ 46 | public static List randomSome(List list, int n) { 47 | Collections.shuffle(list); 48 | return list.subList(0, n); 49 | } 50 | 51 | /** 52 | * 从输入list中随机返回随机个对象. 53 | */ 54 | public static List randomSome(List list) { 55 | int size = random.nextInt(list.size()); 56 | if (size == 0) { 57 | size = 1; 58 | } 59 | return randomSome(list, size); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /modules/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | io.springside 6 | springside-modules 7 | 5.0.0-SNAPSHOT 8 | Springside :: Module 9 | pom 10 | 11 | 4.1.8.RELEASE 12 | 5.0.0-SNAPSHOT 13 | 18.0 14 | 3.4 15 | 1.10 16 | 2.4 17 | 1.9.2 18 | 1.4.6 19 | 2.6.1 20 | 1.7.12 21 | 1.1.3 22 | 4.12 23 | 2.2.0 24 | 1.10.19 25 | 1.6.3 26 | 27 | 28 | UTF-8 29 | 1.7 30 | ${java.version} 31 | ${java.version} 32 | 33 | 34 | 35 | utils 36 | core 37 | 38 | 39 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/constants/MediaTypes.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.constants; 7 | 8 | /** 9 | * 带UTF-8 charset 定义的MediaType. 10 | * 11 | * Jax-RS和Spring的MediaType没有UTF-8的版本; 12 | * 13 | * Google的MediaType必须再调用toString()函数而不是常量,不能用于Restful方法的annotation。 14 | * 15 | * @author calvin 16 | */ 17 | public class MediaTypes { 18 | 19 | public static final String APPLICATION_XML = "application/xml"; 20 | public static final String APPLICATION_XML_UTF_8 = "application/xml; charset=UTF-8"; 21 | 22 | public static final String JSON = "application/json"; 23 | public static final String JSON_UTF_8 = "application/json; charset=UTF-8"; 24 | 25 | public static final String JAVASCRIPT = "application/javascript"; 26 | public static final String JAVASCRIPT_UTF_8 = "application/javascript; charset=UTF-8"; 27 | 28 | public static final String APPLICATION_XHTML_XML = "application/xhtml+xml"; 29 | public static final String APPLICATION_XHTML_XML_UTF_8 = "application/xhtml+xml; charset=UTF-8"; 30 | 31 | public static final String TEXT_PLAIN = "text/plain"; 32 | public static final String TEXT_PLAIN_UTF_8 = "text/plain; charset=UTF-8"; 33 | 34 | public static final String TEXT_XML = "text/xml"; 35 | public static final String TEXT_XML_UTF_8 = "text/xml; charset=UTF-8"; 36 | 37 | public static final String TEXT_HTML = "text/html"; 38 | public static final String TEXT_HTML_UTF_8 = "text/html; charset=UTF-8"; 39 | } 40 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | io.springside 5 | springside-project 6 | 5.0.0-SNAPSHOT 7 | Springside :: Project 8 | pom 9 | 10 | SpringSide is a Spring Framework based JavaEE application reference architecture. 11 | http://www.github.com/springside/springside4 12 | 2006-2012 13 | 14 | SpringSide 15 | http://www.springside.org.cn 16 | 17 | 18 | 19 | 20 | calvin 21 | Calvin Xiao 22 | calvinxiu at gmail.com 23 | 24 | Project leader 25 | 26 | +8 27 | 28 | 29 | 30 | 31 | 32 | Apache License, Version 2.0 33 | http://www.apache.org/licenses/LICENSE-2.0 34 | 35 | 36 | 37 | 38 | modules 39 | examples 40 | 41 | 42 | 43 | Github 44 | https://github.com/springside/springside4/issues 45 | 46 | 47 | 48 | https://github.com/springside/springside4 49 | scm:git:git://github.com/springside/springside4.git 50 | scm:git:ssh://git@github.com:springside/springside4.git 51 | 52 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/utils/Collecitons3Test.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import static org.assertj.core.api.Assertions.*; 9 | 10 | import java.util.List; 11 | 12 | import org.junit.Test; 13 | 14 | import com.google.common.collect.Lists; 15 | 16 | public class Collecitons3Test { 17 | 18 | @Test 19 | public void convertElementPropertyToString() { 20 | TestBean3 bean1 = new TestBean3(); 21 | bean1.setId(1); 22 | TestBean3 bean2 = new TestBean3(); 23 | bean2.setId(2); 24 | 25 | List list = Lists.newArrayList(bean1, bean2); 26 | 27 | assertThat(Collections3.extractToString(list, "id", ",")).isEqualTo("1,2"); 28 | } 29 | 30 | @Test 31 | public void convertElementPropertyToList() { 32 | TestBean3 bean1 = new TestBean3(); 33 | bean1.setId(1); 34 | TestBean3 bean2 = new TestBean3(); 35 | bean2.setId(2); 36 | 37 | List list = Lists.newArrayList(bean1, bean2); 38 | List result = Collections3.extractToList(list, "id"); 39 | assertThat(result).containsOnly(1, 2); 40 | } 41 | 42 | @Test 43 | public void convertCollectionToString() { 44 | List list = Lists.newArrayList("aa", "bb"); 45 | String result = Collections3.convertToString(list, ","); 46 | assertThat(result).isEqualTo("aa,bb"); 47 | 48 | result = Collections3.convertToString(list, "
  • ", "
  • "); 49 | assertThat(result).isEqualTo("
  • aa
  • bb
  • "); 50 | } 51 | 52 | public static class TestBean3 { 53 | 54 | private int id; 55 | 56 | public int getId() { 57 | return id; 58 | } 59 | 60 | public void setId(int id) { 61 | this.id = id; 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/utils/EncodesTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import static org.assertj.core.api.Assertions.*; 9 | 10 | import org.junit.Test; 11 | 12 | public class EncodesTest { 13 | 14 | @Test 15 | public void hexEncode() { 16 | String input = "haha,i am a very long message"; 17 | String result = Encodes.encodeHex(input.getBytes()); 18 | assertThat(new String(Encodes.decodeHex(result))).isEqualTo(input); 19 | } 20 | 21 | @Test 22 | public void base64Encode() { 23 | String input = "haha,i am a very long message"; 24 | String result = Encodes.encodeBase64(input.getBytes()); 25 | assertThat(new String(Encodes.decodeBase64(result))).isEqualTo(input); 26 | } 27 | 28 | @Test 29 | public void base64UrlSafeEncode() { 30 | String input = "haha,i am a very long message"; 31 | String result = Encodes.encodeUrlSafeBase64(input.getBytes()); 32 | assertThat(new String(Encodes.decodeBase64(result))).isEqualTo(input); 33 | } 34 | 35 | @Test 36 | public void urlEncode() { 37 | String input = "http://locahost/?q=中文&t=1"; 38 | String result = Encodes.urlEncode(input); 39 | System.out.println(result); 40 | 41 | assertThat(Encodes.urlDecode(result)).isEqualTo(input); 42 | } 43 | 44 | @Test 45 | public void xmlEncode() { 46 | String input = "1>2"; 47 | String result = Encodes.escapeXml(input); 48 | assertThat(result).isEqualTo("1>2"); 49 | assertThat(Encodes.unescapeXml(result)).isEqualTo(input); 50 | } 51 | 52 | @Test 53 | public void html() { 54 | String input = "1>2"; 55 | String result = Encodes.escapeHtml(input); 56 | assertThat(result).isEqualTo("1>2"); 57 | assertThat(Encodes.unescapeHtml(result)).isEqualTo(input); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/Clock.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * 日期提供者,使用它而不是直接取得系统时间,方便测试。 12 | * 13 | * @author calvin 14 | */ 15 | public interface Clock { 16 | 17 | static final Clock DEFAULT = new DefaultClock(); 18 | 19 | Date getCurrentDate(); 20 | 21 | long getCurrentTimeInMillis(); 22 | 23 | /** 24 | * 默认时间提供者,返回当前的时间,线程安全。 25 | */ 26 | public static class DefaultClock implements Clock { 27 | 28 | @Override 29 | public Date getCurrentDate() { 30 | return new Date(); 31 | } 32 | 33 | @Override 34 | public long getCurrentTimeInMillis() { 35 | return System.currentTimeMillis(); 36 | } 37 | } 38 | 39 | /** 40 | * 可配置的时间提供者,用于测试. 41 | */ 42 | public static class MockClock implements Clock { 43 | 44 | private long time; 45 | 46 | public MockClock() { 47 | this(0); 48 | } 49 | 50 | public MockClock(Date date) { 51 | this.time = date.getTime(); 52 | } 53 | 54 | public MockClock(long time) { 55 | this.time = time; 56 | } 57 | 58 | @Override 59 | public Date getCurrentDate() { 60 | return new Date(time); 61 | } 62 | 63 | @Override 64 | public long getCurrentTimeInMillis() { 65 | return time; 66 | } 67 | 68 | /** 69 | * 重新设置日期。 70 | */ 71 | public void update(Date newDate) { 72 | time = newDate.getTime(); 73 | } 74 | 75 | /** 76 | * 重新设置时间。 77 | */ 78 | public void update(long newTime) { 79 | this.time = newTime; 80 | } 81 | 82 | /** 83 | * 滚动时间. 84 | */ 85 | public void increaseTime(int millis) { 86 | time += millis; 87 | } 88 | 89 | /** 90 | * 滚动时间. 91 | */ 92 | public void decreaseTime(int millis) { 93 | time -= millis; 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/utils/ExceptionsTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import static org.assertj.core.api.Assertions.*; 9 | 10 | import java.io.IOException; 11 | 12 | import org.junit.Test; 13 | 14 | public class ExceptionsTest { 15 | 16 | @Test 17 | public void unchecked() { 18 | // convert Exception to RuntimeException with cause 19 | Exception exception = new Exception("my exception"); 20 | RuntimeException runtimeException = Exceptions.unchecked(exception); 21 | assertThat(runtimeException.getCause()).isEqualTo(exception); 22 | 23 | // do nothing of RuntimeException 24 | RuntimeException runtimeException2 = Exceptions.unchecked(runtimeException); 25 | assertThat(runtimeException2).isSameAs(runtimeException); 26 | } 27 | 28 | @Test 29 | public void getStackTraceAsString() { 30 | Exception exception = new Exception("my exception"); 31 | RuntimeException runtimeException = new RuntimeException(exception); 32 | 33 | String stack = Exceptions.getStackTraceAsString(runtimeException); 34 | System.out.println(stack); 35 | } 36 | 37 | @Test 38 | public void isCausedBy() { 39 | IOException ioexception = new IOException("my exception"); 40 | IllegalStateException illegalStateException = new IllegalStateException(ioexception); 41 | RuntimeException runtimeException = new RuntimeException(illegalStateException); 42 | 43 | assertThat(Exceptions.isCausedBy(runtimeException, IOException.class)).isTrue(); 44 | assertThat(Exceptions.isCausedBy(runtimeException, IllegalStateException.class, IOException.class)).isTrue(); 45 | assertThat(Exceptions.isCausedBy(runtimeException, Exception.class)).isTrue(); 46 | assertThat(Exceptions.isCausedBy(runtimeException, IllegalAccessException.class)).isFalse(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/Exceptions.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import java.io.PrintWriter; 9 | import java.io.StringWriter; 10 | 11 | /** 12 | * 关于异常的工具类. 13 | * 14 | * 参考了guava的Throwables。 15 | * 16 | * @author calvin 17 | */ 18 | public class Exceptions { 19 | 20 | /** 21 | * 将CheckedException转换为UncheckedException. 22 | */ 23 | public static RuntimeException unchecked(Throwable ex) { 24 | if (ex instanceof RuntimeException) { 25 | return (RuntimeException) ex; 26 | } else { 27 | return new RuntimeException(ex); 28 | } 29 | } 30 | 31 | /** 32 | * 将ErrorStack转化为String. 33 | */ 34 | public static String getStackTraceAsString(Throwable ex) { 35 | StringWriter stringWriter = new StringWriter(); 36 | ex.printStackTrace(new PrintWriter(stringWriter)); 37 | return stringWriter.toString(); 38 | } 39 | 40 | /** 41 | * 获取组合本异常信息与底层异常信息的异常描述, 适用于本异常为统一包装异常类,底层异常才是根本原因的情况。 42 | */ 43 | public static String getErrorMessageWithNestedException(Throwable ex) { 44 | Throwable nestedException = ex.getCause(); 45 | return new StringBuilder().append(ex.getMessage()).append(" nested exception is ") 46 | .append(nestedException.getClass().getName()).append(":").append(nestedException.getMessage()) 47 | .toString(); 48 | } 49 | 50 | /** 51 | * 获取异常的Root Cause. 52 | */ 53 | public static Throwable getRootCause(Throwable ex) { 54 | Throwable cause; 55 | while ((cause = ex.getCause()) != null) { 56 | ex = cause; 57 | } 58 | return ex; 59 | } 60 | 61 | /** 62 | * 判断异常是否由某些底层的异常引起. 63 | */ 64 | public static boolean isCausedBy(Exception ex, Class... causeExceptionClasses) { 65 | Throwable cause = ex; 66 | while (cause != null) { 67 | for (Class causeClass : causeExceptionClasses) { 68 | if (causeClass.isInstance(cause)) { 69 | return true; 70 | } 71 | } 72 | cause = cause.getCause(); 73 | } 74 | return false; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/api/AccountEndPoint.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.api; 2 | 3 | import java.util.Collections; 4 | import java.util.Map; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.util.StringUtils; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | import org.springframework.web.bind.annotation.RestController; 13 | import org.springside.examples.bootapi.service.AccountService; 14 | import org.springside.examples.bootapi.service.exception.ErrorCode; 15 | import org.springside.examples.bootapi.service.exception.ServiceException; 16 | import org.springside.modules.constants.MediaTypes; 17 | 18 | // Spring Restful MVC Controller的标识, 直接输出内容,不调用template引擎. 19 | @RestController 20 | public class AccountEndPoint { 21 | 22 | private static Logger logger = LoggerFactory.getLogger(AccountEndPoint.class); 23 | 24 | @Autowired 25 | private AccountService accountServcie; 26 | 27 | @RequestMapping(value = "/api/accounts/login", produces = MediaTypes.JSON_UTF_8) 28 | public Map login(@RequestParam("email") String email, @RequestParam("password") String password) { 29 | 30 | if (StringUtils.isEmpty(email) || StringUtils.isEmpty(password)) { 31 | throw new ServiceException("User or password empty", ErrorCode.BAD_REQUEST); 32 | } 33 | 34 | String token = accountServcie.login(email, password); 35 | 36 | return Collections.singletonMap("token", token); 37 | } 38 | 39 | @RequestMapping(value = "/api/accounts/logout") 40 | public void logout(@RequestParam(value = "token", required = false) String token) { 41 | accountServcie.logout(token); 42 | } 43 | 44 | @RequestMapping(value = "/api/accounts/register") 45 | public void register(@RequestParam("email") String email, 46 | @RequestParam(value = "name", required = false) String name, @RequestParam("password") String password) { 47 | 48 | if (StringUtils.isEmpty(email) || StringUtils.isEmpty(name) || StringUtils.isEmpty(password)) { 49 | throw new ServiceException("User or name or password empty", ErrorCode.BAD_REQUEST); 50 | } 51 | 52 | accountServcie.register(email, name, password); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/utils/CryptosTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import static org.assertj.core.api.Assertions.*; 9 | 10 | import org.junit.Test; 11 | import org.springside.modules.utils.Cryptos; 12 | import org.springside.modules.utils.Encodes; 13 | 14 | public class CryptosTest { 15 | @Test 16 | public void mac() { 17 | String input = "foo message"; 18 | 19 | // key可为任意字符串 20 | // byte[] key = "a foo key".getBytes(); 21 | byte[] key = Cryptos.generateHmacSha1Key(); 22 | assertThat(key).hasSize(20); 23 | 24 | byte[] macResult = Cryptos.hmacSha1(input.getBytes(), key); 25 | System.out.println("hmac-sha1 key in hex :" + Encodes.encodeHex(key)); 26 | System.out.println("hmac-sha1 in hex result :" + Encodes.encodeHex(macResult)); 27 | 28 | assertThat(Cryptos.isMacValid(macResult, input.getBytes(), key)).isTrue(); 29 | } 30 | 31 | @Test 32 | public void aes() { 33 | byte[] key = Cryptos.generateAesKey(); 34 | assertThat(key).hasSize(16); 35 | String input = "foo message"; 36 | 37 | byte[] encryptResult = Cryptos.aesEncrypt(input.getBytes(), key); 38 | String descryptResult = Cryptos.aesDecrypt(encryptResult, key); 39 | 40 | System.out.println("aes key in hex :" + Encodes.encodeHex(key)); 41 | System.out.println("aes encrypt in hex result :" + Encodes.encodeHex(encryptResult)); 42 | assertThat(descryptResult).isEqualTo(input); 43 | } 44 | 45 | @Test 46 | public void aesWithIV() { 47 | byte[] key = Cryptos.generateAesKey(); 48 | byte[] iv = Cryptos.generateIV(); 49 | assertThat(key).hasSize(16); 50 | assertThat(iv).hasSize(16); 51 | String input = "foo message"; 52 | 53 | byte[] encryptResult = Cryptos.aesEncrypt(input.getBytes(), key, iv); 54 | String descryptResult = Cryptos.aesDecrypt(encryptResult, key, iv); 55 | 56 | System.out.println("aes key in hex :" + Encodes.encodeHex(key)); 57 | System.out.println("iv in hex :" + Encodes.encodeHex(iv)); 58 | System.out.println("aes encrypt in hex result :" + Encodes.encodeHex(encryptResult)); 59 | assertThat(descryptResult).isEqualTo(input); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/ConcurrentHashSet.java: -------------------------------------------------------------------------------- 1 | package org.springside.modules.utils; 2 | 3 | import java.util.AbstractSet; 4 | import java.util.Collection; 5 | import java.util.Iterator; 6 | import java.util.Map; 7 | import java.util.Set; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | * JDK并没有提供ConcurrenHashSet,考虑到JDK的HashSet也是基于HashMap实现的,因此ConcurrenHashSet也由ConcurrenHashMap完成。 12 | * 13 | * 实现参考了Jetty的实现. 14 | */ 15 | public class ConcurrentHashSet extends AbstractSet implements Set { 16 | private final Map map; 17 | private transient Set keys; 18 | 19 | public ConcurrentHashSet() { 20 | map = new ConcurrentHashMap(); 21 | keys = map.keySet(); 22 | } 23 | 24 | public ConcurrentHashSet(int initialCapacity) { 25 | map = new ConcurrentHashMap(initialCapacity); 26 | keys = map.keySet(); 27 | } 28 | 29 | @Override 30 | public boolean add(E e) { 31 | return map.put(e, Boolean.TRUE) == null; 32 | } 33 | 34 | @Override 35 | public boolean remove(Object o) { 36 | return map.remove(o) != null; 37 | } 38 | 39 | @Override 40 | public boolean contains(Object o) { 41 | return map.containsKey(o); 42 | } 43 | 44 | @Override 45 | public boolean isEmpty() { 46 | return map.isEmpty(); 47 | } 48 | 49 | @Override 50 | public int size() { 51 | return map.size(); 52 | } 53 | 54 | @Override 55 | public void clear() { 56 | map.clear(); 57 | } 58 | 59 | @Override 60 | public Iterator iterator() { 61 | return keys.iterator(); 62 | } 63 | 64 | @Override 65 | public boolean containsAll(Collection c) { 66 | return keys.containsAll(c); 67 | } 68 | 69 | @Override 70 | public boolean removeAll(Collection c) { 71 | return keys.removeAll(c); 72 | } 73 | 74 | @Override 75 | public boolean retainAll(Collection c) { 76 | return keys.retainAll(c); 77 | } 78 | 79 | @Override 80 | public Object[] toArray() { 81 | return keys.toArray(); 82 | } 83 | 84 | @Override 85 | public T[] toArray(T[] a) { 86 | return keys.toArray(a); 87 | } 88 | 89 | @Override 90 | public boolean equals(Object o) { 91 | return o == this || keys.equals(o); 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | return keys.hashCode(); 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return keys.toString(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorPageController.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.api.support; 2 | 3 | import java.util.Map; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes; 11 | import org.springframework.boot.autoconfigure.web.ErrorAttributes; 12 | import org.springframework.boot.autoconfigure.web.ErrorController; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.ResponseBody; 16 | import org.springframework.web.context.request.RequestAttributes; 17 | import org.springframework.web.context.request.ServletRequestAttributes; 18 | import org.springside.modules.constants.MediaTypes; 19 | import org.springside.modules.mapper.JsonMapper; 20 | 21 | /** 22 | * 重载替换Spring Boot默认的BasicErrorController, 增加日志并让错误返回方式统一. 23 | * 24 | * @author calvin 25 | */ 26 | @Controller 27 | public class ErrorPageController implements ErrorController { 28 | 29 | private static Logger logger = LoggerFactory.getLogger(ErrorPageController.class); 30 | 31 | @Value("${error.path:/error}") 32 | private String errorPath; 33 | 34 | private JsonMapper jsonMapper = new JsonMapper(); 35 | 36 | private ErrorAttributes errorAttributes = new DefaultErrorAttributes(); 37 | 38 | @RequestMapping(value = "${error.path:/error}", produces = MediaTypes.JSON_UTF_8) 39 | @ResponseBody 40 | public ErrorResult handle(HttpServletRequest request) { 41 | Map attributes = getErrorAttributes(request); 42 | 43 | ErrorResult result = new ErrorResult(); 44 | result.code = (int) attributes.get("status"); 45 | result.message = (String) attributes.get("error"); 46 | 47 | logError(attributes, request); 48 | 49 | return result; 50 | } 51 | 52 | private Map getErrorAttributes(HttpServletRequest request) { 53 | RequestAttributes requestAttributes = new ServletRequestAttributes(request); 54 | return this.errorAttributes.getErrorAttributes(requestAttributes, false); 55 | } 56 | 57 | private void logError(Map attributes, HttpServletRequest request) { 58 | attributes.put("from", request.getRemoteAddr()); 59 | logger.error(jsonMapper.toJson(attributes)); 60 | } 61 | 62 | @Override 63 | public String getErrorPath() { 64 | return this.errorPath; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Spring Boot Web Service示例 5 | 6 | 7 | 8 |

    1. 顺序访问P2P图书馆 API:

    9 | 10 | step1: 11 | 15 | 16 | step2: 17 | 18 | 23 | 24 | step3: 25 | 26 | 31 | 32 | step4: 33 | 34 | 38 | 39 | other flow: 40 | 41 | 45 | 46 | 47 |

    2. 访问下列应用管理Endpoint:

    48 | 56 | 57 | 58 |

    3. JMX通过Restful的jolokia访问:

    59 | 62 | 63 | 64 |

    4. 在开发Profile下的H2 Console,用于查看内存中数据库的内容:

    65 | 68 | 69 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/utils/DigestsTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | 11 | import org.junit.Test; 12 | 13 | public class DigestsTest { 14 | 15 | @Test 16 | public void digestString() { 17 | String input = "user"; 18 | byte[] sha1Result = Digests.sha1(input.getBytes()); 19 | System.out.println("sha1 in hex result :" + Encodes.encodeHex(sha1Result)); 20 | 21 | byte[] salt = Digests.generateSalt(8); 22 | System.out.println("salt in hex :" + Encodes.encodeHex(salt)); 23 | sha1Result = Digests.sha1(input.getBytes(), salt); 24 | System.out.println("sha1 in hex result with salt :" + Encodes.encodeHex(sha1Result)); 25 | 26 | sha1Result = Digests.sha1(input.getBytes(), salt, 1024); 27 | System.out.println("sha1 in hex result with salt and 1024 interations:" + Encodes.encodeHex(sha1Result)); 28 | 29 | } 30 | 31 | @Test 32 | public void digestFile() throws IOException { 33 | 34 | InputStream is = this.getClass().getClassLoader().getResourceAsStream("test.txt"); 35 | byte[] md5result = Digests.md5(is); 36 | byte[] sha1result = Digests.sha1(is); 37 | System.out.println("md5: " + Encodes.encodeHex(md5result)); 38 | System.out.println("sha1:" + Encodes.encodeHex(sha1result)); 39 | } 40 | 41 | @Test 42 | public void crc32String() { 43 | 44 | String input = "user1"; 45 | int result = Digests.crc32(input); 46 | System.out.println("crc32 for user1:" + result); 47 | 48 | input = "user2"; 49 | result = Digests.crc32(input); 50 | System.out.println("crc32 for user2:" + result); 51 | } 52 | 53 | @Test 54 | public void murmurString() { 55 | 56 | String input1 = "user1"; 57 | int result = Digests.murmur32(input1); 58 | System.out.println("murmur32 for user1:" + result); 59 | 60 | String input2 = "user2"; 61 | result = Digests.murmur32(input2); 62 | System.out.println("murmur32 for user2:" + result); 63 | 64 | int seed = (int) System.currentTimeMillis(); 65 | result = Digests.murmur32(input1, seed); 66 | System.out.println("murmur32 with seed for user1:" + result); 67 | 68 | result = Digests.murmur32(input2, seed); 69 | System.out.println("murmur32 with seed for user2:" + result); 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/test/log/LogbackListAppenderTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.test.log; 7 | 8 | import static org.assertj.core.api.Assertions.*; 9 | 10 | import org.junit.Test; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | public class LogbackListAppenderTest { 15 | 16 | @Test 17 | public void normal() { 18 | String testString1 = "Hello"; 19 | String testString2 = "World"; 20 | LogbackListAppender appender = new LogbackListAppender(); 21 | appender.addToLogger(LogbackListAppenderTest.class); 22 | 23 | // null 24 | assertThat(appender.getFirstLog()).isNull(); 25 | assertThat(appender.getLastLog()).isNull(); 26 | assertThat(appender.getFirstMessage()).isNull(); 27 | assertThat(appender.getFirstMessage()).isNull(); 28 | 29 | Logger logger = LoggerFactory.getLogger(LogbackListAppenderTest.class); 30 | logger.warn(testString1); 31 | logger.warn(testString2); 32 | 33 | // getFirstLog/getLastLog 34 | assertThat(appender.getFirstLog().getMessage()).isEqualTo(testString1); 35 | assertThat(appender.getLastLog().getMessage()).isEqualTo(testString2); 36 | 37 | assertThat(appender.getFirstMessage()).isEqualTo(testString1); 38 | assertThat(appender.getLastMessage()).isEqualTo(testString2); 39 | 40 | // getAllLogs 41 | assertThat(appender.getLogsCount()).isEqualTo(2); 42 | assertThat(appender.getAllLogs()).hasSize(2); 43 | assertThat(appender.getAllLogs().get(1).getMessage()).isEqualTo(testString2); 44 | 45 | // clearLogs 46 | appender.clearLogs(); 47 | assertThat(appender.getFirstLog()).isNull(); 48 | assertThat(appender.getLastLog()).isNull(); 49 | } 50 | 51 | @Test 52 | public void addAndRemoveAppender() { 53 | String testString = "Hello"; 54 | Logger logger = LoggerFactory.getLogger(LogbackListAppenderTest.class); 55 | LogbackListAppender appender = new LogbackListAppender(); 56 | // class 57 | appender.addToLogger(LogbackListAppenderTest.class); 58 | logger.warn(testString); 59 | assertThat(appender.getFirstLog()).isNotNull(); 60 | 61 | appender.clearLogs(); 62 | appender.removeFromLogger(LogbackListAppenderTest.class); 63 | logger.warn(testString); 64 | assertThat(appender.getFirstLog()).isNull(); 65 | 66 | // name 67 | appender.clearLogs(); 68 | appender.addToLogger("org.springside.modules.test.log"); 69 | logger.warn(testString); 70 | assertThat(appender.getFirstLog()).isNotNull(); 71 | 72 | appender.clearLogs(); 73 | appender.removeFromLogger("org.springside.modules.test.log"); 74 | logger.warn(testString); 75 | assertThat(appender.getFirstLog()).isNull(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /examples/boot-api/src/test/java/org/springside/examples/bootapi/service/BookBorrowServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.service; 2 | 3 | import static org.assertj.core.api.Assertions.*; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.Mockito; 8 | import org.springside.examples.bootapi.domain.Account; 9 | import org.springside.examples.bootapi.domain.Book; 10 | import org.springside.examples.bootapi.domain.Message; 11 | import org.springside.examples.bootapi.repository.BookDao; 12 | import org.springside.examples.bootapi.repository.MessageDao; 13 | import org.springside.modules.test.log.LogbackListAppender; 14 | 15 | public class BookBorrowServiceTest { 16 | 17 | private BookBorrowService service; 18 | 19 | private BookDao mockBookDao; 20 | 21 | private MessageDao mockMessageDao; 22 | 23 | private LogbackListAppender appender; 24 | 25 | @Before 26 | public void setup() { 27 | service = new BookBorrowService(); 28 | mockBookDao = Mockito.mock(BookDao.class); 29 | mockMessageDao = Mockito.mock(MessageDao.class); 30 | service.bookDao = mockBookDao; 31 | service.messageDao = mockMessageDao; 32 | 33 | appender = new LogbackListAppender(); 34 | appender.addToLogger(BookBorrowService.class); 35 | } 36 | 37 | public void tearDown() { 38 | appender.removeFromLogger(BookBorrowService.class); 39 | } 40 | 41 | @Test 42 | public void applyBorrowRequest() { 43 | 44 | Book book = new Book(1L); 45 | book.status = Book.STATUS_IDLE; 46 | book.owner = new Account(1L); 47 | 48 | Mockito.when(mockBookDao.findOne(1L)).thenReturn(book); 49 | 50 | service.applyBorrowRequest(1L, new Account(3L)); 51 | 52 | Mockito.verify(mockBookDao).save(Mockito.any(Book.class)); 53 | Mockito.verify(mockMessageDao).save(Mockito.any(Message.class)); 54 | } 55 | 56 | @Test 57 | public void applyBorrowRequestWithError() { 58 | 59 | // 自己借自己的书 60 | Book book = new Book(1L); 61 | book.status = Book.STATUS_IDLE; 62 | book.owner = new Account(1L); 63 | 64 | Mockito.when(mockBookDao.findOne(1L)).thenReturn(book); 65 | try { 66 | service.applyBorrowRequest(1L, new Account(1L)); 67 | fail("should fail here"); 68 | } catch (Exception e) { 69 | assertThat(e).hasMessageContaining("User shouldn't borrower the book which is himeself"); 70 | assertThat(appender.getLastMessage()).contains("user id:1,book id:1"); 71 | } 72 | // 保证BookDao没被调用 73 | Mockito.verify(mockBookDao, Mockito.never()).save(Mockito.any(Book.class)); 74 | 75 | // 借已借出的书 76 | book.status = Book.STATUS_REQUEST; 77 | 78 | try { 79 | service.applyBorrowRequest(1L, new Account(3L)); 80 | fail("should fail here"); 81 | } catch (Exception e) { 82 | assertThat(e).hasMessageContaining("The book is not idle"); 83 | assertThat(appender.getLastMessage()).contains("user id:3,book id:1,status:request"); 84 | } 85 | Mockito.verify(mockBookDao, Mockito.never()).save(Mockito.any(Book.class)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/service/BookAdminService.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.service; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | import org.springside.examples.bootapi.domain.Account; 12 | import org.springside.examples.bootapi.domain.Book; 13 | import org.springside.examples.bootapi.repository.BookDao; 14 | import org.springside.examples.bootapi.service.exception.ErrorCode; 15 | import org.springside.examples.bootapi.service.exception.ServiceException; 16 | import org.springside.modules.utils.Clock; 17 | 18 | // Spring Bean的标识. 19 | @Service 20 | public class BookAdminService { 21 | 22 | private static Logger logger = LoggerFactory.getLogger(BookBorrowService.class); 23 | 24 | @Autowired 25 | private BookDao bookDao; 26 | 27 | // 可注入的Clock,方便测试时控制日期 28 | protected Clock clock = Clock.DEFAULT; 29 | 30 | @Transactional(readOnly = true) 31 | public Iterable findAll(Pageable pageable) { 32 | return bookDao.findAll(pageable); 33 | } 34 | 35 | @Transactional(readOnly = true) 36 | public Book findOne(Long id) { 37 | return bookDao.findOne(id); 38 | } 39 | 40 | @Transactional(readOnly = true) 41 | public List listMyBook(Long ownerId, Pageable pageable) { 42 | return bookDao.findByOwnerId(ownerId, pageable); 43 | } 44 | 45 | @Transactional 46 | public void saveBook(Book book, Account owner) { 47 | 48 | book.owner = owner; 49 | book.status = Book.STATUS_IDLE; 50 | book.onboardDate = clock.getCurrentDate(); 51 | 52 | bookDao.save(book); 53 | } 54 | 55 | @Transactional 56 | public void modifyBook(Book book, Long currentAccountId) { 57 | if (!currentAccountId.equals(book.owner.id)) { 58 | logger.error("user:" + currentAccountId + " try to modified a book:" + book.id + " which is not him"); 59 | throw new ServiceException("User can't modify others book", ErrorCode.BOOK_OWNERSHIP_WRONG); 60 | } 61 | 62 | Book orginalBook = bookDao.findOne(book.id); 63 | 64 | if (orginalBook == null) { 65 | logger.error("user:" + currentAccountId + " try to modified a book:" + book.id + " which is not exist"); 66 | throw new ServiceException("The Book is not exist", ErrorCode.BAD_REQUEST); 67 | } 68 | 69 | orginalBook.title = book.title; 70 | orginalBook.url = book.url; 71 | bookDao.save(orginalBook); 72 | } 73 | 74 | @Transactional 75 | public void deleteBook(Long id, Long currentAccountId) { 76 | Book book = bookDao.findOne(id); 77 | 78 | if (book == null) { 79 | logger.error("user:" + currentAccountId + " try to delete a book:" + id + " which is not exist"); 80 | throw new ServiceException("The Book is not exist", ErrorCode.BAD_REQUEST); 81 | } 82 | 83 | if (!currentAccountId.equals(book.owner.id)) { 84 | logger.error("user:" + currentAccountId + " try to delete a book:" + book.id + " which is not him"); 85 | throw new ServiceException("User can't delete others book", ErrorCode.BOOK_OWNERSHIP_WRONG); 86 | } 87 | 88 | bookDao.delete(id); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/utils/ThreadsTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import static org.assertj.core.api.Assertions.*; 9 | 10 | import java.util.concurrent.CountDownLatch; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | import org.junit.Test; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import org.springside.modules.test.log.LogbackListAppender; 19 | 20 | public class ThreadsTest { 21 | 22 | @Test 23 | public void gracefulShutdown() throws InterruptedException { 24 | 25 | Logger logger = LoggerFactory.getLogger("test"); 26 | LogbackListAppender appender = new LogbackListAppender(); 27 | appender.addToLogger("test"); 28 | 29 | // time enough to shutdown 30 | ExecutorService pool = Executors.newSingleThreadExecutor(); 31 | Runnable task = new Task(logger, 500, 0); 32 | pool.execute(task); 33 | Threads.gracefulShutdown(pool, 1000, TimeUnit.MILLISECONDS); 34 | assertThat(pool.isTerminated()).isTrue(); 35 | assertThat(appender.getFirstLog()).isNull(); 36 | 37 | // time not enough to shutdown,call shutdownNow 38 | appender.clearLogs(); 39 | pool = Executors.newSingleThreadExecutor(); 40 | task = new Task(logger, 1000, 0); 41 | pool.execute(task); 42 | Threads.gracefulShutdown(pool, 500, TimeUnit.MILLISECONDS); 43 | assertThat(pool.isTerminated()).isTrue(); 44 | assertThat(appender.getFirstLog().getMessage()).isEqualTo("InterruptedException"); 45 | 46 | // self thread interrupt while calling gracefulShutdown 47 | appender.clearLogs(); 48 | 49 | final ExecutorService self = Executors.newSingleThreadExecutor(); 50 | task = new Task(logger, 100000, 0); 51 | self.execute(task); 52 | 53 | final CountDownLatch lock = new CountDownLatch(1); 54 | Thread thread = new Thread(new Runnable() { 55 | 56 | @Override 57 | public void run() { 58 | lock.countDown(); 59 | Threads.gracefulShutdown(self, 200000, TimeUnit.MILLISECONDS); 60 | } 61 | }); 62 | thread.start(); 63 | lock.await(); 64 | thread.interrupt(); 65 | Threads.sleep(500); 66 | assertThat(appender.getFirstLog().getMessage()).isEqualTo("InterruptedException"); 67 | } 68 | 69 | static class Task implements Runnable { 70 | private final Logger logger; 71 | 72 | private int runTime = 0; 73 | 74 | private final int sleepTime; 75 | 76 | Task(Logger logger, int sleepTime, int runTime) { 77 | this.logger = logger; 78 | this.sleepTime = sleepTime; 79 | this.runTime = runTime; 80 | } 81 | 82 | @Override 83 | public void run() { 84 | System.out.println("start task"); 85 | if (runTime > 0) { 86 | long start = System.currentTimeMillis(); 87 | while ((System.currentTimeMillis() - start) < runTime) { 88 | } 89 | } 90 | 91 | try { 92 | Thread.sleep(sleepTime); 93 | } catch (InterruptedException e) { 94 | logger.warn("InterruptedException"); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/Encodes.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import java.io.UnsupportedEncodingException; 9 | import java.net.URLDecoder; 10 | import java.net.URLEncoder; 11 | 12 | import org.apache.commons.codec.DecoderException; 13 | import org.apache.commons.codec.binary.Base64; 14 | import org.apache.commons.codec.binary.Hex; 15 | import org.apache.commons.lang3.StringEscapeUtils; 16 | 17 | /** 18 | * 封装各种格式的编码解码工具类. 19 | * 20 | * 1.Commons-Codec的 hex/base64 编码 21 | * 2.自制的base62 编码 22 | * 3.Commons-Lang的xml/html escape 23 | * 4.JDK提供的URLEncoder 24 | * 25 | * @author calvin 26 | */ 27 | public class Encodes { 28 | 29 | private static final String DEFAULT_URL_ENCODING = "UTF-8"; 30 | private static final char[] BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(); 31 | 32 | /** 33 | * Hex编码. 34 | */ 35 | public static String encodeHex(byte[] input) { 36 | return Hex.encodeHexString(input); 37 | } 38 | 39 | /** 40 | * Hex解码. 41 | */ 42 | public static byte[] decodeHex(String input) { 43 | try { 44 | return Hex.decodeHex(input.toCharArray()); 45 | } catch (DecoderException e) { 46 | throw Exceptions.unchecked(e); 47 | } 48 | } 49 | 50 | /** 51 | * Base64编码. 52 | */ 53 | public static String encodeBase64(byte[] input) { 54 | return Base64.encodeBase64String(input); 55 | } 56 | 57 | /** 58 | * Base64编码, URL安全(将Base64中的URL非法字符'+'和'/'转为'-'和'_', 见RFC3548). 59 | */ 60 | public static String encodeUrlSafeBase64(byte[] input) { 61 | return Base64.encodeBase64URLSafeString(input); 62 | } 63 | 64 | /** 65 | * Base64解码. 66 | */ 67 | public static byte[] decodeBase64(String input) { 68 | return Base64.decodeBase64(input); 69 | } 70 | 71 | /** 72 | * Base62编码。 73 | */ 74 | public static String encodeBase62(byte[] input) { 75 | char[] chars = new char[input.length]; 76 | for (int i = 0; i < input.length; i++) { 77 | chars[i] = BASE62[(input[i] & 0xFF) % BASE62.length]; 78 | } 79 | return new String(chars); 80 | } 81 | 82 | /** 83 | * Html 转码. 84 | */ 85 | public static String escapeHtml(String html) { 86 | return StringEscapeUtils.escapeHtml4(html); 87 | } 88 | 89 | /** 90 | * Html 解码. 91 | */ 92 | public static String unescapeHtml(String htmlEscaped) { 93 | return StringEscapeUtils.unescapeHtml4(htmlEscaped); 94 | } 95 | 96 | /** 97 | * Xml 转码. 98 | */ 99 | public static String escapeXml(String xml) { 100 | return StringEscapeUtils.escapeXml(xml); 101 | } 102 | 103 | /** 104 | * Xml 解码. 105 | */ 106 | public static String unescapeXml(String xmlEscaped) { 107 | return StringEscapeUtils.unescapeXml(xmlEscaped); 108 | } 109 | 110 | /** 111 | * URL 编码, Encode默认为UTF-8. 112 | */ 113 | public static String urlEncode(String part) { 114 | try { 115 | return URLEncoder.encode(part, DEFAULT_URL_ENCODING); 116 | } catch (UnsupportedEncodingException e) { 117 | throw Exceptions.unchecked(e); 118 | } 119 | } 120 | 121 | /** 122 | * URL 解码, Encode默认为UTF-8. 123 | */ 124 | public static String urlDecode(String part) { 125 | 126 | try { 127 | return URLDecoder.decode(part, DEFAULT_URL_ENCODING); 128 | } catch (UnsupportedEncodingException e) { 129 | throw Exceptions.unchecked(e); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/CustomExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.api.support; 2 | 3 | import java.util.Map; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.web.bind.annotation.ControllerAdvice; 14 | import org.springframework.web.bind.annotation.ExceptionHandler; 15 | import org.springframework.web.bind.annotation.RestController; 16 | import org.springframework.web.context.request.WebRequest; 17 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 18 | import org.springside.examples.bootapi.service.exception.ServiceException; 19 | import org.springside.modules.constants.MediaTypes; 20 | import org.springside.modules.mapper.JsonMapper; 21 | 22 | import com.google.common.collect.Maps; 23 | 24 | @ControllerAdvice(annotations = { RestController.class }) 25 | public class CustomExceptionHandler extends ResponseEntityExceptionHandler { 26 | 27 | private Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class); 28 | 29 | private JsonMapper jsonMapper = new JsonMapper(); 30 | 31 | @ExceptionHandler(value = { ServiceException.class }) 32 | public final ResponseEntity handleServiceException(ServiceException ex, HttpServletRequest request) { 33 | // 注入servletRequest,用于出错时打印请求URL与来源地址 34 | logError(ex, request); 35 | 36 | HttpHeaders headers = new HttpHeaders(); 37 | headers.setContentType(MediaType.parseMediaType(MediaTypes.JSON_UTF_8)); 38 | ErrorResult result = new ErrorResult(ex.errorCode.code, ex.getMessage()); 39 | return new ResponseEntity(result, headers, HttpStatus.valueOf(ex.errorCode.httpStatus)); 40 | } 41 | 42 | @ExceptionHandler(value = { Exception.class }) 43 | public final ResponseEntity handleGeneralException(Exception ex, HttpServletRequest request) { 44 | logError(ex, request); 45 | 46 | HttpHeaders headers = new HttpHeaders(); 47 | headers.setContentType(MediaType.parseMediaType(MediaTypes.JSON_UTF_8)); 48 | ErrorResult result = new ErrorResult(HttpStatus.INTERNAL_SERVER_ERROR.value(), 49 | HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()); 50 | return new ResponseEntity(result, headers, HttpStatus.INTERNAL_SERVER_ERROR); 51 | } 52 | 53 | /** 54 | * 重载ResponseEntityExceptionHandler的方法,加入日志 55 | */ 56 | @Override 57 | protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, 58 | HttpStatus status, WebRequest request) { 59 | 60 | logError(ex); 61 | 62 | if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) { 63 | request.setAttribute("javax.servlet.error.exception", ex, WebRequest.SCOPE_REQUEST); 64 | } 65 | 66 | return new ResponseEntity(body, headers, status); 67 | } 68 | 69 | public void logError(Exception ex) { 70 | Map map = Maps.newHashMap(); 71 | map.put("message", ex.getMessage()); 72 | logger.error(jsonMapper.toJson(map), ex); 73 | } 74 | 75 | public void logError(Exception ex, HttpServletRequest request) { 76 | Map map = Maps.newHashMap(); 77 | map.put("message", ex.getMessage()); 78 | map.put("from", request.getRemoteAddr()); 79 | String queryString = request.getQueryString(); 80 | map.put("path", queryString != null ? (request.getRequestURI() + "?" + queryString) : request.getRequestURI()); 81 | 82 | logger.error(jsonMapper.toJson(map), ex); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/service/AccountService.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.service; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import javax.annotation.PostConstruct; 6 | 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.actuate.metrics.CounterService; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.annotation.Transactional; 15 | import org.springside.examples.bootapi.domain.Account; 16 | import org.springside.examples.bootapi.repository.AccountDao; 17 | import org.springside.examples.bootapi.service.exception.ErrorCode; 18 | import org.springside.examples.bootapi.service.exception.ServiceException; 19 | import org.springside.modules.utils.Digests; 20 | import org.springside.modules.utils.Encodes; 21 | import org.springside.modules.utils.Ids; 22 | 23 | import com.google.common.cache.Cache; 24 | import com.google.common.cache.CacheBuilder; 25 | 26 | // Spring Bean的标识. 27 | @Service 28 | public class AccountService { 29 | 30 | private static Logger logger = LoggerFactory.getLogger(AccountService.class); 31 | 32 | @Autowired 33 | private AccountDao accountDao; 34 | 35 | // 注入配置值 36 | @Value("${app.loginTimeoutSecs:600}") 37 | private int loginTimeoutSecs; 38 | 39 | // codehale metrics 40 | @Autowired 41 | private CounterService counterService; 42 | 43 | // guava cache 44 | private Cache loginUsers; 45 | 46 | @PostConstruct 47 | public void init() { 48 | loginUsers = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(loginTimeoutSecs, TimeUnit.SECONDS) 49 | .build(); 50 | } 51 | 52 | @Transactional(readOnly = true) 53 | public String login(String email, String password) { 54 | Account account = accountDao.findByEmail(email); 55 | 56 | if (account == null) { 57 | throw new ServiceException("User not exist", ErrorCode.UNAUTHORIZED); 58 | } 59 | 60 | if (!account.hashPassword.equals(hashPassword(password))) { 61 | throw new ServiceException("Password wrong", ErrorCode.UNAUTHORIZED); 62 | } 63 | 64 | String token = Ids.uuid2(); 65 | loginUsers.put(token, account); 66 | counterService.increment("loginUser"); 67 | return token; 68 | } 69 | 70 | public void logout(String token) { 71 | Account account = loginUsers.getIfPresent(token); 72 | if (account == null) { 73 | logger.warn("logout an alreay logout token:" + token); 74 | } else { 75 | loginUsers.invalidate(token); 76 | counterService.decrement("loginUser"); 77 | } 78 | } 79 | 80 | public Account getLoginUser(String token) { 81 | 82 | Account account = loginUsers.getIfPresent(token); 83 | 84 | if (account == null) { 85 | throw new ServiceException("User doesn't login", ErrorCode.UNAUTHORIZED); 86 | } 87 | 88 | return account; 89 | } 90 | 91 | @Transactional 92 | public void register(String email, String name, String password) { 93 | 94 | if (StringUtils.isBlank(email) || StringUtils.isBlank(password)) { 95 | throw new ServiceException("Invalid parameter", ErrorCode.BAD_REQUEST); 96 | } 97 | 98 | Account account = new Account(); 99 | account.email = email; 100 | account.name = name; 101 | account.hashPassword = hashPassword(password); 102 | accountDao.save(account); 103 | } 104 | 105 | protected static String hashPassword(String password) { 106 | return Encodes.encodeBase64(Digests.sha1(password)); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/Threads.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.ThreadFactory; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import com.google.common.util.concurrent.MoreExecutors; 16 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 17 | 18 | /** 19 | * 线程相关工具类. 20 | * 21 | * @author calvin 22 | */ 23 | public class Threads { 24 | 25 | /** 26 | * sleep等待, 单位为毫秒, 已捕捉并处理InterruptedException. 27 | */ 28 | public static void sleep(long durationMillis) { 29 | try { 30 | Thread.sleep(durationMillis); 31 | } catch (InterruptedException e) { 32 | Thread.currentThread().interrupt(); 33 | } 34 | } 35 | 36 | /** 37 | * sleep等待,已捕捉并处理InterruptedException. 38 | */ 39 | public static void sleep(long duration, TimeUnit unit) { 40 | try { 41 | Thread.sleep(unit.toMillis(duration)); 42 | } catch (InterruptedException e) { 43 | Thread.currentThread().interrupt(); 44 | } 45 | } 46 | 47 | /** 48 | * 按照ExecutorService JavaDoc示例代码编写的Graceful Shutdown方法. 49 | * 50 | * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. 51 | * 52 | * 如果1/2超时时间后, 则调用shutdownNow,取消在workQueue中Pending的任务,并中断所有阻塞函数. 53 | * 54 | * 如果1/2超时仍然超時,則強制退出. 55 | * 56 | * 另对在shutdown时线程本身被调用中断做了处理. 57 | * 58 | * 返回线程最后是否被中断. 59 | */ 60 | public static boolean gracefulShutdown(ExecutorService threadPool, int shutdownTimeoutMills) { 61 | return MoreExecutors.shutdownAndAwaitTermination(threadPool, shutdownTimeoutMills, TimeUnit.MILLISECONDS); 62 | } 63 | 64 | /** 65 | * @see #gracefulShutdown(ExecutorService, int) 66 | */ 67 | public static boolean gracefulShutdown(ExecutorService threadPool, int shutdownTimeout, TimeUnit timeUnit) { 68 | return MoreExecutors.shutdownAndAwaitTermination(threadPool, shutdownTimeout, timeUnit); 69 | } 70 | 71 | /** 72 | * 创建ThreadFactory,使得创建的线程有自己的名字而不是默认的"pool-x-thread-y" 73 | * 74 | * 格式如"mythread-%d",使用了Guava的工具类 75 | */ 76 | public static ThreadFactory buildThreadFactory(String nameFormat) { 77 | return new ThreadFactoryBuilder().setNameFormat(nameFormat).build(); 78 | } 79 | 80 | /** 81 | * 可设定是否daemon, daemon线程在主线程已执行完毕时, 不会阻塞应用不退出, 而非daemon线程则会阻塞. 82 | * 83 | * @see #buildThreadFactory(String) 84 | */ 85 | public static ThreadFactory buildThreadFactory(String nameFormat, boolean daemon) { 86 | return new ThreadFactoryBuilder().setNameFormat(nameFormat).setDaemon(daemon).build(); 87 | } 88 | 89 | /** 90 | * 保证不会有Exception抛出到线程池的Runnable包裹类,防止用户没有捕捉异常导致中断了线程池中的线程, 使得SchedulerService无法执行. 91 | */ 92 | public static class WrapExceptionRunnable implements Runnable { 93 | 94 | private static Logger logger = LoggerFactory.getLogger(WrapExceptionRunnable.class); 95 | 96 | private Runnable runnable; 97 | 98 | public WrapExceptionRunnable(Runnable runnable) { 99 | this.runnable = runnable; 100 | } 101 | 102 | @Override 103 | public void run() { 104 | try { 105 | runnable.run(); 106 | } catch (Throwable e) { 107 | // catch any exception, because the scheduled thread will break if the exception thrown to outside. 108 | logger.error("Unexpected error occurred in task", e); 109 | } 110 | } 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /modules/utils/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.springside 7 | springside-modules 8 | 5.0.0-SNAPSHOT 9 | ../ 10 | 11 | springside-utils 12 | jar 13 | Springside :: Module :: Utils 14 | 15 | 16 | 17 | 18 | com.google.guava 19 | guava 20 | ${guava.version} 21 | true 22 | 23 | 24 | org.apache.commons 25 | commons-lang3 26 | ${commons-lang3.version} 27 | true 28 | 29 | 30 | commons-codec 31 | commons-codec 32 | ${commons-codec.version} 33 | true 34 | 35 | 36 | commons-beanutils 37 | commons-beanutils 38 | ${commons-beanutils.version} 39 | true 40 | 41 | 42 | 43 | 44 | 45 | org.slf4j 46 | slf4j-api 47 | ${slf4j.version} 48 | true 49 | 50 | 51 | ch.qos.logback 52 | logback-classic 53 | ${logback.version} 54 | true 55 | 56 | 57 | 58 | 59 | 60 | junit 61 | junit 62 | ${junit.version} 63 | test 64 | 65 | 66 | 67 | org.assertj 68 | assertj-core 69 | ${assertj.version} 70 | test 71 | 72 | 73 | 74 | org.mockito 75 | mockito-core 76 | ${mockito.version} 77 | test 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.apache.maven.plugins 87 | maven-source-plugin 88 | 89 | 90 | attach-sources 91 | 92 | jar 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | maven-jar-plugin 101 | 102 | 103 | 104 | test-jar 105 | 106 | 107 | 108 | org/springside/modules/test/**/*.class 109 | jetty/webdefault-windows.xml 110 | 111 | 112 | org/springside/modules/test/**/*Test.class 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/test/log/LogbackListAppender.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.test.log; 7 | 8 | import java.util.List; 9 | 10 | import org.slf4j.LoggerFactory; 11 | 12 | import ch.qos.logback.classic.Logger; 13 | import ch.qos.logback.classic.spi.ILoggingEvent; 14 | import ch.qos.logback.core.UnsynchronizedAppenderBase; 15 | 16 | import com.google.common.collect.Iterables; 17 | import com.google.common.collect.Lists; 18 | 19 | /** 20 | * 在List中保存日志的Appender, 用于测试Logback的日志输出. 21 | * 22 | * 在测试开始前, 使用任意一种addToLogger()方法将此appender添加到需要侦听的logger中. 23 | * 24 | * @author calvin 25 | */ 26 | public class LogbackListAppender extends UnsynchronizedAppenderBase { 27 | 28 | private final List logs = Lists.newArrayList(); 29 | 30 | public LogbackListAppender() { 31 | start(); 32 | } 33 | 34 | @Override 35 | protected void append(ILoggingEvent e) { 36 | logs.add(e); 37 | } 38 | 39 | /** 40 | * 返回之前append的第一个log. 41 | */ 42 | public ILoggingEvent getFirstLog() { 43 | if (logs.isEmpty()) { 44 | return null; 45 | } 46 | return logs.get(0); 47 | } 48 | 49 | /** 50 | * 返回之前append的第一个log的内容. 51 | */ 52 | public String getFirstMessage() { 53 | if (logs.isEmpty()) { 54 | return null; 55 | } 56 | return getFirstLog().getFormattedMessage(); 57 | } 58 | 59 | /** 60 | * 返回之前append的最后一个log. 61 | */ 62 | public ILoggingEvent getLastLog() { 63 | if (logs.isEmpty()) { 64 | return null; 65 | } 66 | return Iterables.getLast(logs); 67 | } 68 | 69 | /** 70 | * 返回之前append的最后一个log的内容. 71 | */ 72 | public String getLastMessage() { 73 | if (logs.isEmpty()) { 74 | return null; 75 | } 76 | return getLastLog().getFormattedMessage(); 77 | } 78 | 79 | /** 80 | * 返回之前append的所有log. 81 | */ 82 | public List getAllLogs() { 83 | return logs; 84 | } 85 | 86 | /** 87 | * 返回Log的数量。 88 | */ 89 | public int getLogsCount() { 90 | return logs.size(); 91 | } 92 | 93 | /** 94 | * 判断是否有log. 95 | */ 96 | public boolean isEmpty() { 97 | return logs.isEmpty(); 98 | } 99 | 100 | /** 101 | * 清除之前append的所有log. 102 | */ 103 | public void clearLogs() { 104 | logs.clear(); 105 | } 106 | 107 | /** 108 | * 将此appender添加到logger中. 109 | */ 110 | public void addToLogger(String loggerName) { 111 | Logger logger = (Logger) LoggerFactory.getLogger(loggerName); 112 | logger.addAppender(this); 113 | } 114 | 115 | /** 116 | * 将此appender添加到logger中. 117 | */ 118 | public void addToLogger(Class loggerClass) { 119 | Logger logger = (Logger) LoggerFactory.getLogger(loggerClass); 120 | logger.addAppender(this); 121 | } 122 | 123 | /** 124 | * 将此appender添加到root logger中. 125 | */ 126 | public void addToRootLogger() { 127 | Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); 128 | logger.addAppender(this); 129 | } 130 | 131 | /** 132 | * 将此appender从logger中移除. 133 | */ 134 | public void removeFromLogger(String loggerName) { 135 | Logger logger = (Logger) LoggerFactory.getLogger(loggerName); 136 | logger.detachAppender(this); 137 | } 138 | 139 | /** 140 | * 将此appender从logger中移除. 141 | */ 142 | public void removeFromLogger(Class loggerClass) { 143 | Logger logger = (Logger) LoggerFactory.getLogger(loggerClass); 144 | logger.detachAppender(this); 145 | } 146 | 147 | /** 148 | * 将此appender从root logger中移除. 149 | */ 150 | public void removeFromRootLogger() { 151 | Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); 152 | logger.detachAppender(this); 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /examples/boot-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.springside.examples 7 | boot-api 8 | 5.0.0-SNAPSHOT 9 | war 10 | Springside :: Examples :: SpringBoot WebService 11 | 12 | 13 | org.springframework.boot 14 | spring-boot-starter-parent 15 | 1.2.7.RELEASE 16 | 17 | 18 | 19 | 5.0.0-SNAPSHOT 20 | 3.4 21 | 18.0 22 | 1.4.6 23 | 1.10 24 | 2.2.0 25 | 1.3.176 26 | 27 | 1.7 28 | 29 | 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-data-jpa 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-actuator 44 | 45 | 46 | 47 | 48 | 49 | io.springside 50 | springside-utils 51 | ${springside.version} 52 | 53 | 54 | 55 | io.springside 56 | springside-core 57 | ${springside.version} 58 | 59 | 60 | 61 | 62 | com.h2database 63 | h2 64 | 65 | 66 | 67 | org.flywaydb 68 | flyway-core 69 | 70 | 71 | 72 | 73 | org.apache.commons 74 | commons-lang3 75 | ${commons-lang3.version} 76 | 77 | 78 | com.google.guava 79 | guava 80 | ${guava.version} 81 | 82 | 83 | ma.glasnost.orika 84 | orika-core 85 | ${orika.version} 86 | 87 | 88 | commons-codec 89 | commons-codec 90 | ${commons-codec.version} 91 | 92 | 93 | 94 | 95 | org.jolokia 96 | jolokia-core 97 | 98 | 99 | 100 | 101 | org.springframework.boot 102 | spring-boot-starter-test 103 | test 104 | 105 | 106 | 107 | org.assertj 108 | assertj-core 109 | ${assertj.version} 110 | test 111 | 112 | 113 | 114 | io.springside 115 | springside-utils 116 | ${springside.version} 117 | tests 118 | test 119 | 120 | 121 | 122 | 123 | 124 | 125 | org.springframework.boot 126 | spring-boot-maven-plugin 127 | 128 | 129 | org.flywaydb 130 | flyway-maven-plugin 131 | 3.1 132 | 133 | org.h2.Driver 134 | jdbc:h2:file:~/.h2/bootapi;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1; 135 | sa 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /modules/core/src/test/java/org/springside/modules/mapper/JaxbMapperTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.mapper; 7 | 8 | import static org.assertj.core.api.Assertions.*; 9 | 10 | import java.util.List; 11 | 12 | import javax.xml.bind.annotation.XmlAttribute; 13 | import javax.xml.bind.annotation.XmlElement; 14 | import javax.xml.bind.annotation.XmlElementWrapper; 15 | import javax.xml.bind.annotation.XmlRootElement; 16 | import javax.xml.bind.annotation.XmlTransient; 17 | import javax.xml.bind.annotation.XmlType; 18 | 19 | import org.apache.commons.lang3.builder.ToStringBuilder; 20 | import org.dom4j.Document; 21 | import org.dom4j.DocumentException; 22 | import org.dom4j.DocumentHelper; 23 | import org.dom4j.Element; 24 | import org.junit.Test; 25 | 26 | import com.google.common.collect.Lists; 27 | 28 | /** 29 | * 演示基于JAXB2.0的Java对象-XML转换及Dom4j的使用. 30 | * 31 | * 演示用xml如下: 32 | * 33 | *
     34 |  * 
     35 |  * 
     36 |  * 	calvin
     37 |  * 	
     38 |  * 		movie
     39 |  * 		sports
     40 |  * 	
     41 |  * 
     42 |  * 
    43 | */ 44 | public class JaxbMapperTest { 45 | 46 | @Test 47 | public void objectToXml() { 48 | User user = new User(); 49 | user.setId(1L); 50 | user.setName("calvin"); 51 | 52 | user.getInterests().add("movie"); 53 | user.getInterests().add("sports"); 54 | 55 | String xml = JaxbMapper.toXml(user, "UTF-8"); 56 | System.out.println("Jaxb Object to Xml result:\n" + xml); 57 | assertXmlByDom4j(xml); 58 | } 59 | 60 | @Test 61 | public void xmlToObject() { 62 | String xml = generateXmlByDom4j(); 63 | User user = JaxbMapper.fromXml(xml, User.class); 64 | 65 | System.out.println("Jaxb Xml to Object result:\n" + user); 66 | 67 | assertThat(user.getId()).isEqualTo(1L); 68 | assertThat(user.getInterests()).containsOnly("movie", "sports"); 69 | } 70 | 71 | /** 72 | * 测试以List对象作为根节点时的XML输出 73 | */ 74 | @Test 75 | public void toXmlWithListAsRoot() { 76 | User user1 = new User(); 77 | user1.setId(1L); 78 | user1.setName("calvin"); 79 | 80 | User user2 = new User(); 81 | user2.setId(2L); 82 | user2.setName("kate"); 83 | 84 | List userList = Lists.newArrayList(user1, user2); 85 | 86 | String xml = JaxbMapper.toXml(userList, "userList", User.class, "UTF-8"); 87 | System.out.println("Jaxb Object List to Xml result:\n" + xml); 88 | } 89 | 90 | /** 91 | * 使用Dom4j生成测试用的XML文档字符串. 92 | */ 93 | private static String generateXmlByDom4j() { 94 | Document document = DocumentHelper.createDocument(); 95 | 96 | Element root = document.addElement("user").addAttribute("id", "1"); 97 | 98 | root.addElement("name").setText("calvin"); 99 | 100 | // List 101 | Element interests = root.addElement("interests"); 102 | interests.addElement("interest").addText("movie"); 103 | interests.addElement("interest").addText("sports"); 104 | 105 | return document.asXML(); 106 | } 107 | 108 | /** 109 | * 使用Dom4j验证Jaxb所生成XML的正确性. 110 | */ 111 | private static void assertXmlByDom4j(String xml) { 112 | Document doc = null; 113 | try { 114 | doc = DocumentHelper.parseText(xml); 115 | } catch (DocumentException e) { 116 | fail(e.getMessage()); 117 | } 118 | Element user = doc.getRootElement(); 119 | assertThat(user.attribute("id").getValue()).isEqualTo("1"); 120 | 121 | Element interests = (Element) doc.selectSingleNode("//interests"); 122 | assertThat(interests.elements()).hasSize(2); 123 | assertThat(((Element) interests.elements().get(0)).getText()).isEqualTo("movie"); 124 | } 125 | 126 | @XmlRootElement 127 | // 指定子节点的顺序 128 | @XmlType(propOrder = { "name", "interests" }) 129 | private static class User { 130 | 131 | private Long id; 132 | private String name; 133 | private String password; 134 | 135 | private List interests = Lists.newArrayList(); 136 | 137 | // 设置转换为xml节点中的属性 138 | @XmlAttribute 139 | public Long getId() { 140 | return id; 141 | } 142 | 143 | public void setId(Long id) { 144 | this.id = id; 145 | } 146 | 147 | public String getName() { 148 | return name; 149 | } 150 | 151 | public void setName(String name) { 152 | this.name = name; 153 | } 154 | 155 | // 设置不转换为xml 156 | @XmlTransient 157 | public String getPassword() { 158 | return password; 159 | } 160 | 161 | public void setPassword(String password) { 162 | this.password = password; 163 | } 164 | 165 | // 设置对List的映射, xml为movie 166 | @XmlElementWrapper(name = "interests") 167 | @XmlElement(name = "interest") 168 | public List getInterests() { 169 | return interests; 170 | } 171 | 172 | public void setInterests(List interests) { 173 | this.interests = interests; 174 | } 175 | 176 | @Override 177 | public String toString() { 178 | return ToStringBuilder.reflectionToString(this); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /examples/boot-showcase/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.springside.examples 7 | boot-showcase 8 | 5.0.0-SNAPSHOT 9 | war 10 | Springside :: Example :: SpringBoot WebService Advanced Demo 11 | 12 | 13 | 5.0.0-SNAPSHOT 14 | 1.2.7.RELEASE 15 | 3.2.1 16 | 3.5.2 17 | 2.2.0 18 | 19 | 1.7 20 | UTF-8 21 | ${java.version} 22 | ${java.version} 23 | 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-web 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-data-jpa 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-actuator 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-mail 45 | 46 | 47 | 48 | 49 | io.springside 50 | springside-utils 51 | ${springside.version} 52 | 53 | 54 | 55 | io.springside 56 | springside-core 57 | ${springside.version} 58 | 59 | 60 | 61 | 62 | javax.servlet 63 | javax.servlet-api 64 | 65 | 66 | 67 | 68 | com.h2database 69 | h2 70 | runtime 71 | 72 | 73 | 74 | 75 | org.apache.commons 76 | commons-lang3 77 | ${commons-lang3.version} 78 | 79 | 80 | 81 | 82 | org.jolokia 83 | jolokia-core 84 | 85 | 86 | 87 | 88 | org.javasimon 89 | javasimon-spring 90 | ${javasimon.version} 91 | 92 | 93 | org.javasimon 94 | javasimon-console-embed 95 | ${javasimon.version} 96 | 97 | 98 | 99 | 100 | org.springframework.boot 101 | spring-boot-starter-test 102 | test 103 | 104 | 105 | 106 | org.assertj 107 | assertj-core 108 | ${assertj.version} 109 | test 110 | 111 | 112 | 113 | io.springside 114 | springside-utils 115 | ${springside.version} 116 | tests 117 | test 118 | 119 | 120 | 121 | 122 | 123 | 124 | src/main/resources 125 | true 126 | 127 | 128 | 129 | 130 | 131 | org.springframework.boot 132 | spring-boot-maven-plugin 133 | ${spring-boot.version} 134 | 135 | 136 | 137 | repackage 138 | 139 | 140 | 141 | 142 | 143 | org.apache.maven.plugins 144 | maven-war-plugin 145 | 2.4 146 | 147 | false 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | org.springframework.boot 159 | spring-boot-dependencies 160 | ${spring-boot.version} 161 | pom 162 | import 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /modules/core/src/main/java/org/springside/modules/mapper/JsonMapper.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.mapper; 7 | 8 | import java.io.IOException; 9 | import java.util.Collection; 10 | import java.util.Map; 11 | 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import com.fasterxml.jackson.annotation.JsonInclude.Include; 17 | import com.fasterxml.jackson.core.JsonProcessingException; 18 | import com.fasterxml.jackson.databind.DeserializationFeature; 19 | import com.fasterxml.jackson.databind.JavaType; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | import com.fasterxml.jackson.databind.SerializationFeature; 22 | import com.fasterxml.jackson.databind.util.JSONPObject; 23 | 24 | /** 25 | * 简单封装Jackson,实现JSON String<->Java Object的Mapper. 26 | * 27 | * 封装不同的输出风格, 使用不同的builder函数创建实例. 28 | * 29 | * @author calvin 30 | */ 31 | public class JsonMapper { 32 | 33 | private static Logger logger = LoggerFactory.getLogger(JsonMapper.class); 34 | 35 | private ObjectMapper mapper; 36 | 37 | public JsonMapper() { 38 | this(null); 39 | } 40 | 41 | public JsonMapper(Include include) { 42 | mapper = new ObjectMapper(); 43 | // 设置输出时包含属性的风格 44 | if (include != null) { 45 | mapper.setSerializationInclusion(include); 46 | } 47 | // 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性 48 | mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); 49 | } 50 | 51 | /** 52 | * 创建只输出非Null且非Empty(如List.isEmpty)的属性到Json字符串的Mapper,建议在外部接口中使用. 53 | */ 54 | public static JsonMapper nonEmptyMapper() { 55 | return new JsonMapper(Include.NON_EMPTY); 56 | } 57 | 58 | /** 59 | * 创建只输出初始值被改变的属性到Json字符串的Mapper, 最节约的存储方式,建议在内部接口中使用。 60 | */ 61 | public static JsonMapper nonDefaultMapper() { 62 | return new JsonMapper(Include.NON_DEFAULT); 63 | } 64 | 65 | /** 66 | * Object可以是POJO,也可以是Collection或数组。 如果对象为Null, 返回"null". 如果集合为空集合, 返回"[]". 67 | */ 68 | public String toJson(Object object) { 69 | 70 | try { 71 | return mapper.writeValueAsString(object); 72 | } catch (IOException e) { 73 | logger.warn("write to json string error:" + object, e); 74 | return null; 75 | } 76 | } 77 | 78 | /** 79 | * 反序列化POJO或简单Collection如List. 80 | * 81 | * 如果JSON字符串为Null或"null"字符串, 返回Null. 如果JSON字符串为"[]", 返回空集合. 82 | * 83 | * 如需反序列化复杂Collection如List, 请使用fromJson(String, JavaType) 84 | * 85 | * @see #fromJson(String, JavaType) 86 | */ 87 | public T fromJson(String jsonString, Class clazz) { 88 | if (StringUtils.isEmpty(jsonString)) { 89 | return null; 90 | } 91 | 92 | try { 93 | return mapper.readValue(jsonString, clazz); 94 | } catch (IOException e) { 95 | logger.warn("parse json string error:" + jsonString, e); 96 | return null; 97 | } 98 | } 99 | 100 | /** 101 | * 反序列化复杂Collection如List, contructCollectionType()或contructMapType()构造类型, 然后调用本函数. 102 | * 103 | * @see #createCollectionType(Class, Class...) 104 | */ 105 | public T fromJson(String jsonString, JavaType javaType) { 106 | if (StringUtils.isEmpty(jsonString)) { 107 | return null; 108 | } 109 | 110 | try { 111 | return (T) mapper.readValue(jsonString, javaType); 112 | } catch (IOException e) { 113 | logger.warn("parse json string error:" + jsonString, e); 114 | return null; 115 | } 116 | } 117 | 118 | /** 119 | * 构造Collection类型. 120 | */ 121 | public JavaType contructCollectionType(Class collectionClass, Class elementClass) { 122 | return mapper.getTypeFactory().constructCollectionType(collectionClass, elementClass); 123 | } 124 | 125 | /** 126 | * 构造Map类型. 127 | */ 128 | public JavaType contructMapType(Class mapClass, Class keyClass, Class valueClass) { 129 | return mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass); 130 | } 131 | 132 | /** 133 | * 当JSON里只含有Bean的部分屬性時,更新一個已存在Bean,只覆蓋該部分的屬性. 134 | */ 135 | public void update(String jsonString, Object object) { 136 | try { 137 | mapper.readerForUpdating(object).readValue(jsonString); 138 | } catch (JsonProcessingException e) { 139 | logger.warn("update json string:" + jsonString + " to object:" + object + " error.", e); 140 | } catch (IOException e) { 141 | logger.warn("update json string:" + jsonString + " to object:" + object + " error.", e); 142 | } 143 | } 144 | 145 | /** 146 | * 輸出JSONP格式數據. 147 | */ 148 | public String toJsonP(String functionName, Object object) { 149 | return toJson(new JSONPObject(functionName, object)); 150 | } 151 | 152 | /** 153 | * 設定是否使用Enum的toString函數來讀寫Enum, 為False時時使用Enum的name()函數來讀寫Enum, 默認為False. 注意本函數一定要在Mapper創建後, 所有的讀寫動作之前調用. 154 | */ 155 | public void enableEnumUseToString() { 156 | mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); 157 | mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); 158 | } 159 | 160 | /** 161 | * 取出Mapper做进一步的设置或使用其他序列化API. 162 | */ 163 | public ObjectMapper getMapper() { 164 | return mapper; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /modules/core/src/main/java/org/springside/modules/mapper/JaxbMapper.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.mapper; 7 | 8 | import java.io.StringReader; 9 | import java.io.StringWriter; 10 | import java.util.Collection; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | import java.util.concurrent.ConcurrentMap; 13 | 14 | import javax.xml.bind.JAXBContext; 15 | import javax.xml.bind.JAXBElement; 16 | import javax.xml.bind.JAXBException; 17 | import javax.xml.bind.Marshaller; 18 | import javax.xml.bind.Unmarshaller; 19 | import javax.xml.bind.annotation.XmlAnyElement; 20 | import javax.xml.namespace.QName; 21 | 22 | import org.apache.commons.lang3.StringUtils; 23 | import org.apache.commons.lang3.Validate; 24 | import org.springside.modules.utils.Exceptions; 25 | import org.springside.modules.utils.Reflections; 26 | 27 | /** 28 | * 使用Jaxb2.0实现XML<->Java Object的Mapper. 29 | * 30 | * 在创建时需要设定所有需要序列化的Root对象的Class. 31 | * 特别支持Root对象是Collection的情形. 32 | * 33 | * @author calvin 34 | */ 35 | public class JaxbMapper { 36 | 37 | private static ConcurrentMap jaxbContexts = new ConcurrentHashMap(); 38 | 39 | /** 40 | * Java Object->Xml without encoding. 41 | */ 42 | public static String toXml(Object root) { 43 | Class clazz = Reflections.getUserClass(root); 44 | return toXml(root, clazz, null); 45 | } 46 | 47 | /** 48 | * Java Object->Xml with encoding. 49 | */ 50 | public static String toXml(Object root, String encoding) { 51 | Class clazz = Reflections.getUserClass(root); 52 | return toXml(root, clazz, encoding); 53 | } 54 | 55 | /** 56 | * Java Object->Xml with encoding. 57 | */ 58 | public static String toXml(Object root, Class clazz, String encoding) { 59 | try { 60 | StringWriter writer = new StringWriter(); 61 | createMarshaller(clazz, encoding).marshal(root, writer); 62 | return writer.toString(); 63 | } catch (JAXBException e) { 64 | throw Exceptions.unchecked(e); 65 | } 66 | } 67 | 68 | /** 69 | * Java Collection->Xml without encoding, 特别支持Root Element是Collection的情形. 70 | */ 71 | public static String toXml(Collection root, String rootName, Class clazz) { 72 | return toXml(root, rootName, clazz, null); 73 | } 74 | 75 | /** 76 | * Java Collection->Xml with encoding, 特别支持Root Element是Collection的情形. 77 | */ 78 | public static String toXml(Collection root, String rootName, Class clazz, String encoding) { 79 | try { 80 | CollectionWrapper wrapper = new CollectionWrapper(); 81 | wrapper.collection = root; 82 | 83 | JAXBElement wrapperElement = new JAXBElement(new QName(rootName), 84 | CollectionWrapper.class, wrapper); 85 | 86 | StringWriter writer = new StringWriter(); 87 | createMarshaller(clazz, encoding).marshal(wrapperElement, writer); 88 | 89 | return writer.toString(); 90 | } catch (JAXBException e) { 91 | throw Exceptions.unchecked(e); 92 | } 93 | } 94 | 95 | /** 96 | * Xml->Java Object. 97 | */ 98 | public static T fromXml(String xml, Class clazz) { 99 | try { 100 | StringReader reader = new StringReader(xml); 101 | return (T) createUnmarshaller(clazz).unmarshal(reader); 102 | } catch (JAXBException e) { 103 | throw Exceptions.unchecked(e); 104 | } 105 | } 106 | 107 | /** 108 | * 创建Marshaller并设定encoding(可为null). 109 | * 线程不安全,需要每次创建或pooling。 110 | */ 111 | public static Marshaller createMarshaller(Class clazz, String encoding) { 112 | try { 113 | JAXBContext jaxbContext = getJaxbContext(clazz); 114 | 115 | Marshaller marshaller = jaxbContext.createMarshaller(); 116 | 117 | marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); 118 | 119 | if (StringUtils.isNotBlank(encoding)) { 120 | marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding); 121 | } 122 | 123 | return marshaller; 124 | } catch (JAXBException e) { 125 | throw Exceptions.unchecked(e); 126 | } 127 | } 128 | 129 | /** 130 | * 创建UnMarshaller. 131 | * 线程不安全,需要每次创建或pooling。 132 | */ 133 | public static Unmarshaller createUnmarshaller(Class clazz) { 134 | try { 135 | JAXBContext jaxbContext = getJaxbContext(clazz); 136 | return jaxbContext.createUnmarshaller(); 137 | } catch (JAXBException e) { 138 | throw Exceptions.unchecked(e); 139 | } 140 | } 141 | 142 | protected static JAXBContext getJaxbContext(Class clazz) { 143 | Validate.notNull(clazz, "'clazz' must not be null"); 144 | JAXBContext jaxbContext = jaxbContexts.get(clazz); 145 | if (jaxbContext == null) { 146 | try { 147 | jaxbContext = JAXBContext.newInstance(clazz, CollectionWrapper.class); 148 | jaxbContexts.putIfAbsent(clazz, jaxbContext); 149 | } catch (JAXBException ex) { 150 | throw new RuntimeException("Could not instantiate JAXBContext for class [" + clazz + "]: " 151 | + ex.getMessage(), ex); 152 | } 153 | } 154 | return jaxbContext; 155 | } 156 | 157 | /** 158 | * 封装Root Element 是 Collection的情况. 159 | */ 160 | public static class CollectionWrapper { 161 | 162 | @XmlAnyElement 163 | protected Collection collection; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /modules/core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | io.springside 7 | springside-modules 8 | 5.0.0-SNAPSHOT 9 | ../ 10 | 11 | springside-core 12 | jar 13 | Springside :: Module :: Core 14 | 15 | 16 | 17 | io.springside 18 | springside-utils 19 | ${springside.version} 20 | true 21 | 22 | 23 | 24 | 25 | com.google.guava 26 | guava 27 | ${guava.version} 28 | true 29 | 30 | 31 | org.apache.commons 32 | commons-lang3 33 | ${commons-lang3.version} 34 | true 35 | 36 | 37 | 38 | com.fasterxml.jackson.core 39 | jackson-databind 40 | ${jackson.version} 41 | true 42 | 43 | 44 | ma.glasnost.orika 45 | orika-core 46 | ${orika.version} 47 | true 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.slf4j 55 | slf4j-api 56 | ${slf4j.version} 57 | true 58 | 59 | 60 | ch.qos.logback 61 | logback-classic 62 | ${logback.version} 63 | true 64 | 65 | 66 | 67 | 68 | 69 | 70 | junit 71 | junit 72 | ${junit.version} 73 | test 74 | 75 | 76 | 77 | org.assertj 78 | assertj-core 79 | ${assertj.version} 80 | test 81 | 82 | 83 | 84 | org.mockito 85 | mockito-core 86 | ${mockito.version} 87 | test 88 | 89 | 90 | 91 | org.springframework 92 | spring-test 93 | test 94 | 95 | 96 | 97 | org.powermock 98 | powermock-module-junit4 99 | ${powermock.version} 100 | test 101 | 102 | 103 | org.powermock 104 | powermock-api-mockito 105 | ${powermock.version} 106 | test 107 | 108 | 109 | 110 | dom4j 111 | dom4j 112 | 1.6.1 113 | test 114 | 115 | 116 | jaxen 117 | jaxen 118 | 1.1.6 119 | test 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | org.springframework 128 | spring-framework-bom 129 | ${spring.version} 130 | pom 131 | import 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-source-plugin 142 | 143 | 144 | attach-sources 145 | 146 | jar 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | maven-jar-plugin 155 | 156 | 157 | 158 | test-jar 159 | 160 | 161 | 162 | org/springside/modules/test/**/*.class 163 | jetty/webdefault-windows.xml 164 | 165 | 166 | org/springside/modules/test/**/*Test.class 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | org.apache.maven.plugins 176 | maven-enforcer-plugin 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /modules/utils/src/test/java/org/springside/modules/utils/ReflectionsTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import static org.assertj.core.api.Assertions.*; 9 | 10 | import java.lang.reflect.InvocationTargetException; 11 | 12 | import org.junit.Test; 13 | 14 | public class ReflectionsTest { 15 | 16 | @Test 17 | public void getAndSetFieldValue() { 18 | TestBean bean = new TestBean(); 19 | // 无需getter函数, 直接读取privateField 20 | assertThat(Reflections.getFieldValue(bean, "privateField")).isEqualTo(1); 21 | // 绕过将publicField+1的getter函数,直接读取publicField的原始值 22 | assertThat(Reflections.getFieldValue(bean, "publicField")).isEqualTo(1); 23 | 24 | bean = new TestBean(); 25 | // 无需setter函数, 直接设置privateField 26 | Reflections.setFieldValue(bean, "privateField", 2); 27 | assertThat(bean.inspectPrivateField()).isEqualTo(2); 28 | 29 | // 绕过将publicField+1的setter函数,直接设置publicField的原始值 30 | Reflections.setFieldValue(bean, "publicField", 2); 31 | assertThat(bean.inspectPublicField()).isEqualTo(2); 32 | 33 | try { 34 | Reflections.getFieldValue(bean, "notExist"); 35 | failBecauseExceptionWasNotThrown(IllegalArgumentException.class); 36 | } catch (IllegalArgumentException e) { 37 | 38 | } 39 | 40 | try { 41 | Reflections.setFieldValue(bean, "notExist", 2); 42 | failBecauseExceptionWasNotThrown(IllegalArgumentException.class); 43 | } catch (IllegalArgumentException e) { 44 | 45 | } 46 | 47 | } 48 | 49 | @Test 50 | public void invokeGetterAndSetter() { 51 | TestBean bean = new TestBean(); 52 | assertThat(Reflections.invokeGetter(bean, "publicField")).isEqualTo(bean.inspectPublicField() + 1); 53 | 54 | bean = new TestBean(); 55 | // 通过setter的函数将+1 56 | Reflections.invokeSetter(bean, "publicField", 10); 57 | assertThat(bean.inspectPublicField()).isEqualTo(10 + 1); 58 | } 59 | 60 | @Test 61 | public void invokeMethod() { 62 | TestBean bean = new TestBean(); 63 | // 使用函数名+参数类型的匹配 64 | assertThat( 65 | Reflections 66 | .invokeMethod(bean, "privateMethod", new Class[] { String.class }, new Object[] { "calvin" })) 67 | .isEqualTo("hello calvin"); 68 | 69 | // 仅匹配函数名 70 | assertThat(Reflections.invokeMethodByName(bean, "privateMethod", new Object[] { "calvin" })).isEqualTo( 71 | "hello calvin"); 72 | 73 | // 函数名错 74 | try { 75 | Reflections.invokeMethod(bean, "notExistMethod", new Class[] { String.class }, new Object[] { "calvin" }); 76 | failBecauseExceptionWasNotThrown(IllegalArgumentException.class); 77 | } catch (IllegalArgumentException e) { 78 | 79 | } 80 | 81 | // 参数类型错 82 | try { 83 | Reflections.invokeMethod(bean, "privateMethod", new Class[] { Integer.class }, new Object[] { "calvin" }); 84 | failBecauseExceptionWasNotThrown(RuntimeException.class); 85 | } catch (RuntimeException e) { 86 | 87 | } 88 | 89 | // 函数名错 90 | try { 91 | Reflections.invokeMethodByName(bean, "notExistMethod", new Object[] { "calvin" }); 92 | failBecauseExceptionWasNotThrown(IllegalArgumentException.class); 93 | } catch (IllegalArgumentException e) { 94 | 95 | } 96 | 97 | } 98 | 99 | @Test 100 | public void getSuperClassGenricType() { 101 | // 获取第1,2个泛型类型 102 | assertThat(Reflections.getClassGenricType(TestBean.class)).isEqualTo(String.class); 103 | assertThat(Reflections.getClassGenricType(TestBean.class, 1)).isEqualTo(Long.class); 104 | 105 | // 定义父类时无泛型定义 106 | assertThat(Reflections.getClassGenricType(TestBean2.class)).isEqualTo(Object.class); 107 | 108 | // 无父类定义 109 | assertThat(Reflections.getClassGenricType(TestBean3.class)).isEqualTo(Object.class); 110 | } 111 | 112 | @Test 113 | public void convertReflectionExceptionToUnchecked() { 114 | IllegalArgumentException iae = new IllegalArgumentException(); 115 | // ReflectionException,normal 116 | RuntimeException e = Reflections.convertReflectionExceptionToUnchecked(iae); 117 | assertThat(e.getCause()).isEqualTo(iae); 118 | 119 | // InvocationTargetException,extract it's target exception. 120 | Exception ex = new Exception(); 121 | e = Reflections.convertReflectionExceptionToUnchecked(new InvocationTargetException(ex)); 122 | assertThat(e.getCause()).isEqualTo(ex); 123 | 124 | // UncheckedException, ignore it. 125 | RuntimeException re = new RuntimeException("abc"); 126 | e = Reflections.convertReflectionExceptionToUnchecked(re); 127 | assertThat(e).hasMessage("abc"); 128 | 129 | // Unexcepted Checked exception. 130 | e = Reflections.convertReflectionExceptionToUnchecked(ex); 131 | assertThat(e).hasMessage("Unexpected Checked Exception."); 132 | } 133 | 134 | public static class ParentBean { 135 | } 136 | 137 | public static class TestBean extends ParentBean { 138 | /** 没有getter/setter的field */ 139 | private int privateField = 1; 140 | /** 有getter/setter的field */ 141 | private int publicField = 1; 142 | 143 | // 通過getter函數會比屬性值+1 144 | public int getPublicField() { 145 | return publicField + 1; 146 | } 147 | 148 | // 通過setter函數會被比輸入值加1 149 | public void setPublicField(int publicField) { 150 | this.publicField = publicField + 1; 151 | } 152 | 153 | public int inspectPrivateField() { 154 | return privateField; 155 | } 156 | 157 | public int inspectPublicField() { 158 | return publicField; 159 | } 160 | 161 | private String privateMethod(String text) { 162 | return "hello " + text; 163 | } 164 | } 165 | 166 | public static class TestBean2 extends ParentBean { 167 | } 168 | 169 | public static class TestBean3 { 170 | 171 | private int id; 172 | 173 | public int getId() { 174 | return id; 175 | } 176 | 177 | public void setId(int id) { 178 | this.id = id; 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/Cryptos.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import java.security.GeneralSecurityException; 9 | import java.security.SecureRandom; 10 | import java.util.Arrays; 11 | 12 | import javax.crypto.Cipher; 13 | import javax.crypto.KeyGenerator; 14 | import javax.crypto.Mac; 15 | import javax.crypto.SecretKey; 16 | import javax.crypto.spec.IvParameterSpec; 17 | import javax.crypto.spec.SecretKeySpec; 18 | 19 | /** 20 | * 支持HMAC-SHA1消息签名 及 DES/AES对称加密的工具类. 21 | * 22 | * 支持Hex与Base64两种编码方式. 23 | * 24 | * @author calvin 25 | */ 26 | public class Cryptos { 27 | 28 | private static final String AES = "AES"; 29 | private static final String AES_CBC = "AES/CBC/PKCS5Padding"; 30 | private static final String HMACSHA1 = "HmacSHA1"; 31 | 32 | private static final int DEFAULT_HMACSHA1_KEYSIZE = 160; // RFC2401 33 | private static final int DEFAULT_AES_KEYSIZE = 128; 34 | private static final int DEFAULT_IVSIZE = 16; 35 | 36 | private static SecureRandom random = new SecureRandom(); 37 | 38 | // -- HMAC-SHA1 funciton --// 39 | /** 40 | * 使用HMAC-SHA1进行消息签名, 返回字节数组,长度为20字节. 41 | * 42 | * @param input 原始输入字符数组 43 | * @param key HMAC-SHA1密钥 44 | */ 45 | public static byte[] hmacSha1(byte[] input, byte[] key) { 46 | try { 47 | SecretKey secretKey = new SecretKeySpec(key, HMACSHA1); 48 | Mac mac = Mac.getInstance(HMACSHA1); 49 | mac.init(secretKey); 50 | return mac.doFinal(input); 51 | } catch (GeneralSecurityException e) { 52 | throw Exceptions.unchecked(e); 53 | } 54 | } 55 | 56 | /** 57 | * 校验HMAC-SHA1签名是否正确. 58 | * 59 | * @param expected 已存在的签名 60 | * @param input 原始输入字符串 61 | * @param key 密钥 62 | */ 63 | public static boolean isMacValid(byte[] expected, byte[] input, byte[] key) { 64 | byte[] actual = hmacSha1(input, key); 65 | return Arrays.equals(expected, actual); 66 | } 67 | 68 | /** 69 | * 生成HMAC-SHA1密钥,返回字节数组,长度为160位(20字节). 70 | * HMAC-SHA1算法对密钥无特殊要求, RFC2401建议最少长度为160位(20字节). 71 | */ 72 | public static byte[] generateHmacSha1Key() { 73 | try { 74 | KeyGenerator keyGenerator = KeyGenerator.getInstance(HMACSHA1); 75 | keyGenerator.init(DEFAULT_HMACSHA1_KEYSIZE); 76 | SecretKey secretKey = keyGenerator.generateKey(); 77 | return secretKey.getEncoded(); 78 | } catch (GeneralSecurityException e) { 79 | throw Exceptions.unchecked(e); 80 | } 81 | } 82 | 83 | // -- AES funciton --// 84 | /** 85 | * 使用AES加密原始字符串. 86 | * 87 | * @param input 原始输入字符数组 88 | * @param key 符合AES要求的密钥 89 | */ 90 | public static byte[] aesEncrypt(byte[] input, byte[] key) { 91 | return aes(input, key, Cipher.ENCRYPT_MODE); 92 | } 93 | 94 | /** 95 | * 使用AES加密原始字符串. 96 | * 97 | * @param input 原始输入字符数组 98 | * @param key 符合AES要求的密钥 99 | * @param iv 初始向量 100 | */ 101 | public static byte[] aesEncrypt(byte[] input, byte[] key, byte[] iv) { 102 | return aes(input, key, iv, Cipher.ENCRYPT_MODE); 103 | } 104 | 105 | /** 106 | * 使用AES解密字符串, 返回原始字符串. 107 | * 108 | * @param input Hex编码的加密字符串 109 | * @param key 符合AES要求的密钥 110 | */ 111 | public static String aesDecrypt(byte[] input, byte[] key) { 112 | byte[] decryptResult = aes(input, key, Cipher.DECRYPT_MODE); 113 | return new String(decryptResult); 114 | } 115 | 116 | /** 117 | * 使用AES解密字符串, 返回原始字符串. 118 | * 119 | * @param input Hex编码的加密字符串 120 | * @param key 符合AES要求的密钥 121 | * @param iv 初始向量 122 | */ 123 | public static String aesDecrypt(byte[] input, byte[] key, byte[] iv) { 124 | byte[] decryptResult = aes(input, key, iv, Cipher.DECRYPT_MODE); 125 | return new String(decryptResult); 126 | } 127 | 128 | /** 129 | * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果. 130 | * 131 | * @param input 原始字节数组 132 | * @param key 符合AES要求的密钥 133 | * @param mode Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE 134 | */ 135 | private static byte[] aes(byte[] input, byte[] key, int mode) { 136 | try { 137 | SecretKey secretKey = new SecretKeySpec(key, AES); 138 | Cipher cipher = Cipher.getInstance(AES); 139 | cipher.init(mode, secretKey); 140 | return cipher.doFinal(input); 141 | } catch (GeneralSecurityException e) { 142 | throw Exceptions.unchecked(e); 143 | } 144 | } 145 | 146 | /** 147 | * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果. 148 | * 149 | * @param input 原始字节数组 150 | * @param key 符合AES要求的密钥 151 | * @param iv 初始向量 152 | * @param mode Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE 153 | */ 154 | private static byte[] aes(byte[] input, byte[] key, byte[] iv, int mode) { 155 | try { 156 | SecretKey secretKey = new SecretKeySpec(key, AES); 157 | IvParameterSpec ivSpec = new IvParameterSpec(iv); 158 | Cipher cipher = Cipher.getInstance(AES_CBC); 159 | cipher.init(mode, secretKey, ivSpec); 160 | return cipher.doFinal(input); 161 | } catch (GeneralSecurityException e) { 162 | throw Exceptions.unchecked(e); 163 | } 164 | } 165 | 166 | /** 167 | * 生成AES密钥,返回字节数组, 默认长度为128位(16字节). 168 | */ 169 | public static byte[] generateAesKey() { 170 | return generateAesKey(DEFAULT_AES_KEYSIZE); 171 | } 172 | 173 | /** 174 | * 生成AES密钥,可选长度为128,192,256位. 175 | */ 176 | public static byte[] generateAesKey(int keysize) { 177 | try { 178 | KeyGenerator keyGenerator = KeyGenerator.getInstance(AES); 179 | keyGenerator.init(keysize); 180 | SecretKey secretKey = keyGenerator.generateKey(); 181 | return secretKey.getEncoded(); 182 | } catch (GeneralSecurityException e) { 183 | throw Exceptions.unchecked(e); 184 | } 185 | } 186 | 187 | /** 188 | * 生成随机向量,默认大小为cipher.getBlockSize(), 16字节. 189 | */ 190 | public static byte[] generateIV() { 191 | byte[] bytes = new byte[DEFAULT_IVSIZE]; 192 | random.nextBytes(bytes); 193 | return bytes; 194 | } 195 | } -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/api/BookEndpoint.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.api; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RequestMethod; 13 | import org.springframework.web.bind.annotation.RequestParam; 14 | import org.springframework.web.bind.annotation.RestController; 15 | import org.springside.examples.bootapi.domain.Account; 16 | import org.springside.examples.bootapi.domain.Book; 17 | import org.springside.examples.bootapi.dto.BookDto; 18 | import org.springside.examples.bootapi.service.AccountService; 19 | import org.springside.examples.bootapi.service.BookAdminService; 20 | import org.springside.examples.bootapi.service.BookBorrowService; 21 | import org.springside.examples.bootapi.service.exception.ErrorCode; 22 | import org.springside.examples.bootapi.service.exception.ServiceException; 23 | import org.springside.modules.constants.MediaTypes; 24 | import org.springside.modules.mapper.BeanMapper; 25 | 26 | // Spring Restful MVC Controller的标识, 直接输出内容,不调用template引擎. 27 | @RestController 28 | public class BookEndpoint { 29 | 30 | private static Logger logger = LoggerFactory.getLogger(BookEndpoint.class); 31 | 32 | @Autowired 33 | private AccountService accountService; 34 | 35 | @Autowired 36 | private BookAdminService adminService; 37 | 38 | @Autowired 39 | private BookBorrowService borrowService; 40 | 41 | @RequestMapping(value = "/api/books", produces = MediaTypes.JSON_UTF_8) 42 | public List listAllBook(Pageable pageable) { 43 | Iterable books = adminService.findAll(pageable); 44 | 45 | return BeanMapper.mapList(books, BookDto.class); 46 | } 47 | 48 | @RequestMapping(value = "/api/books/{id}", produces = MediaTypes.JSON_UTF_8) 49 | public BookDto listOneBook(@PathVariable("id") Long id) { 50 | Book book = adminService.findOne(id); 51 | 52 | return BeanMapper.map(book, BookDto.class); 53 | } 54 | 55 | @RequestMapping(value = "/api/books", method = RequestMethod.POST, consumes = MediaTypes.JSON_UTF_8) 56 | public void createBook(@RequestBody BookDto bookDto, 57 | @RequestParam(value = "token", required = false) String token) { 58 | checkToken(token); 59 | // 使用Header中的Token,查找登录用户 60 | Account currentUser = accountService.getLoginUser(token); 61 | 62 | // 使用BeanMapper, 将与外部交互的BookDto对象复制为应用内部的Book对象 63 | Book book = BeanMapper.map(bookDto, Book.class); 64 | 65 | // 保存Book对象 66 | adminService.saveBook(book, currentUser); 67 | } 68 | 69 | @RequestMapping(value = "/api/books/{id}/modify", method = RequestMethod.POST, consumes = MediaTypes.JSON_UTF_8) 70 | public void modifyBook(@RequestBody BookDto bookDto, 71 | @RequestParam(value = "token", required = false) String token) { 72 | checkToken(token); 73 | Account currentUser = accountService.getLoginUser(token); 74 | Book book = BeanMapper.map(bookDto, Book.class); 75 | adminService.modifyBook(book, currentUser.id); 76 | } 77 | 78 | @RequestMapping(value = "/api/books/{id}/delete") 79 | public void deleteBook(@PathVariable("id") Long id, @RequestParam(value = "token", required = false) String token) { 80 | checkToken(token); 81 | Account currentUser = accountService.getLoginUser(token); 82 | adminService.deleteBook(id, currentUser.id); 83 | } 84 | 85 | @RequestMapping(value = "/api/books/{id}/request") 86 | public void applyBorrowRequest(@PathVariable("id") Long id, 87 | @RequestParam(value = "token", required = false) String token) { 88 | checkToken(token); 89 | Account currentUser = accountService.getLoginUser(token); 90 | borrowService.applyBorrowRequest(id, currentUser); 91 | } 92 | 93 | @RequestMapping(value = "/api/books/{id}/cancel") 94 | public void cancelBorrowRequest(@PathVariable("id") Long id, 95 | @RequestParam(value = "token", required = false) String token) { 96 | checkToken(token); 97 | Account currentUser = accountService.getLoginUser(token); 98 | borrowService.cancelBorrowRequest(id, currentUser); 99 | } 100 | 101 | @RequestMapping(value = "/api/books/{id}/confirm") 102 | public void markBookBorrowed(@PathVariable("id") Long id, 103 | @RequestParam(value = "token", required = false) String token) { 104 | checkToken(token); 105 | Account currentUser = accountService.getLoginUser(token); 106 | borrowService.markBookBorrowed(id, currentUser); 107 | } 108 | 109 | @RequestMapping(value = "/api/books/{id}/reject") 110 | public void rejectBorrowRequest(@PathVariable("id") Long id, 111 | @RequestParam(value = "token", required = false) String token) { 112 | checkToken(token); 113 | Account currentUser = accountService.getLoginUser(token); 114 | borrowService.rejectBorrowRequest(id, currentUser); 115 | } 116 | 117 | @RequestMapping(value = "/api/books/{id}/return") 118 | public void markBookReturned(@PathVariable("id") Long id, 119 | @RequestParam(value = "token", required = false) String token) { 120 | checkToken(token); 121 | Account currentUser = accountService.getLoginUser(token); 122 | borrowService.markBookReturned(id, currentUser); 123 | } 124 | 125 | @RequestMapping(value = "/api/mybook", produces = MediaTypes.JSON_UTF_8) 126 | public List listMyBook(@RequestParam(value = "token", required = false) String token, Pageable pageable) { 127 | checkToken(token); 128 | Account currentUser = accountService.getLoginUser(token); 129 | List books = adminService.listMyBook(currentUser.id, pageable); 130 | return BeanMapper.mapList(books, BookDto.class); 131 | } 132 | 133 | @RequestMapping(value = "/api/myborrowedbook", produces = MediaTypes.JSON_UTF_8) 134 | public List listMyBorrowedBook(@RequestParam(value = "token", required = false) String token, 135 | Pageable pageable) { 136 | checkToken(token); 137 | Account currentUser = accountService.getLoginUser(token); 138 | List books = borrowService.listMyBorrowedBook(currentUser.id, pageable); 139 | return BeanMapper.mapList(books, BookDto.class); 140 | } 141 | 142 | private void checkToken(String token) { 143 | if (token == null) { 144 | throw new ServiceException("No token in request", ErrorCode.NO_TOKEN); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /modules/core/src/test/java/org/springside/modules/mapper/JsonMapperTest.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.mapper; 7 | 8 | import static org.assertj.core.api.Assertions.*; 9 | 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Map.Entry; 14 | 15 | import org.junit.Test; 16 | 17 | import com.fasterxml.jackson.core.type.TypeReference; 18 | import com.google.common.collect.Lists; 19 | import com.google.common.collect.Maps; 20 | 21 | /** 22 | * 测试Jackson对Object,Map,List,数组,枚举,日期类等的持久化. 23 | * 更多测试见showcase中的JsonDemo. 24 | * 25 | * @author calvin 26 | */ 27 | public class JsonMapperTest { 28 | 29 | private static JsonMapper binder = JsonMapper.nonDefaultMapper(); 30 | 31 | /** 32 | * 序列化对象/集合到Json字符串. 33 | */ 34 | @Test 35 | public void toJson() throws Exception { 36 | // Bean 37 | TestBean bean = new TestBean("A"); 38 | String beanString = binder.toJson(bean); 39 | System.out.println("Bean:" + beanString); 40 | assertThat(beanString).isEqualTo("{\"name\":\"A\"}"); 41 | 42 | // Map 43 | Map map = Maps.newLinkedHashMap(); 44 | map.put("name", "A"); 45 | map.put("age", 2); 46 | String mapString = binder.toJson(map); 47 | System.out.println("Map:" + mapString); 48 | assertThat(mapString).isEqualTo("{\"name\":\"A\",\"age\":2}"); 49 | 50 | // List 51 | List stringList = Lists.newArrayList("A", "B", "C"); 52 | String listString = binder.toJson(stringList); 53 | System.out.println("String List:" + listString); 54 | assertThat(listString).isEqualTo("[\"A\",\"B\",\"C\"]"); 55 | 56 | // List 57 | List beanList = Lists.newArrayList(new TestBean("A"), new TestBean("B")); 58 | String beanListString = binder.toJson(beanList); 59 | System.out.println("Bean List:" + beanListString); 60 | assertThat(beanListString).isEqualTo("[{\"name\":\"A\"},{\"name\":\"B\"}]"); 61 | 62 | // Bean[] 63 | TestBean[] beanArray = new TestBean[] { new TestBean("A"), new TestBean("B") }; 64 | String beanArrayString = binder.toJson(beanArray); 65 | System.out.println("Array List:" + beanArrayString); 66 | assertThat(beanArrayString).isEqualTo("[{\"name\":\"A\"},{\"name\":\"B\"}]"); 67 | } 68 | 69 | /** 70 | * 从Json字符串反序列化对象/集合. 71 | */ 72 | @Test 73 | public void fromJson() throws Exception { 74 | // Bean 75 | String beanString = "{\"name\":\"A\"}"; 76 | TestBean bean = binder.fromJson(beanString, TestBean.class); 77 | System.out.println("Bean:" + bean); 78 | 79 | // Map 80 | String mapString = "{\"name\":\"A\",\"age\":2}"; 81 | Map map = binder.fromJson(mapString, HashMap.class); 82 | System.out.println("Map:"); 83 | for (Entry entry : map.entrySet()) { 84 | System.out.println(entry.getKey() + " " + entry.getValue()); 85 | } 86 | 87 | // List 88 | String listString = "[\"A\",\"B\",\"C\"]"; 89 | List stringList = binder.getMapper().readValue(listString, List.class); 90 | System.out.println("String List:"); 91 | for (String element : stringList) { 92 | System.out.println(element); 93 | } 94 | 95 | // List 96 | String beanListString = "[{\"name\":\"A\"},{\"name\":\"B\"}]"; 97 | List beanList = binder.getMapper().readValue(beanListString, new TypeReference>() { 98 | }); 99 | System.out.println("Bean List:"); 100 | for (TestBean element : beanList) { 101 | System.out.println(element); 102 | } 103 | } 104 | 105 | /** 106 | * 测试传入空对象,空字符串,Empty的集合,"null"字符串的结果. 107 | */ 108 | @Test 109 | public void nullAndEmpty() { 110 | // toJson测试 // 111 | 112 | // Null Bean 113 | TestBean nullBean = null; 114 | String nullBeanString = binder.toJson(nullBean); 115 | assertThat(nullBeanString).isEqualTo("null"); 116 | 117 | // Empty List 118 | List emptyList = Lists.newArrayList(); 119 | String emptyListString = binder.toJson(emptyList); 120 | assertThat(emptyListString).isEqualTo("[]"); 121 | 122 | // fromJson测试 // 123 | 124 | // Null String for Bean 125 | TestBean nullBeanResult = binder.fromJson(null, TestBean.class); 126 | assertThat(nullBeanResult).isNull(); 127 | 128 | nullBeanResult = binder.fromJson("null", TestBean.class); 129 | assertThat(nullBeanResult).isNull(); 130 | 131 | // Null/Empty String for List 132 | List nullListResult = binder.fromJson(null, List.class); 133 | assertThat(nullListResult).isNull(); 134 | 135 | nullListResult = binder.fromJson("null", List.class); 136 | assertThat(nullListResult).isNull(); 137 | 138 | nullListResult = binder.fromJson("[]", List.class); 139 | assertThat(nullListResult).isEmpty(); 140 | } 141 | 142 | /** 143 | * 测试三种不同的Binder. 144 | */ 145 | @Test 146 | public void threeTypeBinders() { 147 | // 打印全部属性 148 | JsonMapper normalBinder = new JsonMapper(); 149 | TestBean bean = new TestBean("A"); 150 | assertThat(normalBinder.toJson(bean)).isEqualTo( 151 | "{\"name\":\"A\",\"defaultValue\":\"hello\",\"nullValue\":null}"); 152 | 153 | // 不打印nullValue属性 154 | JsonMapper nonNullBinder = JsonMapper.nonEmptyMapper(); 155 | assertThat(nonNullBinder.toJson(bean)).isEqualTo("{\"name\":\"A\",\"defaultValue\":\"hello\"}"); 156 | 157 | // 不打印默认值未改变的nullValue与defaultValue属性 158 | JsonMapper nonDefaultBinder = JsonMapper.nonDefaultMapper(); 159 | assertThat(nonDefaultBinder.toJson(bean)).isEqualTo("{\"name\":\"A\"}"); 160 | } 161 | 162 | public static class TestBean { 163 | 164 | private String name; 165 | private String defaultValue = "hello"; 166 | private String nullValue = null; 167 | 168 | public TestBean() { 169 | } 170 | 171 | public TestBean(String name) { 172 | this.name = name; 173 | } 174 | 175 | public String getName() { 176 | return name; 177 | } 178 | 179 | public void setName(String name) { 180 | this.name = name; 181 | } 182 | 183 | public String getDefaultValue() { 184 | return defaultValue; 185 | } 186 | 187 | public void setDefaultValue(String defaultValue) { 188 | this.defaultValue = defaultValue; 189 | } 190 | 191 | public String getNullValue() { 192 | return nullValue; 193 | } 194 | 195 | public void setNullValue(String nullValue) { 196 | this.nullValue = nullValue; 197 | } 198 | 199 | @Override 200 | public String toString() { 201 | return "TestBean [defaultValue=" + defaultValue + ", name=" + name + ", nullValue=" + nullValue + "]"; 202 | } 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /examples/boot-api/src/main/java/org/springside/examples/bootapi/service/BookBorrowService.java: -------------------------------------------------------------------------------- 1 | package org.springside.examples.bootapi.service; 2 | 3 | import java.util.List; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | import org.springside.examples.bootapi.domain.Account; 12 | import org.springside.examples.bootapi.domain.Book; 13 | import org.springside.examples.bootapi.domain.Message; 14 | import org.springside.examples.bootapi.repository.BookDao; 15 | import org.springside.examples.bootapi.repository.MessageDao; 16 | import org.springside.examples.bootapi.service.exception.ErrorCode; 17 | import org.springside.examples.bootapi.service.exception.ServiceException; 18 | import org.springside.modules.utils.Clock; 19 | 20 | // Spring Bean的标识. 21 | @Service 22 | public class BookBorrowService { 23 | 24 | private static Logger logger = LoggerFactory.getLogger(BookBorrowService.class); 25 | 26 | @Autowired 27 | protected BookDao bookDao; 28 | 29 | @Autowired 30 | protected MessageDao messageDao; 31 | 32 | // 可注入的Clock,方便测试时控制日期 33 | protected Clock clock = Clock.DEFAULT; 34 | 35 | @Transactional 36 | public void applyBorrowRequest(Long id, Account borrower) { 37 | Book book = bookDao.findOne(id); 38 | 39 | if (!book.status.equals(Book.STATUS_IDLE)) { 40 | logger.error("User request the book not idle, user id:" + borrower.id + ",book id:" + id + ",status:" 41 | + book.status); 42 | throw new ServiceException("The book is not idle", ErrorCode.BOOK_STATUS_WRONG); 43 | } 44 | 45 | if (borrower.id.equals(book.owner.id)) { 46 | logger.error("User borrow the book himself, user id:" + borrower.id + ",book id:" + id); 47 | throw new ServiceException("User shouldn't borrower the book which is himeself", 48 | ErrorCode.BOOK_OWNERSHIP_WRONG); 49 | } 50 | 51 | book.status = Book.STATUS_REQUEST; 52 | book.borrower = borrower; 53 | bookDao.save(book); 54 | 55 | Message message = new Message(book.owner, 56 | String.format("Apply book <%s> request by %s", book.title, borrower.name), clock.getCurrentDate()); 57 | 58 | messageDao.save(message); 59 | } 60 | 61 | @Transactional 62 | public void cancelBorrowRequest(Long id, Account borrower) { 63 | Book book = bookDao.findOne(id); 64 | 65 | if (!book.status.equals(Book.STATUS_REQUEST)) { 66 | logger.error("User cancel the book not reqesting, user id:" + borrower.id + ",book id:" + id + ",status:" 67 | + book.status); 68 | throw new ServiceException("The book is not requesting", ErrorCode.BOOK_STATUS_WRONG); 69 | } 70 | 71 | if (!borrower.id.equals(book.borrower.id)) { 72 | logger.error("User cancel the book not request by him, user id:" + borrower.id + ",book id:" + id 73 | + ",borrower id" + book.borrower.id); 74 | throw new ServiceException("User can't cancel other ones request", ErrorCode.BOOK_OWNERSHIP_WRONG); 75 | } 76 | 77 | book.status = Book.STATUS_IDLE; 78 | book.borrower = null; 79 | bookDao.save(book); 80 | 81 | Message message = new Message(book.owner, 82 | String.format("Cancel book <%s> request by %s", book.title, borrower.name), clock.getCurrentDate()); 83 | 84 | messageDao.save(message); 85 | } 86 | 87 | @Transactional 88 | public void markBookBorrowed(Long id, Account owner) { 89 | Book book = bookDao.findOne(id); 90 | 91 | if (!book.status.equals(Book.STATUS_REQUEST)) { 92 | logger.error("User confirm the book not reqesting, user id:" + owner.id + ",book id:" + id + ",status:" 93 | + book.status); 94 | throw new ServiceException("The book is not requesting", ErrorCode.BOOK_STATUS_WRONG); 95 | } 96 | 97 | if (!owner.id.equals(book.owner.id)) { 98 | logger.error("User confirm the book not himself, user id:" + owner.id + ",book id:" + id + ",owner id" 99 | + book.owner.id); 100 | throw new ServiceException("User can't cofirm others book", ErrorCode.BOOK_OWNERSHIP_WRONG); 101 | } 102 | 103 | book.status = Book.STATUS_OUT; 104 | book.borrowDate = clock.getCurrentDate(); 105 | bookDao.save(book); 106 | 107 | Message message = new Message(book.borrower, 108 | String.format("Confirm book <%s> request by %s", book.title, owner.name), clock.getCurrentDate()); 109 | messageDao.save(message); 110 | } 111 | 112 | @Transactional 113 | public void rejectBorrowRequest(Long id, Account owner) { 114 | Book book = bookDao.findOne(id); 115 | 116 | if (!book.status.equals(Book.STATUS_REQUEST)) { 117 | logger.error("User reject the book not reqesting, user id:" + owner.id + ",book id:" + id + ",status:" 118 | + book.status); 119 | throw new ServiceException("The book is not requesting", ErrorCode.BOOK_STATUS_WRONG); 120 | } 121 | 122 | if (!owner.id.equals(book.owner.id)) { 123 | 124 | logger.error("User reject the book not himself, user id:" + owner.id + ",book id:" + id + ",owener id" 125 | + book.owner.id); 126 | throw new ServiceException("User can't reject others book", ErrorCode.BOOK_OWNERSHIP_WRONG); 127 | } 128 | 129 | book.status = Book.STATUS_IDLE; 130 | book.borrowDate = null; 131 | book.borrower = null; 132 | bookDao.save(book); 133 | 134 | Message message = new Message(book.borrower, 135 | String.format("Reject book <%s> request by %s", book.title, owner.name), clock.getCurrentDate()); 136 | messageDao.save(message); 137 | } 138 | 139 | @Transactional 140 | public void markBookReturned(Long id, Account owner) { 141 | Book book = bookDao.findOne(id); 142 | 143 | if (!book.status.equals(Book.STATUS_OUT)) { 144 | logger.error( 145 | "User return the book not out, user id:" + owner.id + ",book id:" + id + ",status:" + book.status); 146 | throw new ServiceException("The book is not borrowing", ErrorCode.BOOK_STATUS_WRONG); 147 | } 148 | 149 | if (!owner.id.equals(book.owner.id)) { 150 | logger.error("User return the book not himself, user id:" + owner.id + ",book id:" + id + ",owner id" 151 | + book.owner.id); 152 | throw new ServiceException("User can't make others book returned", ErrorCode.BOOK_OWNERSHIP_WRONG); 153 | } 154 | 155 | book.status = Book.STATUS_IDLE; 156 | book.borrowDate = null; 157 | book.borrower = null; 158 | bookDao.save(book); 159 | 160 | Message message = new Message(book.borrower, 161 | String.format("Mark book <%s> returned by %s", book.title, owner.name), clock.getCurrentDate()); 162 | messageDao.save(message); 163 | } 164 | 165 | @Transactional(readOnly = true) 166 | public List listMyBorrowedBook(Long borrowerId, Pageable pageable) { 167 | return bookDao.findByBorrowerId(borrowerId, pageable); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/Collections3.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.HashMap; 11 | import java.util.Iterator; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import org.apache.commons.beanutils.PropertyUtils; 16 | import org.apache.commons.lang3.StringUtils; 17 | 18 | import com.google.common.primitives.Doubles; 19 | import com.google.common.primitives.Ints; 20 | import com.google.common.primitives.Longs; 21 | 22 | /** 23 | * Collections工具集. 24 | * 25 | * 主要由三部分组成: 26 | * 27 | * 1. 是否Empty的最常用函数. 28 | * 29 | * 2. 支持以原始类型存储的List,节约存储空间, 基于Guava. 30 | * 31 | * 2. 源自Apache Commons Collection, 争取不用在项目里引入它. 32 | * 33 | * 3. 反射提取集合种元素及其属性的功能. 34 | * 35 | * 在JDK的Collections和Guava的Collections2/Iterables后, 命名为Collections3. 36 | * 37 | * 另请直接使用: 38 | * 39 | * 1. JDK Collections的singletonList()/singletonMap()/emptyList()/emptyMap() 40 | * 41 | * 2. Guava Lists/Maps的newArrayLists(Element...elements)等函数 42 | * 43 | * @author calvin 44 | */ 45 | @SuppressWarnings("rawtypes") 46 | public class Collections3 { 47 | 48 | /** 49 | * 判断是否为空. 50 | */ 51 | public static boolean isEmpty(Collection collection) { 52 | return (collection == null) || collection.isEmpty(); 53 | } 54 | 55 | /** 56 | * 判断是否为空. 57 | */ 58 | public static boolean isEmpty(Map map) { 59 | return (map == null) || map.isEmpty(); 60 | } 61 | 62 | /** 63 | * 判断是否不为空. 64 | */ 65 | public static boolean isNotEmpty(Collection collection) { 66 | return (collection != null) && !(collection.isEmpty()); 67 | } 68 | 69 | /** 70 | * 判断是否不为空. 71 | */ 72 | public static boolean isNotEmpty(Map map) { 73 | return (map != null) && !(map.isEmpty()); 74 | } 75 | 76 | /** 77 | * 取得Collection的第一个元素,如果collection为空返回null. 78 | */ 79 | public static T getFirst(Collection collection) { 80 | if (isEmpty(collection)) { 81 | return null; 82 | } 83 | 84 | return collection.iterator().next(); 85 | } 86 | 87 | /** 88 | * 获取Collection的最后一个元素,如果collection为空返回null. 89 | */ 90 | public static T getLast(Collection collection) { 91 | if (isEmpty(collection)) { 92 | return null; 93 | } 94 | 95 | // 当类型List时,直接取得最后一个元素. 96 | if (collection instanceof List) { 97 | List list = (List) collection; 98 | return list.get(list.size() - 1); 99 | } 100 | 101 | // 其他类型通过iterator滚动到最后一个元素. 102 | Iterator iterator = collection.iterator(); 103 | while (true) { 104 | T current = iterator.next(); 105 | if (!iterator.hasNext()) { 106 | return current; 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * 返回a+b的新List. 113 | */ 114 | public static List union(final Collection a, final Collection b) { 115 | List result = new ArrayList(a); 116 | result.addAll(b); 117 | return result; 118 | } 119 | 120 | /** 121 | * 返回a-b的新List. 122 | */ 123 | public static List subtract(final Collection a, final Collection b) { 124 | List list = new ArrayList(a); 125 | for (T element : b) { 126 | list.remove(element); 127 | } 128 | 129 | return list; 130 | } 131 | 132 | /** 133 | * 返回a与b的交集的新List. 134 | */ 135 | public static List intersection(Collection a, Collection b) { 136 | List list = new ArrayList(); 137 | 138 | for (T element : a) { 139 | if (b.contains(element)) { 140 | list.add(element); 141 | } 142 | } 143 | return list; 144 | } 145 | 146 | /** 147 | * 返回一个底层由原始类型long保存的List, 与保存Long相比节约空间. 148 | */ 149 | public static List asList(long... backingArray) { 150 | return Longs.asList(backingArray); 151 | } 152 | 153 | /** 154 | * 返回一个底层由原始类型int保存的List, 与保存Integer相比节约空间. 155 | */ 156 | public static List asList(int... backingArray) { 157 | return Ints.asList(backingArray); 158 | } 159 | 160 | /** 161 | * 返回一个底层由原始类型double保存的Double, 与保存Double相比节约空间. 162 | */ 163 | public static List asList(double... backingArray) { 164 | return Doubles.asList(backingArray); 165 | } 166 | 167 | /** 168 | * 提取集合中的对象的两个属性(通过Getter函数), 组合成Map. 169 | * 170 | * @param collection 来源集合. 171 | * @param keyPropertyName 要提取为Map中的Key值的属性名. 172 | * @param valuePropertyName 要提取为Map中的Value值的属性名. 173 | */ 174 | public static Map extractToMap(final Collection collection, final String keyPropertyName, 175 | final String valuePropertyName) { 176 | Map map = new HashMap(collection.size()); 177 | 178 | try { 179 | for (Object obj : collection) { 180 | map.put(PropertyUtils.getProperty(obj, keyPropertyName), 181 | PropertyUtils.getProperty(obj, valuePropertyName)); 182 | } 183 | } catch (Exception e) { 184 | throw Reflections.convertReflectionExceptionToUnchecked(e); 185 | } 186 | 187 | return map; 188 | } 189 | 190 | /** 191 | * 提取集合中的对象的一个属性(通过Getter函数), 组合成List. 192 | * 193 | * @param collection 来源集合. 194 | * @param propertyName 要提取的属性名. 195 | */ 196 | public static List extractToList(final Collection collection, final String propertyName) { 197 | List list = new ArrayList(collection.size()); 198 | 199 | try { 200 | for (Object obj : collection) { 201 | list.add(PropertyUtils.getProperty(obj, propertyName)); 202 | } 203 | } catch (Exception e) { 204 | throw Reflections.convertReflectionExceptionToUnchecked(e); 205 | } 206 | 207 | return list; 208 | } 209 | 210 | /** 211 | * 提取集合中的对象的一个属性(通过Getter函数), 组合成由分割符分隔的字符串. 212 | * 213 | * @param collection 来源集合. 214 | * @param propertyName 要提取的属性名. 215 | * @param separator 分隔符. 216 | */ 217 | public static String extractToString(final Collection collection, final String propertyName, 218 | final String separator) { 219 | List list = extractToList(collection, propertyName); 220 | return StringUtils.join(list, separator); 221 | } 222 | 223 | /** 224 | * 转换Collection所有元素(通过toString())为String, 中间以 separator分隔。 225 | */ 226 | public static String convertToString(final Collection collection, final String separator) { 227 | return StringUtils.join(collection, separator); 228 | } 229 | 230 | /** 231 | * 转换Collection所有元素(通过toString())为String, 每个元素的前面加入prefix,后面加入postfix,如
    mymessage
    。 232 | */ 233 | public static String convertToString(final Collection collection, final String prefix, final String postfix) { 234 | StringBuilder builder = new StringBuilder(); 235 | for (Object o : collection) { 236 | builder.append(prefix).append(o).append(postfix); 237 | } 238 | return builder.toString(); 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /modules/core/src/main/resources/META-INF/springside-shiro.tld: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 23 | 1.1.2 24 | 1.2 25 | shiro 26 | http://www.springside.org.cn/tags/shiro 27 | Apache Shiro JSP Tag Library extends the hasAnyPermissions tag. 28 | 29 | 30 | hasPermission 31 | org.apache.shiro.web.tags.HasPermissionTag 32 | JSP 33 | Displays body content only if the current Subject (user) 34 | 'has' (implies) the specified permission (i.e the user has the specified ability). 35 | 36 | 37 | name 38 | true 39 | true 40 | 41 | 42 | 43 | 44 | 45 | hasAnyPermissions 46 | org.springside.modules.security.shiro.HasAnyPermissionsTag 47 | JSP 48 | Displays body content only if the current user has one of the specified permissions from a 49 | comma-separated list of permission names. 50 | 51 | 52 | name 53 | true 54 | true 55 | 56 | 57 | 58 | 59 | lacksPermission 60 | org.apache.shiro.web.tags.LacksPermissionTag 61 | JSP 62 | Displays body content only if the current Subject (user) does 63 | NOT have (not imply) the specified permission (i.e. the user lacks the specified ability) 64 | 65 | 66 | name 67 | true 68 | true 69 | 70 | 71 | 72 | 73 | hasRole 74 | org.apache.shiro.web.tags.HasRoleTag 75 | JSP 76 | Displays body content only if the current user has the specified role. 77 | 78 | name 79 | true 80 | true 81 | 82 | 83 | 84 | 85 | 86 | hasAnyRoles 87 | org.apache.shiro.web.tags.HasAnyRolesTag 88 | JSP 89 | Displays body content only if the current user has one of the specified roles from a 90 | comma-separated list of role names. 91 | 92 | 93 | name 94 | true 95 | true 96 | 97 | 98 | 99 | 100 | lacksRole 101 | org.apache.shiro.web.tags.LacksRoleTag 102 | JSP 103 | Displays body content only if the current user does NOT have the specified role 104 | (i.e. they explicitly lack the specified role) 105 | 106 | 107 | name 108 | true 109 | true 110 | 111 | 112 | 113 | 114 | authenticated 115 | org.apache.shiro.web.tags.AuthenticatedTag 116 | JSP 117 | Displays body content only if the current user has successfully authenticated 118 | _during their current session_. It is more restrictive than the 'user' tag. 119 | It is logically opposite to the 'notAuthenticated' tag. 120 | 121 | 122 | 123 | 124 | notAuthenticated 125 | org.apache.shiro.web.tags.NotAuthenticatedTag 126 | JSP 127 | Displays body content only if the current user has NOT succesfully authenticated 128 | _during their current session_. It is logically opposite to the 'authenticated' tag. 129 | 130 | 131 | 132 | 133 | user 134 | org.apache.shiro.web.tags.UserTag 135 | JSP 136 | Displays body content only if the current Subject has a known identity, either 137 | from a previous login or from 'RememberMe' services. Note that this is semantically different 138 | from the 'authenticated' tag, which is more restrictive. It is logically 139 | opposite to the 'guest' tag. 140 | 141 | 142 | 143 | 144 | guest 145 | org.apache.shiro.web.tags.GuestTag 146 | JSP 147 | Displays body content only if the current Subject IS NOT known to the system, either 148 | because they have not logged in or they have no corresponding 'RememberMe' identity. It is logically 149 | opposite to the 'user' tag. 150 | 151 | 152 | 153 | 154 | principal 155 | org.apache.shiro.web.tags.PrincipalTag 156 | JSP 157 | Displays the user's principal or a property of the user's principal. 158 | 159 | type 160 | false 161 | true 162 | 163 | 164 | property 165 | false 166 | true 167 | 168 | 169 | defaultValue 170 | false 171 | true 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/ThreadPoolBuilder.java: -------------------------------------------------------------------------------- 1 | package org.springside.modules.utils; 2 | 3 | import java.util.concurrent.ArrayBlockingQueue; 4 | import java.util.concurrent.BlockingQueue; 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.Executors; 7 | import java.util.concurrent.LinkedBlockingQueue; 8 | import java.util.concurrent.RejectedExecutionHandler; 9 | import java.util.concurrent.SynchronousQueue; 10 | import java.util.concurrent.ThreadFactory; 11 | import java.util.concurrent.ThreadPoolExecutor; 12 | import java.util.concurrent.ThreadPoolExecutor.AbortPolicy; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * ThreadPool创建的工具类. 17 | * 18 | * 对比JDK Executors中的newFixedThreadPool()和newCachedThreadPool()函数,提供更多有用的配置项. 19 | * 20 | * 使用示例: 21 | * 22 | *
     23 |  * ExecutorService ExecutorService = new FixedThreadPoolBuilder().setPoolSize(10).build();
     24 |  * 
    25 | */ 26 | public class ThreadPoolBuilder { 27 | 28 | private static RejectedExecutionHandler defaultRejectHandler = new AbortPolicy(); 29 | 30 | /** 31 | * 创建FixedThreadPool. 32 | * 33 | * 1. 任务提交时, 如果线程数还没达到poolSize即创建新线程并绑定任务(即poolSize次提交后线程总数必达到poolSize,不会重用之前的线程) 34 | * 35 | * 1.a poolSize是必填项,不能忽略. 36 | * 37 | * 2. 第poolSize次任务提交后, 新增任务放入Queue中, Pool中的所有线程从Queue中take任务执行. 38 | * 39 | * 2.a Queue默认为无限长的LinkedBlockingQueue, 也可以设置queueSize换成有界的队列. 40 | * 41 | * 2.b 如果使用有界队列, 当队列满了之后,会调用RejectHandler进行处理, 默认为AbortPolicy,抛出RejectedExecutionException异常. 42 | * 其他可选的Policy包括静默放弃当前任务(Discard),放弃Queue里最老的任务(DisacardOldest),或由主线程来直接执行(CallerRuns). 43 | * 44 | * 3. 因为线程全部为core线程,所以不会在空闲回收. 45 | */ 46 | public static class FixedThreadPoolBuilder { 47 | 48 | private int poolSize = 0; 49 | private int queueSize = 0; 50 | 51 | private ThreadFactory threadFactory = null; 52 | private RejectedExecutionHandler rejectHandler; 53 | 54 | public FixedThreadPoolBuilder setPoolSize(int poolSize) { 55 | this.poolSize = poolSize; 56 | return this; 57 | } 58 | 59 | public FixedThreadPoolBuilder setQueueSize(int queueSize) { 60 | this.queueSize = queueSize; 61 | return this; 62 | } 63 | 64 | public FixedThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) { 65 | this.threadFactory = threadFactory; 66 | return this; 67 | } 68 | 69 | public FixedThreadPoolBuilder setRejectHanlder(RejectedExecutionHandler rejectHandler) { 70 | this.rejectHandler = rejectHandler; 71 | return this; 72 | } 73 | 74 | public ExecutorService build() { 75 | if (poolSize < 1) { 76 | throw new IllegalArgumentException("size not set"); 77 | } 78 | 79 | BlockingQueue queue = null; 80 | if (queueSize == 0) { 81 | queue = new LinkedBlockingQueue(); 82 | } else { 83 | queue = new ArrayBlockingQueue(queueSize); 84 | } 85 | 86 | if (threadFactory == null) { 87 | threadFactory = Executors.defaultThreadFactory(); 88 | } 89 | 90 | if (rejectHandler == null) { 91 | rejectHandler = defaultRejectHandler; 92 | } 93 | 94 | return new ThreadPoolExecutor(poolSize, poolSize, 0L, TimeUnit.MILLISECONDS, queue, threadFactory, 95 | rejectHandler); 96 | } 97 | } 98 | 99 | /** 100 | * 创建CachedThreadPool. 101 | * 102 | * 1. 任务提交时, 如果线程数还没达到minSize即创建新线程并绑定任务(即minSize次提交后线程总数必达到minSize, 不会重用之前的线程) 103 | * 104 | * 1.a minSize默认为0, 可设置保证有基本的线程处理请求不被回收. 105 | * 106 | * 2. 第minSize次任务提交后, 新增任务提交进SynchronousQueue后,如果没有空闲线程立刻处理,则会创建新的线程, 直到总线程数达到上限. 107 | * 108 | * 2.a maxSize默认为Integer.Max, 可进行设置. 109 | * 110 | * 2.b 如果设置了maxSize, 当总线程数达到上限, 会调用RejectHandler进行处理, 默认为AbortPolicy, 抛出RejectedExecutionException异常. 111 | * 其他可选的Policy包括静默放弃当前任务(Discard),或由主线程来直接执行(CallerRuns). 112 | * 113 | * 3. minSize以上, maxSize以下的线程, 如果在keepAliveTime中都poll不到任务执行将会被结束掉, keeAliveTime默认为60秒, 可设置. 114 | */ 115 | public static class CachedThreadPoolBuilder { 116 | 117 | private int minSize = 0; 118 | private int maxSize = Integer.MAX_VALUE; 119 | private int keepAliveSecs = 60; 120 | 121 | private ThreadFactory threadFactory = null; 122 | private RejectedExecutionHandler rejectHandler; 123 | 124 | public CachedThreadPoolBuilder setMinSize(int minSize) { 125 | this.minSize = minSize; 126 | return this; 127 | } 128 | 129 | public CachedThreadPoolBuilder setMaxSize(int maxSize) { 130 | this.maxSize = maxSize; 131 | return this; 132 | } 133 | 134 | public CachedThreadPoolBuilder setKeepAliveSecs(int keepAliveSecs) { 135 | this.keepAliveSecs = keepAliveSecs; 136 | return this; 137 | } 138 | 139 | public CachedThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) { 140 | this.threadFactory = threadFactory; 141 | return this; 142 | } 143 | 144 | public CachedThreadPoolBuilder setRejectHanlder(RejectedExecutionHandler rejectHandler) { 145 | this.rejectHandler = rejectHandler; 146 | return this; 147 | } 148 | 149 | public ExecutorService build() { 150 | 151 | if (threadFactory == null) { 152 | threadFactory = Executors.defaultThreadFactory(); 153 | } 154 | 155 | if (rejectHandler == null) { 156 | rejectHandler = defaultRejectHandler; 157 | } 158 | 159 | return new ThreadPoolExecutor(minSize, maxSize, keepAliveSecs, TimeUnit.SECONDS, 160 | new SynchronousQueue(), threadFactory, rejectHandler); 161 | } 162 | } 163 | 164 | /** 165 | * 可同时设置min/max/queue Size的线程池, 仅用于特殊场景. 166 | * 167 | * 比如并发要求非常高,觉得SynchronousQueue的性能太差. 168 | * 169 | * 比如平常使用Core线程工作,如果满了先放queue,queue满再开临时线程,此时queue的长度一定要按项目需求设好. 170 | */ 171 | public static class ConfigurableThreadPoolBuilder { 172 | 173 | private int minSize = 0; 174 | private int maxSize = Integer.MAX_VALUE; 175 | private int queueSize = 0; 176 | private int keepAliveSecs = 60; 177 | 178 | private ThreadFactory threadFactory = null; 179 | private RejectedExecutionHandler rejectHandler; 180 | 181 | public ConfigurableThreadPoolBuilder setMinSize(int minSize) { 182 | this.minSize = minSize; 183 | return this; 184 | } 185 | 186 | public ConfigurableThreadPoolBuilder setMaxSize(int maxSize) { 187 | this.maxSize = maxSize; 188 | return this; 189 | } 190 | 191 | public ConfigurableThreadPoolBuilder setQueueSize(int queueSize) { 192 | this.queueSize = queueSize; 193 | return this; 194 | } 195 | 196 | public ConfigurableThreadPoolBuilder setKeepAliveSecs(int keepAliveSecs) { 197 | this.keepAliveSecs = keepAliveSecs; 198 | return this; 199 | } 200 | 201 | public ConfigurableThreadPoolBuilder setThreadFactory(ThreadFactory threadFactory) { 202 | this.threadFactory = threadFactory; 203 | return this; 204 | } 205 | 206 | public ConfigurableThreadPoolBuilder setRejectHanlder(RejectedExecutionHandler rejectHandler) { 207 | this.rejectHandler = rejectHandler; 208 | return this; 209 | } 210 | 211 | public ExecutorService build() { 212 | 213 | BlockingQueue queue = null; 214 | if (queueSize == 0) { 215 | queue = new LinkedBlockingQueue(); 216 | } else { 217 | queue = new ArrayBlockingQueue(queueSize); 218 | } 219 | 220 | if (threadFactory == null) { 221 | threadFactory = Executors.defaultThreadFactory(); 222 | } 223 | 224 | if (rejectHandler == null) { 225 | rejectHandler = defaultRejectHandler; 226 | } 227 | 228 | return new ThreadPoolExecutor(minSize, maxSize, keepAliveSecs, TimeUnit.SECONDS, queue, threadFactory, 229 | rejectHandler); 230 | } 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/Digests.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.nio.charset.Charset; 11 | import java.security.GeneralSecurityException; 12 | import java.security.MessageDigest; 13 | import java.security.SecureRandom; 14 | import java.util.zip.CRC32; 15 | 16 | import org.apache.commons.lang3.Validate; 17 | import org.springside.modules.constants.Charsets; 18 | 19 | import com.google.common.hash.Hashing; 20 | 21 | /** 22 | * 消息摘要的工具类. 23 | * 24 | * 支持SHA-1/MD5这些安全性较高,返回byte[]的(可用Encodes进一步被编码为Hex, Base64或UrlSafeBase64),支持带salt达到更高的安全性. 25 | * 26 | * 也支持crc32,murmur32这些不追求安全性,性能较高,返回int的. 27 | * 28 | * @author calvin 29 | */ 30 | public class Digests { 31 | 32 | private static final String SHA1 = "SHA-1"; 33 | private static final String MD5 = "MD5"; 34 | 35 | private static SecureRandom random = new SecureRandom(); 36 | 37 | /** 38 | * 对输入字符串进行sha1散列. 39 | */ 40 | public static byte[] sha1(byte[] input) { 41 | return digest(input, SHA1, null, 1); 42 | } 43 | 44 | /** 45 | * 对输入字符串进行sha1散列. 46 | */ 47 | public static byte[] sha1(String input) { 48 | return digest(input.getBytes(Charsets.UTF8), SHA1, null, 1); 49 | } 50 | 51 | /** 52 | * 对输入字符串进行sha1散列. 53 | */ 54 | public static byte[] sha1(String input, Charset charset) { 55 | return digest(input.getBytes(charset), SHA1, null, 1); 56 | } 57 | 58 | /** 59 | * 对输入字符串进行sha1散列,带salt达到更高的安全性. 60 | */ 61 | public static byte[] sha1(byte[] input, byte[] salt) { 62 | return digest(input, SHA1, salt, 1); 63 | } 64 | 65 | /** 66 | * 对输入字符串进行sha1散列,带salt达到更高的安全性. 67 | */ 68 | public static byte[] sha1(String input, byte[] salt) { 69 | return digest(input.getBytes(Charsets.UTF8), SHA1, salt, 1); 70 | } 71 | 72 | /** 73 | * 对输入字符串进行sha1散列,带salt达到更高的安全性. 74 | */ 75 | public static byte[] sha1(String input, Charset charset, byte[] salt) { 76 | return digest(input.getBytes(charset), SHA1, salt, 1); 77 | } 78 | 79 | /** 80 | * 对输入字符串进行sha1散列,带salt而且迭代达到更高更高的安全性. 81 | */ 82 | public static byte[] sha1(byte[] input, byte[] salt, int iterations) { 83 | return digest(input, SHA1, salt, iterations); 84 | } 85 | 86 | /** 87 | * 对输入字符串进行sha1散列,带salt而且迭代达到更高更高的安全性. 88 | */ 89 | public static byte[] sha1(String input, byte[] salt, int iterations) { 90 | return digest(input.getBytes(Charsets.UTF8), SHA1, salt, iterations); 91 | } 92 | 93 | /** 94 | * 对输入字符串进行sha1散列,带salt而且迭代达到更高更高的安全性. 95 | */ 96 | public static byte[] sha1(String input, Charset charset, byte[] salt, int iterations) { 97 | return digest(input.getBytes(charset), SHA1, salt, iterations); 98 | } 99 | 100 | /** 101 | * 对字符串进行散列, 支持md5与sha1算法. 102 | */ 103 | private static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) { 104 | try { 105 | MessageDigest digest = MessageDigest.getInstance(algorithm); 106 | 107 | if (salt != null) { 108 | digest.update(salt); 109 | } 110 | 111 | byte[] result = digest.digest(input); 112 | 113 | for (int i = 1; i < iterations; i++) { 114 | digest.reset(); 115 | result = digest.digest(result); 116 | } 117 | return result; 118 | } catch (GeneralSecurityException e) { 119 | throw Exceptions.unchecked(e); 120 | } 121 | } 122 | 123 | /** 124 | * 生成随机的Byte[]作为salt. 125 | * 126 | * @param numBytes salt数组的大小 127 | */ 128 | public static byte[] generateSalt(int numBytes) { 129 | Validate.isTrue(numBytes > 0, "numBytes argument must be a positive integer (1 or larger)", numBytes); 130 | 131 | byte[] bytes = new byte[numBytes]; 132 | random.nextBytes(bytes); 133 | return bytes; 134 | } 135 | 136 | /** 137 | * 对文件进行md5散列. 138 | */ 139 | public static byte[] md5(InputStream input) throws IOException { 140 | return digest(input, MD5); 141 | } 142 | 143 | /** 144 | * 对文件进行sha1散列. 145 | */ 146 | public static byte[] sha1(InputStream input) throws IOException { 147 | return digest(input, SHA1); 148 | } 149 | 150 | private static byte[] digest(InputStream input, String algorithm) throws IOException { 151 | try { 152 | MessageDigest messageDigest = MessageDigest.getInstance(algorithm); 153 | int bufferLength = 8 * 1024; 154 | byte[] buffer = new byte[bufferLength]; 155 | int read = input.read(buffer, 0, bufferLength); 156 | 157 | while (read > -1) { 158 | messageDigest.update(buffer, 0, read); 159 | read = input.read(buffer, 0, bufferLength); 160 | } 161 | 162 | return messageDigest.digest(); 163 | } catch (GeneralSecurityException e) { 164 | throw Exceptions.unchecked(e); 165 | } 166 | } 167 | 168 | /** 169 | * 对输入字符串进行crc32散列. 170 | */ 171 | public static int crc32(byte[] input) { 172 | CRC32 crc32 = new CRC32(); 173 | crc32.update(input); 174 | return (int) crc32.getValue(); 175 | } 176 | 177 | /** 178 | * 对输入字符串进行crc32散列. 179 | */ 180 | public static int crc32(String input) { 181 | CRC32 crc32 = new CRC32(); 182 | crc32.update(input.getBytes(Charsets.UTF8)); 183 | return (int) crc32.getValue(); 184 | } 185 | 186 | /** 187 | * 对输入字符串进行crc32散列. 188 | */ 189 | public static int crc32(String input, Charset charset) { 190 | CRC32 crc32 = new CRC32(); 191 | crc32.update(input.getBytes(charset)); 192 | return (int) crc32.getValue(); 193 | } 194 | 195 | /** 196 | * 对输入字符串进行crc32散列,与php兼容,在64bit系统下返回永远是正数的long 197 | */ 198 | public static long crc32AsLong(byte[] input) { 199 | CRC32 crc32 = new CRC32(); 200 | crc32.update(input); 201 | return crc32.getValue(); 202 | } 203 | 204 | /** 205 | * 对输入字符串进行crc32散列,与php兼容,在64bit系统下返回永远是正数的long 206 | */ 207 | public static long crc32AsLong(String input) { 208 | CRC32 crc32 = new CRC32(); 209 | crc32.update(input.getBytes(Charsets.UTF8)); 210 | return crc32.getValue(); 211 | } 212 | 213 | /** 214 | * 对输入字符串进行crc32散列,与php兼容,在64bit系统下返回永远是正数的long 215 | */ 216 | public static long crc32AsLong(String input, Charset charset) { 217 | CRC32 crc32 = new CRC32(); 218 | crc32.update(input.getBytes(charset)); 219 | return crc32.getValue(); 220 | } 221 | 222 | /** 223 | * 对输入字符串进行murmur32散列 224 | */ 225 | public static int murmur32(byte[] input) { 226 | return Hashing.murmur3_32().hashBytes(input).asInt(); 227 | } 228 | 229 | /** 230 | * 对输入字符串进行murmur32散列 231 | */ 232 | public static int murmur32(String input) { 233 | return Hashing.murmur3_32().hashString(input, Charsets.UTF8).asInt(); 234 | } 235 | 236 | /** 237 | * 对输入字符串进行murmur32散列 238 | */ 239 | public static int murmur32(String input, Charset charset) { 240 | return Hashing.murmur3_32().hashString(input, charset).asInt(); 241 | } 242 | 243 | /** 244 | * 对输入字符串进行murmur32散列,带有seed 245 | */ 246 | public static int murmur32(byte[] input, int seed) { 247 | return Hashing.murmur3_32(seed).hashBytes(input).asInt(); 248 | } 249 | 250 | /** 251 | * 对输入字符串进行murmur32散列,带有seed 252 | */ 253 | public static int murmur32(String input, int seed) { 254 | return Hashing.murmur3_32(seed).hashString(input, Charsets.UTF8).asInt(); 255 | } 256 | 257 | /** 258 | * 对输入字符串进行murmur32散列,带有seed 259 | */ 260 | public static int murmur32(String input, Charset charset, int seed) { 261 | return Hashing.murmur3_32(seed).hashString(input, charset).asInt(); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /examples/boot-api/src/test/java/org/springside/examples/bootapi/functional/BookEndpointTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2014 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | * the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 10 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | * specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | package org.springside.examples.bootapi.functional; 15 | 16 | import static org.assertj.core.api.Assertions.*; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Map; 20 | 21 | import org.junit.Before; 22 | import org.junit.FixMethodOrder; 23 | import org.junit.Test; 24 | import org.junit.runners.MethodSorters; 25 | import org.springframework.beans.factory.annotation.Autowired; 26 | import org.springframework.boot.test.TestRestTemplate; 27 | import org.springframework.http.HttpStatus; 28 | import org.springframework.http.ResponseEntity; 29 | import org.springframework.web.client.RestTemplate; 30 | import org.springside.examples.bootapi.api.support.ErrorResult; 31 | import org.springside.examples.bootapi.domain.Book; 32 | import org.springside.examples.bootapi.dto.BookDto; 33 | import org.springside.examples.bootapi.repository.BookDao; 34 | import org.springside.examples.bootapi.service.exception.ErrorCode; 35 | import org.springside.modules.mapper.JsonMapper; 36 | import org.springside.modules.test.data.RandomData; 37 | 38 | import com.google.common.collect.Maps; 39 | 40 | // 测试方法的执行顺序在不同JVM里是不固定的,此处设为按方法名排序,避免方法间数据影响的不确定性 41 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 42 | public class BookEndpointTest extends BaseFunctionalTest { 43 | 44 | // 注入Spring Context中的BookDao,实现白盒查询数据库实际情况 45 | @Autowired 46 | private BookDao bookDao; 47 | 48 | private RestTemplate restTemplate; 49 | private JsonMapper jsonMapper = new JsonMapper(); 50 | 51 | private String resourceUrl; 52 | private String loginUrl; 53 | private String logoutUrl; 54 | 55 | @Before 56 | public void setup() { 57 | // TestRestTemplate与RestTemplate, 服务端返回非200返回码时,不会抛异常. 58 | restTemplate = new TestRestTemplate(); 59 | resourceUrl = "http://localhost:" + port + "/api/books"; 60 | loginUrl = "http://localhost:" + port + "/api/accounts/login"; 61 | logoutUrl = "http://localhost:" + port + "/api/accounts/logout"; 62 | } 63 | 64 | @Test 65 | public void listBook() { 66 | BookList tasks = restTemplate.getForObject(resourceUrl, BookList.class); 67 | assertThat(tasks).hasSize(3); 68 | BookDto firstBook = tasks.get(0); 69 | 70 | assertThat(firstBook.title).isEqualTo("Big Data日知录"); 71 | assertThat(firstBook.owner.name).isEqualTo("Calvin"); 72 | 73 | BookDto book = restTemplate.getForObject(resourceUrl + "/{id}", BookDto.class, 1L); 74 | assertThat(book.title).isEqualTo("Big Data日知录"); 75 | assertThat(book.owner.name).isEqualTo("Calvin"); 76 | } 77 | 78 | @Test 79 | public void applyRequest() { 80 | String token = login("calvin.xiao@springside.io"); 81 | 82 | ResponseEntity response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", 83 | String.class, 3L, token); 84 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 85 | 86 | // 查询数据库状态 87 | Book book = bookDao.findOne(3L); 88 | assertThat(book.borrower.id).isEqualTo(1L); 89 | assertThat(book.status).isEqualTo(Book.STATUS_REQUEST); 90 | 91 | // 回退操作 92 | response = restTemplate.getForEntity(resourceUrl + "/{id}/cancel?token={token}", String.class, 3L, token); 93 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 94 | 95 | logout(token); 96 | } 97 | 98 | @Test 99 | public void applyRequestWithError() { 100 | // 未设置token 101 | ResponseEntity response = restTemplate.getForEntity(resourceUrl + "/{id}/request", String.class, 1L); 102 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); 103 | ErrorResult errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class); 104 | assertThat(errorResult.code).isEqualTo(ErrorCode.NO_TOKEN.code); 105 | 106 | Book book = bookDao.findOne(1L); 107 | assertThat(book.borrower).isNull(); 108 | 109 | // 设置错误token 110 | response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 1L, "abc"); 111 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); 112 | errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class); 113 | assertThat(errorResult.code).isEqualTo(ErrorCode.UNAUTHORIZED.code); 114 | 115 | book = bookDao.findOne(1L); 116 | assertThat(book.borrower).isNull(); 117 | 118 | // 自己借自己的书 119 | String token = login("calvin.xiao@springside.io"); 120 | 121 | response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 1L, token); 122 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); 123 | errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class); 124 | assertThat(errorResult.code).isEqualTo(ErrorCode.BOOK_OWNERSHIP_WRONG.code); 125 | 126 | book = bookDao.findOne(1L); 127 | assertThat(book.borrower).isNull(); 128 | 129 | logout(token); 130 | 131 | // 借一本已被申请借出的书 132 | token = login("calvin.xiao@springside.io"); 133 | 134 | response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 3L, token); 135 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 136 | 137 | response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 3L, token); 138 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); 139 | errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class); 140 | assertThat(errorResult.code).isEqualTo(ErrorCode.BOOK_STATUS_WRONG.code); 141 | 142 | // 回退操作 143 | response = restTemplate.getForEntity(resourceUrl + "/{id}/cancel?token={token}", String.class, 3L, token); 144 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 145 | 146 | logout(token); 147 | } 148 | 149 | @Test 150 | public void fullBorrowProcess() { 151 | // 发起请求 152 | String token = login("david.wang@springside.io"); 153 | 154 | ResponseEntity response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", 155 | String.class, 1L, token); 156 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 157 | 158 | logout(token); 159 | 160 | // 确认借出 161 | token = login("calvin.xiao@springside.io"); 162 | 163 | response = restTemplate.getForEntity(resourceUrl + "/{id}/confirm?token={token}", String.class, 1L, token); 164 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 165 | 166 | // 确认归还 167 | response = restTemplate.getForEntity(resourceUrl + "/{id}/return?token={token}", String.class, 1L, token); 168 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 169 | 170 | logout(token); 171 | } 172 | 173 | private String login(String user) { 174 | Map map = Maps.newHashMap(); 175 | map.put("email", user); 176 | map.put("password", "springside"); 177 | 178 | ResponseEntity response = restTemplate.getForEntity(loginUrl + "?email={email}&password={password}", 179 | Map.class, map); 180 | assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); 181 | return (String) response.getBody().get("token"); 182 | } 183 | 184 | public void logout(String token) { 185 | restTemplate.getForEntity(logoutUrl + "?token={token}", String.class, token); 186 | } 187 | 188 | private static BookDto randomBook() { 189 | BookDto book = new BookDto(); 190 | book.title = RandomData.randomName("Book"); 191 | 192 | return book; 193 | } 194 | 195 | // ArrayList在RestTemplate转换时不好表示,创建一个类来表达它是最简单的。 196 | private static class BookList extends ArrayList { 197 | } 198 | 199 | } 200 | -------------------------------------------------------------------------------- /modules/utils/src/main/java/org/springside/modules/utils/Reflections.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005, 2014 springside.github.io 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | *******************************************************************************/ 6 | package org.springside.modules.utils; 7 | 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.lang.reflect.Method; 11 | import java.lang.reflect.Modifier; 12 | import java.lang.reflect.ParameterizedType; 13 | import java.lang.reflect.Type; 14 | 15 | import org.apache.commons.lang3.StringUtils; 16 | import org.apache.commons.lang3.Validate; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | /** 21 | * 反射工具类. 22 | * 23 | * 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. 24 | * 25 | * @author calvin 26 | */ 27 | public class Reflections { 28 | private static final String SETTER_PREFIX = "set"; 29 | 30 | private static final String GETTER_PREFIX = "get"; 31 | 32 | private static final String CGLIB_CLASS_SEPARATOR = "$$"; 33 | 34 | private static Logger logger = LoggerFactory.getLogger(Reflections.class); 35 | 36 | /** 37 | * 调用Getter方法. 38 | */ 39 | public static Object invokeGetter(Object obj, String propertyName) { 40 | String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(propertyName); 41 | return invokeMethod(obj, getterMethodName, new Class[] {}, new Object[] {}); 42 | } 43 | 44 | /** 45 | * 调用Setter方法, 仅匹配方法名。 46 | */ 47 | public static void invokeSetter(Object obj, String propertyName, Object value) { 48 | String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(propertyName); 49 | invokeMethodByName(obj, setterMethodName, new Object[] { value }); 50 | } 51 | 52 | /** 53 | * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. 54 | */ 55 | public static Object getFieldValue(final Object obj, final String fieldName) { 56 | Field field = getAccessibleField(obj, fieldName); 57 | 58 | if (field == null) { 59 | throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]"); 60 | } 61 | 62 | Object result = null; 63 | try { 64 | result = field.get(obj); 65 | } catch (IllegalAccessException e) { 66 | logger.error("不可能抛出的异常{}", e.getMessage()); 67 | } 68 | return result; 69 | } 70 | 71 | /** 72 | * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. 73 | */ 74 | public static void setFieldValue(final Object obj, final String fieldName, final Object value) { 75 | Field field = getAccessibleField(obj, fieldName); 76 | 77 | if (field == null) { 78 | throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]"); 79 | } 80 | 81 | try { 82 | field.set(obj, value); 83 | } catch (IllegalAccessException e) { 84 | logger.error("不可能抛出的异常:{}", e.getMessage()); 85 | } 86 | } 87 | 88 | /** 89 | * 直接调用对象方法, 无视private/protected修饰符. 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. 同时匹配方法名+参数类型, 90 | */ 91 | public static Object invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, 92 | final Object[] args) { 93 | Method method = getAccessibleMethod(obj, methodName, parameterTypes); 94 | if (method == null) { 95 | throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); 96 | } 97 | 98 | try { 99 | return method.invoke(obj, args); 100 | } catch (Exception e) { 101 | throw convertReflectionExceptionToUnchecked(e); 102 | } 103 | } 104 | 105 | /** 106 | * 直接调用对象方法, 无视private/protected修饰符, 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. 107 | * 只匹配函数名,如果有多个同名函数调用第一个。 108 | */ 109 | public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) { 110 | Method method = getAccessibleMethodByName(obj, methodName); 111 | if (method == null) { 112 | throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]"); 113 | } 114 | 115 | try { 116 | return method.invoke(obj, args); 117 | } catch (Exception e) { 118 | throw convertReflectionExceptionToUnchecked(e); 119 | } 120 | } 121 | 122 | /** 123 | * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. 124 | * 125 | * 如向上转型到Object仍无法找到, 返回null. 126 | */ 127 | public static Field getAccessibleField(final Object obj, final String fieldName) { 128 | Validate.notNull(obj, "object can't be null"); 129 | Validate.notBlank(fieldName, "fieldName can't be blank"); 130 | for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass 131 | .getSuperclass()) { 132 | try { 133 | Field field = superClass.getDeclaredField(fieldName); 134 | makeAccessible(field); 135 | return field; 136 | } catch (NoSuchFieldException e) {// NOSONAR 137 | // Field不在当前类定义,继续向上转型 138 | } 139 | } 140 | return null; 141 | } 142 | 143 | /** 144 | * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. 如向上转型到Object仍无法找到, 返回null. 匹配函数名+参数类型。 145 | * 146 | * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) 147 | */ 148 | public static Method getAccessibleMethod(final Object obj, final String methodName, 149 | final Class... parameterTypes) { 150 | Validate.notNull(obj, "object can't be null"); 151 | Validate.notBlank(methodName, "methodName can't be blank"); 152 | 153 | for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType 154 | .getSuperclass()) { 155 | try { 156 | Method method = searchType.getDeclaredMethod(methodName, parameterTypes); 157 | makeAccessible(method); 158 | return method; 159 | } catch (NoSuchMethodException e) { 160 | // Method不在当前类定义,继续向上转型 161 | } 162 | } 163 | return null; 164 | } 165 | 166 | /** 167 | * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. 如向上转型到Object仍无法找到, 返回null. 只匹配函数名。 168 | * 169 | * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) 170 | */ 171 | public static Method getAccessibleMethodByName(final Object obj, final String methodName) { 172 | Validate.notNull(obj, "object can't be null"); 173 | Validate.notBlank(methodName, "methodName can't be blank"); 174 | 175 | for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType 176 | .getSuperclass()) { 177 | Method[] methods = searchType.getDeclaredMethods(); 178 | for (Method method : methods) { 179 | if (method.getName().equals(methodName)) { 180 | makeAccessible(method); 181 | return method; 182 | } 183 | } 184 | } 185 | return null; 186 | } 187 | 188 | /** 189 | * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 190 | */ 191 | public static void makeAccessible(Method method) { 192 | if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) 193 | && !method.isAccessible()) { 194 | method.setAccessible(true); 195 | } 196 | } 197 | 198 | /** 199 | * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 200 | */ 201 | public static void makeAccessible(Field field) { 202 | if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) 203 | || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { 204 | field.setAccessible(true); 205 | } 206 | } 207 | 208 | /** 209 | * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 如无法找到, 返回Object.class. eg. public UserDao extends HibernateDao 210 | * 211 | * @param clazz The class to introspect 212 | * @return the first generic declaration, or Object.class if cannot be determined 213 | */ 214 | public static Class getClassGenricType(final Class clazz) { 215 | return getClassGenricType(clazz, 0); 216 | } 217 | 218 | /** 219 | * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. 如无法找到, 返回Object.class. 220 | * 221 | * 如public UserDao extends HibernateDao 222 | * 223 | * @param clazz clazz The class to introspect 224 | * @param index the Index of the generic ddeclaration,start from 0. 225 | * @return the index generic declaration, or Object.class if cannot be determined 226 | */ 227 | public static Class getClassGenricType(final Class clazz, final int index) { 228 | 229 | Type genType = clazz.getGenericSuperclass(); 230 | 231 | if (!(genType instanceof ParameterizedType)) { 232 | logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType"); 233 | return Object.class; 234 | } 235 | 236 | Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); 237 | 238 | if ((index >= params.length) || (index < 0)) { 239 | logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " 240 | + params.length); 241 | return Object.class; 242 | } 243 | if (!(params[index] instanceof Class)) { 244 | logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); 245 | return Object.class; 246 | } 247 | 248 | return (Class) params[index]; 249 | } 250 | 251 | /** 252 | * 获取CGLib处理过后的实体的原类. 253 | */ 254 | public static Class getUserClass(Object instance) { 255 | Validate.notNull(instance, "Instance must not be null"); 256 | Class clazz = instance.getClass(); 257 | if ((clazz != null) && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { 258 | Class superClass = clazz.getSuperclass(); 259 | if ((superClass != null) && !Object.class.equals(superClass)) { 260 | return superClass; 261 | } 262 | } 263 | return clazz; 264 | 265 | } 266 | 267 | /** 268 | * 将反射时的checked exception转换为unchecked exception. 269 | */ 270 | public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) { 271 | if ((e instanceof IllegalAccessException) || (e instanceof IllegalArgumentException) 272 | || (e instanceof NoSuchMethodException)) { 273 | return new IllegalArgumentException(e); 274 | } else if (e instanceof InvocationTargetException) { 275 | return new RuntimeException(((InvocationTargetException) e).getTargetException()); 276 | } else if (e instanceof RuntimeException) { 277 | return (RuntimeException) e; 278 | } 279 | return new RuntimeException("Unexpected Checked Exception.", e); 280 | } 281 | } 282 | --------------------------------------------------------------------------------