- 投稿日:2020-06-28T22:40:44+09:00
SpringのFilterでHttpResponseのBody部を取得する
いい感じの凡例が見つからなかったので作ってみました。
STSのデバック環境下で動作確認済みです。LoggingFilter.java@Component public class LoggingFilter implements Filter { private static final Logger log = LoggerFactory.getLogger(LoggingFilter.class.getName()); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponseWrapperEx httpResponseEx = new HttpServletResponseWrapperEx(httpResponse); chain.doFilter(request, httpResponseEx); String responseBody = httpResponseEx.getBody(); log.debug(responseBody); } }HttpServletResponseWrapperEx.javapublic class HttpServletResponseWrapperEx extends HttpServletResponseWrapper { private ServletResponse response; private PrintWriterEx writerEx; private ServletOutputStreamEx outputStreamEx; public HttpServletResponseWrapperEx(HttpServletResponse response) { super(response); this.response = response; } @Override public ServletOutputStream getOutputStream() throws IOException { ServletOutputStream stream = this.response.getOutputStream(); if (null == this.outputStreamEx) { this.outputStreamEx = new ServletOutputStreamEx(stream); } return this.outputStreamEx; } @Override public PrintWriter getWriter() throws IOException { PrintWriter writer = this.response.getWriter(); this.writerEx = new PrintWriterEx(writer); return this.writerEx; } public String getBody() { String result = ""; if (null != this.outputStreamEx) { result = this.outputStreamEx.getBody(); } else if (null != this.writerEx) { result = this.writerEx.getBody(); } return result; } }ServletOutputStreamEx.javapublic class ServletOutputStreamEx extends ServletOutputStream { private StringBuilder body = new StringBuilder(); private ServletOutputStream target; public ServletOutputStreamEx(ServletOutputStream target) { this.target = target; } @Override public boolean isReady() { return this.target.isReady(); } @Override public void setWriteListener(WriteListener listener) { this.target.setWriteListener(listener); } @Override public void write(int b) throws IOException { this.target.write(b); byte [] value = new byte[] {(byte)b}; String temp = new String(value); body.append(temp); } public String getBody() { return this.body.toString(); } }PrintWriterEx.javapublic class PrintWriterEx extends PrintWriter { private StringBuilder body = new StringBuilder(); public PrintWriterEx(Writer out) { super(out); } @Override public void write(int c) { super.write(c); this.body.append((char)c); } @Override public void write(char[] chars, int offset, int length) { super.write(chars, offset, length); this.body.append(chars, offset, length); } public void write(String string, int offset, int length) { super.write(string, offset, length); this.body.append(string, offset, length); } public String getBody() { return this.body.toString(); } }適当にサンプルを作って動作を確認。
下記サンプルの場合、PrintWriterExが動作した。HelloController.java@Controller public class HeloController{ @RequestMapping(value = "/", method = RequestMethod.GET) public String index(Model model) { model.addAttribute("message", "Hello Springboot"); return "index"; } }下記サンプルの場合、GET/POSTのどちらの場合もServletOutputStreamExが動作した。
GoodbyeController.java@RestController public class GoodbyeController { @RequestMapping(path = "/goodbye", method = RequestMethod.GET) public String goodbye() { return "good bye"; } @RequestMapping(path = "/goodbyebye", method = RequestMethod.POST) public String goodbyebye() { return "{ message: \"goodbye\" }"; } }
- 投稿日:2020-06-28T22:10:30+09:00
Spring Cloud FunctionでAmazon API Gatewayのプロキシ統合なLambda関数をサクッと書いてみる
はじめに
Lambda関数をJavaで実装するのは面倒くさい。
Spring Cloud Functionというのを使うと、その辺の煩雑さを良い感じにフレームワークが吸収してくれるらしい。
ということで、本当に良い感じに吸収されてサクッと書けるのかを試してみた。ちなみに、Spring Cloud Functionはプロキシ統合していないサンプルは多くあれど、統合版はなかなか見つからなかったので、その検討の一助にもなれば。
構成
以下のようなMavenプロジェクトを作成する。
SpringCloudFunctionTest ├── pom.xml ├── serverless.yml └── src ├── main │ ├── java │ │ └── com │ │ └── springcloudfunctiontest │ │ ├── AWSLambdaHandler.java │ │ ├── Hello.java │ │ └── SpringCloudFunctionExampleApplication.java │ └── resources │ └── application.properties └── test └── java └── com └── springcloudfunctiontest └── SpringCloudFunctionExampleApplicationTests.javapom.xmlは↓こんな感じにしておく。
pom.xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.techprimers.serverless</groupId> <artifactId>spring-cloud-function-test</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud-function-test</name> <description>Demo project for Spring Boot with Spring Cloud Function</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR2</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-function-adapter-aws</artifactId> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-events</artifactId> <version>2.0.2</version> </dependency> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> <version>1.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot.experimental</groupId> <artifactId>spring-boot-thin-layout</artifactId> <version>1.0.10.RELEASE</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> <shadedArtifactAttached>true</shadedArtifactAttached> <shadedClassifierName>aws</shadedClassifierName> </configuration> </plugin> </plugins> </build> </project>ハンドラのコード
通常のSpring Bootなアプリ同様、
SpringApplication.run
するクラスを作る。SpringCloudFunctionExampleApplication.javapackage com.springcloudfunctiontest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringCloudFunctionExampleApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudFunctionExampleApplication.class, args); } }で、Lambdaのイベントハンドラを作る。
正直、このクラスが空っぽで良い理由がよく分からない……。
API Gatewayのバックエンドに置くので、InputとOutputにはそれぞれAPIGatewayProxyRequestEvent
とAPIGatewayProxyResponseEvent
を設定する。AWSLambdaHandler.javapackage com.springcloudfunctiontest; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler; public class AWSLambdaHandler extends SpringBootRequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { }最後に、ビジネスロジックのクラスを作成する。
APIGatewayProxyRequestEvent
と
APIGatewayProxyResponseEvent
`の仕様はjavadocを確認しよう。しかし、Spring Cloud Functionはマルチクラウドでの互換性がウリなのに、こんなインタフェースにしたら完全にAWS特化な実装になっちゃうよなぁ……。
最後のsetBodyのJSONの扱いが雑なのは気にしないように。
Hello.javapackage com.springcloudfunctiontest; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; import org.springframework.stereotype.Component; import java.util.function.Function; import java.util.Map; @Component public class Hello implements Function<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> { @Override public APIGatewayProxyResponseEvent apply(APIGatewayProxyRequestEvent input) { Map<String, String> queryStringParameter = input.getQueryStringParameters(); String id = queryStringParameter.get("id"); String name = null; if( id.equals("11111") ) { name = "\"Taro\""; } else { name = "\"Nanashi\""; } APIGatewayProxyResponseEvent responseEvent = new APIGatewayProxyResponseEvent(); responseEvent.setStatusCode(200); responseEvent.setBody("{\"name\":" + name + "}"); return responseEvent; } }テストコード
テストコードも普通に書ける。
SpringCloudFunctionExampleApplicationTests.javapackage com.springcloudfunctiontest; import java.util.HashMap; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; @RunWith(SpringRunner.class) @SpringBootTest public class SpringCloudFunctionExampleApplicationTests { @Test public void HelloTest1() { Hello hello = new Hello(); APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); Map<String,String> queryStringParameter = new HashMap<>(); queryStringParameter.put("id", "11111"); request.setQueryStringParameters(queryStringParameter); APIGatewayProxyResponseEvent response = hello.apply(request); assertThat(response.getBody()).isEqualTo("{\"name\":\"Taro\"}"); } @Test public void HelloTest2() { Hello hello = new Hello(); APIGatewayProxyRequestEvent request = new APIGatewayProxyRequestEvent(); Map<String,String> queryStringParameter = new HashMap<>(); queryStringParameter.put("id", "22222"); request.setQueryStringParameters(queryStringParameter); APIGatewayProxyResponseEvent response = hello.apply(request); assertThat(response.getBody()).isEqualTo("{\"name\":\"Nanashi\"}"); } }デプロイ
手でいちいちS3にアップロードしたり、パイプラインのビルド待ちしたりするのも面倒臭いので、正式版のアプリでないからServerless Frameworkでテキトーにデプロイしよう。
serverless.ymlservice: test provider: name: aws runtime: java8 timeout: 30 region: ap-northeast-1 package: artifact: target/spring-cloud-function-test-0.0.1-SNAPSHOT-aws.jar functions: hello: handler: org.springframework.cloud.function.adapter.aws.SpringBootStreamHandler events: - http: path: testapi method: get integration: lambda-proxy request: template: application/json: '$input.json("$")'これでデプロイすると、こんな感じでちゃんとRESTAPIが返されるぞ!
$ curl -X GET https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/testapi?id=11111; echo {"name":"Taro"}結論
うーん、Springフレームワークの恩恵を全然預かっていないので、普通のLambdaのイベントハンドラを作った時と手間としては変わらなかった気分。もっとフレームワークの機能をふんだんに使うようになったらありがたみがわかるのだろうか。
今回は、JavaでLambda関数を書くならやっぱりAPIGatewayProxyRequestEventを使うと実装が楽だね、ということと、Serverless Frameworkのデプロイがお手軽だね、と言うことは理解できた。
- 投稿日:2020-06-28T22:07:55+09:00
JAVAオブジェクトマッピングライブラリ
はじめに
Beanの変換に詰め替え作業は手動で実施するとコード量は増えてしまい、メンテナンス性は良くないです。
BeanUtils.copyPropertiesのような便利メソッドがありますが、ほかのマッピングライブラリをまとめてみます。FasterXML ObjectMapper
https://github.com/FasterXML/jackson-databind
JSON関連する処理はこれを使うと便利です。
使い方
build.gradleに追加
build.gradle// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'Beanクラス作成
package com.test.lombok; import lombok.Getter; import lombok.Setter; import lombok.ToString; @Getter @Setter @ToString public class User { private String userId; private String userName; private String age; private String gender; }user.jsonサンプル作成
user.json{ "userId": "user001", "userName": "テスト 太郎", "age": 20, "gender": "famale" }テストコード
package com.test.lombok; import java.io.File; import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; public class TestMain { public static void main(String[] args) throws Exception { ObjectMapper mapper = new ObjectMapper(); // ファイルパスで指定 User user1 = mapper.readValue( new File("C:\\workspace_sts\\lombok\\src\\main\\java\\com\\test\\lombok\\user.json"), User.class); System.out.println(user1); // URL形式で指定 User user2 = mapper.readValue(TestMain.class.getResource("user.json"), User.class); System.out.println(user2); // 文字列から変換 User user3 = mapper.readValue( "{\"userId\":\"user001\", \"userName\": \"テスト 太郎2\", \"age\": 22, \"gender\": \"male\"}", User.class); System.out.println(user3); // オブジェクトから文字列に変換 String jsonString = mapper.writeValueAsString(user3); System.out.println(jsonString); // 文字列からMapに変換 Map<String, String> userMap = mapper.readValue(jsonString, Map.class); System.out.println(userMap); } }結果:
User(userId=user001, userName=テスト 太郎, age=20, gender=famale)
User(userId=user001, userName=テスト 太郎, age=20, gender=famale)
User(userId=user001, userName=テスト 太郎2, age=22, gender=male)
{"userId":"user001","userName":"テスト 太郎2","age":"22","gender":"male"}
{userId=user001, userName=テスト 太郎2, age=22, gender=male}ModelMapper
使い方
build.gradleに追加
build.gradle// https://mvnrepository.com/artifact/org.modelmapper/modelmapper compile group: 'org.modelmapper', name: 'modelmapper', version: '2.3.5'元Beanクラス
package com.test.lombok; import lombok.Data; @Data public class User { private String userId; private String userName; private String age; private String gender; private Address addr; } @Data class Address { private String zipCode; private String address1; private String address2; private String address3; }DTOクラス
package com.test.lombok; import lombok.Getter; import lombok.Setter; import lombok.ToString; @Getter @Setter @ToString public class UserDTO { private String userId; private String userName; private String age; private String gender; private String addrZipCode; private String addrAddress1; private String addrAddress2; private String addrAddress3; }テストコード
package com.test.lombok; import org.modelmapper.ModelMapper; public class TestMain { public static void main(String[] args) throws Exception { ModelMapper modelMapper = new ModelMapper(); User user = new User(); user.setUserId("user001"); user.setUserName("テスト"); user.setAge("18"); user.setGender("male"); Address addr = new Address(); addr.setZipCode("984-0031"); addr.setAddress1("○○県"); user.setAddr(addr ); UserDTO userDTO = modelMapper.map(user, UserDTO.class); System.out.println(userDTO); } }結果:
UserDTO(userId=user001, userName=テスト, age=18, gender=male, addrZipCode=984-0031, addrAddress1=○○県, addrAddress2=null, addrAddress3=null)
明示的にマッピング(Explicit Mapping)
package com.test.lombok; import org.modelmapper.ModelMapper; import org.modelmapper.TypeMap; public class TestMain { public static void main(String[] args) throws Exception { ModelMapper modelMapper = new ModelMapper(); User user = new User(); user.setUserId("user001"); user.setUserName("テスト"); user.setAge("18"); user.setGender("male"); Address addr = new Address(); addr.setZipCode("984-0031"); addr.setAddress1("○○県"); user.setAddr(addr); TypeMap<User, UserDTO> typeMap = modelMapper.typeMap(User.class, UserDTO.class).addMappings(mapper -> { mapper.map(src -> src.getAddr().getZipCode(), UserDTO::setAddrZipCode); mapper.map(src -> src.getAddr().getAddress1(), UserDTO::setAddrAddress1); }); System.out.println(typeMap.map(user)); } }結果:
UserDTO(userId=user001, userName=テスト, age=18, gender=male, addrZipCode=984-0031, addrAddress1=○○県, addrAddress2=null, addrAddress3=null)
SpringBootのControllerにてFormのBeanから自分のDTOに変換するに使うと便利です。
MapStruct
使い方
build.gradleに追加
build.gradleplugins { id 'java' id 'net.ltgt.apt' version '0.20' } repositories { jcenter() } dependencies { // lombok compileOnly "org.projectlombok:lombok:1.18.12" annotationProcessor "org.projectlombok:lombok:1.18.12" // https://mvnrepository.com/artifact/org.mapstruct/mapstruct implementation group: 'org.mapstruct', name: 'mapstruct', version: '1.3.1.Final' // https://mvnrepository.com/artifact/org.mapstruct/mapstruct-processor annotationProcessor group: 'org.mapstruct', name: 'mapstruct-processor', version: '1.3.1.Final' } [compileJava, compileTestJava]*.options*.encoding= "UTF-8" sourceSets { main.java.srcDirs += "build/generated/sources/annotationProcessor/java/main" }Mapperインタフェース
package com.test.lombok; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; @Mapper public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); @Mapping(source = "addr.zipCode", target = "addrZipCode") @Mapping(source = "addr.address1", target = "addrAddress1") @Mapping(source = "addr.address2", target = "addrAddress2") UserDTO userToUserDTO(User user); }上記定義し、ビルドするとbuild\generated\sources\annotationProcessor\java\main\com\test\lombokにUserMapperImpl.javaクラスが生成されます。
UserMapperImpl.javapackage com.test.lombok; import javax.annotation.Generated; @Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-06-28T21:47:47+0900", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)" ) public class UserMapperImpl implements UserMapper { @Override public UserDTO userToUserDTO(User user) { if ( user == null ) { return null; } UserDTO userDTO = new UserDTO(); userDTO.setAddrAddress2( userAddrAddress2( user ) ); userDTO.setAddrAddress1( userAddrAddress1( user ) ); userDTO.setAddrZipCode( userAddrZipCode( user ) ); userDTO.setUserId( user.getUserId() ); userDTO.setUserName( user.getUserName() ); userDTO.setAge( user.getAge() ); userDTO.setGender( user.getGender() ); return userDTO; } private String userAddrAddress2(User user) { if ( user == null ) { return null; } Address addr = user.getAddr(); if ( addr == null ) { return null; } String address2 = addr.getAddress2(); if ( address2 == null ) { return null; } return address2; } private String userAddrAddress1(User user) { if ( user == null ) { return null; } Address addr = user.getAddr(); if ( addr == null ) { return null; } String address1 = addr.getAddress1(); if ( address1 == null ) { return null; } return address1; } private String userAddrZipCode(User user) { if ( user == null ) { return null; } Address addr = user.getAddr(); if ( addr == null ) { return null; } String zipCode = addr.getZipCode(); if ( zipCode == null ) { return null; } return zipCode; } }テストコード
package com.test.lombok; public class TestMain { public static void main(String[] args) throws Exception { User user = new User(); user.setUserId("user001"); user.setUserName("テスト"); user.setAge("18"); user.setGender("male"); Address addr = new Address(); addr.setZipCode("984-0031"); addr.setAddress1("○○県"); user.setAddr(addr); UserDTO userDTO = UserMapper.INSTANCE.userToUserDTO(user); System.out.println(userDTO); } }結果:
UserDTO(userId=user001, userName=テスト, age=18, gender=male, addrZipCode=984-0031, addrAddress1=○○県, addrAddress2=null, addrAddress3=null)
ClassNotFoundエラー
Exception in thread "main" java.lang.ExceptionInInitializerError at com.test.lombok.TestMain.main(TestMain.java:18) Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: Cannot find implementation for com.test.lombok.UserMapper at org.mapstruct.factory.Mappers.getMapper(Mappers.java:61) at com.test.lombok.UserMapper.<clinit>(UserMapper.java:9) ... 1 more Caused by: java.lang.ClassNotFoundException: Cannot find implementation for com.test.lombok.UserMapper at org.mapstruct.factory.Mappers.getMapper(Mappers.java:75) at org.mapstruct.factory.Mappers.getMapper(Mappers.java:58)build.gradleにsourceSetsを追加
build.gradlesourceSets { main.java.srcDirs += "build/generated/sources/annotationProcessor/java/main" }詳細Doc:https://mapstruct.org/documentation/stable/reference/html/
Dozer
https://github.com/DozerMapper/dozer
使い方
build.gradleに追加
build.gradle// https://mvnrepository.com/artifact/com.github.dozermapper/dozer-core compile group: 'com.github.dozermapper', name: 'dozer-core', version: '6.5.0'テストコード
package com.test.lombok; import org.modelmapper.ModelMapper; import org.modelmapper.TypeMap; import com.github.dozermapper.core.DozerBeanMapperBuilder; import com.github.dozermapper.core.Mapper; public class TestMain { public static void main(String[] args) throws Exception { User user = new User(); user.setUserId("user001"); user.setUserName("テスト"); user.setAge("18"); user.setGender("male"); Address addr = new Address(); addr.setZipCode("984-0031"); addr.setAddress1("○○県"); user.setAddr(addr); Mapper mapper = DozerBeanMapperBuilder.buildDefault(); UserDTO userDTO = mapper.map(user, UserDTO.class); System.out.println(userDTO); } }結果:
UserDTO(userId=user001, userName=テスト, age=18, gender=male, addrZipCode=null, addrAddress1=null, addrAddress2=null, addrAddress3=null)
同じ項目名はコピーされましたが、それ以外nullでした。
違う項目の場合はXML、API、アノテーションで定義して対応可能
違う項目名の場合は
@Mapping("binaryData")
のように設定で対応可能ですが、複雑のタイプの場合はXMLで設定する必要があります。好きなライブラリを選択すればと思います。
以上
- 投稿日:2020-06-28T21:27:59+09:00
【Java】enumを使ってDBのステータスコードをコード値変換する
DBに登録されたステータスコードをJava側でコード値に変換するサンプルです。
これにより、ステータスコードの意味を定義するマスタを作成する必要がなくなります。
フレームワークはSpring Boot。環境
- OS:Windows10
- IDE:Eclipse2020-03
- Java:8
- MySQL:5.7
- Hibernate ORM:5.4
- Spring Boot:2.3.1
概要
生徒テーブルに登録された生徒ステータスコード(整数値)をJava側でenumを用いて対応するコード値に変換します。
ER図
ステータスコードとコード値
ステータスコード コード値 1 在学中 2 休学中 3 卒業 パッケージ構成図
enum-sample | src/main/java |----jp.co.high_school | |---- app | | |---- controller | | |---- response | | | |---- domain | | |---- service | | |---- repository | | |---- model | | | |---- util | src/main/resources |----application.ymlソースコード
1. DDL
生徒テーブルCREATE TABLE `students` ( `student_id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '生徒ID', `student_name` varchar(300) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '生徒名', `sex` tinyint(4) DEFAULT NULL COMMENT '性別', `school_year` tinyint(4) DEFAULT NULL COMMENT '学年', `class` tinyint(4) DEFAULT NULL COMMENT 'クラス', `student_status` tinyint(4) DEFAULT NULL COMMENT '生徒ステータス', PRIMARY KEY (`student_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='生徒';2. 定数クラス
定数クラスにenumを定義して使用します。
クラスとしてenumを作成してもよいのですが、数が多くなることを想定してクラス内に定義しました。
こうすることで1クラスファイルで複数のenumを管理することができます。ConstUtil.javapackage jp.co.test_shop.util; import java.util.stream.Stream; import lombok.Getter; /** * 定数クラス * @author CHI-3 * @Version 1.0 */ public class ConstUtil { /** * StudentStatus * 生徒ステータス */ @Getter public static enum StudentStatus { In(1, "在学中"), Rest(2, "休学中"), Out(3, "卒業"); private StudentStatus(int key, String value) { this.key = key; this.value = value; } private int key; private String value; public static Stream<StudentStatus> stream(){ return Stream.of(StudentStatus.values()); } } // 今回は使用しない:1クラス内に複数enum定義できることの証明として記述 /** * Grade * 成績 */ @Getter public static enum Grade{ // 優、良、可は最低条件-sam、不可は最高条件 Excellent(1, "優", 90), Good(2, "良", 75), Passing(3, "可", 60), Failing(4, "不可", 59); private Grade(int key, String value, int order) { this.key = key; this.value = value; this.order = order; } private int key; private String value; private int order; } }3. Entityクラス
今回は、EclipseのHibernate Toolsを用いて、DBより自動生成しました。
Student.javapackage jp.co.test_shop.domain.model; import java.io.Serializable; import javax.persistence.*; /** * The persistent class for the students database table. * */ @Entity @Table(name="students") @NamedQuery(name="Student.findAll", query="SELECT s FROM Student s") public class Student implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name="student_id") private Integer studentId; @Column(name="class") private Byte class_; @Column(name="school_year") private Byte schoolYear; private Byte sex; @Column(name="student_name") private String studentName; @Column(name="student_status") private Byte studentStatus; //bi-directional one-to-one association to Grade @OneToOne(mappedBy="student") private Grade grade; public Student() { } public Integer getStudentId() { return this.studentId; } public void setStudentId(Integer studentId) { this.studentId = studentId; } public Byte getClass_() { return this.class_; } public void setClass_(Byte class_) { this.class_ = class_; } public Byte getSchoolYear() { return this.schoolYear; } public void setSchoolYear(Byte schoolYear) { this.schoolYear = schoolYear; } public Byte getSex() { return this.sex; } public void setSex(Byte sex) { this.sex = sex; } public String getStudentName() { return this.studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } public Byte getStudentStatus() { return this.studentStatus; } public void setStudentStatus(Byte studentStatus) { this.studentStatus = studentStatus; } public Grade getGrade() { return this.grade; } public void setGrade(Grade grade) { this.grade = grade; } }4. Repositoryクラス
生徒IDの指定により、該当する生徒の情報を取得します。
StudentRepository.javapackage jp.co.test_shop.domain.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import jp.co.test_shop.domain.model.Student; /** * 生徒(students)テーブル操作用リポジトリクラス * @author CHI-3 * @Version 1.0 */ @Repository public interface StudentRepository extends JpaRepository<Student, Integer>{ /** * findByStudentIdメソッド * 生徒IDに対応する生徒を取得する * @param studentId * @return 生徒 */ public Student findByStudentId(Integer studentId); }5. Serviceクラス
取得した生徒の、生徒ステータスのコードを対応するステータスコードに変換して返します。
StudentInformationService.javapackage jp.co.high_school.domain.service; import org.springframework.stereotype.Service; import jp.co.test_shop.domain.model.Student; import jp.co.test_shop.domain.repository.StudentRepository; import jp.co.test_shop.util.ConstUtil.StudentStatus; import lombok.RequiredArgsConstructor; /** * 生徒情報取得用サービスクラス * @author CHI-3 * @version 1.0 */ @Service @RequiredArgsConstructor public class StudentInformationService { private final StudentRepository studentRepository; /** * getStatusメソッド * 対象の生徒の生徒ステータス(コード値)を取得する * @param studentId 生徒ID * @return 生徒ステータス(コード値) */ public String getStatus(Integer studentId) { Student student = studentRepository.findByStudentId(studentId); String status = null; // ステータスコード → 対応するコード値 for(StudentStatus ss:StudentStatus.values()){ if(ss.getKey() == student.getStudentStatus()) { status = ss.getValue(); break; } } return status; } }6. Controllerクラス
Serviceクラスで取得したコード値を整形して返します。
StudentInformationController.javapackage jp.co.high_school.app.controller; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import jp.co.test_shop.app.response.StudentStatusResponse; import jp.co.test_shop.domain.service.StudentInformationService; import lombok.RequiredArgsConstructor; /** * 生徒情報取得用コントローラークラス * @author CHI-3 * @Version 1.0 */ @RestController @RequiredArgsConstructor public class StudentInformationController { private final StudentInformationService studenInformationService; /** * getStatusメソッド * 生徒ステータスを取得する * @param studentId 生徒ID * @return レスポンスエンティティ(ステータスのコード値+HTTPステータス) */ @GetMapping("/student-information/{studentId}") public ResponseEntity<StudentStatusResponse> getStatus(@PathVariable("studentId") Integer studentId){ String status = studenInformationService.getStatus(studentId); StudentStatusResponse studentStatusReponse = StudentStatusResponse.builder().status(status).build(); return new ResponseEntity<>(studentStatusReponse, HttpStatus.OK); } }7. Responseクラス
StudentStatusResponse.javapackage jp.co.test_shop.app.response; import lombok.Builder; import lombok.Getter; /** * 生徒ステータスレスポンスクラス * 生徒ステータスの成形に使用 * @author CHI-3 * @Version 1.0 */ @Getter @Builder public class StudentStatusResponse { private String status; }動作確認
今回はローカル環境で動作確認を行います。
生徒(students)テーブル
student_id student_name sex school_year class student_status 1 在学中 1 1 2 1 生徒テーブルが上記の内容の場合、API(Advanced Rest Clientを使用)を叩くと以下のような結果が返ります。
きちんと、ステータスコード(1)に対応するコード値(在学中)がとれていますね。
メソッド GET URL http://localhost:8080/student-information/1 終わりに
enumを用いれば、マスタ(DB)や定数(Java)の管理がかなり楽になりそう。
うまく使っていきたいところです。参考
- 投稿日:2020-06-28T20:01:12+09:00
今更Spring framework+JettyでHelloWorld Webアプリを作る
タイトル通りですが、10年ぶりにJavaを仕事で触る機会ができたので、今のうちに自習します。
稼働中の既存システムの勉強なので、最新の技術スタックではなく、ちょっと古めの組み合わせです。Webアプリはデプロイせず、ローカルで実行するだけ。
使うツールと筆者の知識レベル。
- Mac book Pro
- 使い始めて半年くらい。私生活はほぼWindows。
- Java
- 約10年ぶりに触る。3年くらい触ってた。当時はちょうどJava8が発表されたくらいか。
- Maven
- 大昔にちょっとだけ触ったことがある。当時のメインはAntだった。今はGradleの方が主流?
- Spring framework
- 名前は知ってる。当時はStrutsも多かったが、今はSpring bootなのだろう。
- IntelliJ IDEA Community
- 名前は知ってる。当時はEclipseしか使っていなかった。
- Jetty Server
- JavaはTomcat以外で使ったことがなかった
環境準備
Javaは以前に入れてあったので、そのまま使う。大人の事情で14でも11でもなく、Java8。
$ java -version java version "1.8.0_221" Java(TM) SE Runtime Environment (build 1.8.0_221-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)とりあえずmacなので、brewでMavenを入れる。
$ brew install maven (略) $ mvn -v Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) (略) Default locale: ja_JP, platform encoding: UTF-8 OS name: "mac os x", version: "10.14.6", arch: "x86_64", family: "mac"IntelliJのインストール
IntelliJ IDEA Communityもインストール。GUIで入れたので方法は省略。
好みに応じて設定を変える。
ここが参考になった。 https://www.karakaram.com/intellij-idea-always-do-things/自分は基本はそのままで使うスタイルなのだけれど、精神衛生上よろしくないので、Keymapだけ少しいじった(特にC-h,C-d)。
プロジェクト作成
とりあえずGitHubに空のプロジェクトを作って、それをcloneしておく。ここが作業ディレクトリ。
$ git clone https://github.com/sekikatsu36/spring-sample.git $ cd spring-sample $ git config --local user.name "sekikatsu36" $ git config --local user.email "(略)"IntelliJでMavenの新規プロジェクトを作成。特に設定は変更せず、デフォルトのまま。
これで初期状態が完成。
$ ls -a . .DS_Store .gitignore pom.xml src .. .git .idea spring-sample.imlspring frameworkの設定
Webにはspring bootの記事が多く、spring frameworkは少なめ。
とはいえ、今はspring frameworkが必要なので、使えるようにするためにpom.xmlを修正。これが完成形。
pom.xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example.api</groupId> <artifactId>spring-sample</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- pom.xml内でバージョンを変数的に扱えるようにする --> <spring.version>5.2.5.RELEASE</spring.version> <jetty.version>9.4.11.v20180605</jetty.version> </properties> <dependencies> <!-- spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- jetty --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.4.14.v20181114</version> </plugin> </plugins> </build> </project>最初の段階では、
properties
とdependencies
だけ書いてた。それ以外の点については後で追加説明する。参照でエラーが出た場合
自分の環境の場合、
org.springframework.spring-webmvc:5.2.5.RELEASE
が見つからないというエラーが出た。
リポジトリには存在している。以下を実行
mvn clean install
結果
- ログに
Downloaded from central: https://repo.maven.apache.org/maven2/org/springframework/spring-webmvc/5.2.5.RELEASE/spring-webmvc-5.2.5.RELEASE.jar
が表示された/Users/(ユーザ名)/.m2/repository/org/springframework/spring-webmvc/5.2.5.RELEASE
が追加された- IntelliJ ではエラーが表示されたままだったが、Mavenのリロードをしたらエラーが消えた
ソースを書く
基本的に https://qiita.com/kasa_le/items/6aaf17823db67c951fb0 の通りにやればいい。
ただし、こちらの記事はIntelliJ IDEA Ultimate
なので、IDEA Community
ではViewの作成以降はこのまま動かない。とりあえず、コントローラの作成とviewフォルダの作成まではこの通りに終わらせる。
以下ができている状態。/src/main /java/org/example/controllers IndexController.java /webapp/WEB-INF /views web.xml dispatcher-servlet.xmlJSPファイルを作る
Communityエディションの場合、Springサポートがないため、ファイルの新規作成で
JSP/JSPX
が出てこない様子。 https://www.jetbrains.com/ja-jp/idea/features/editions_comparison_matrix.htmlなので、普通に
File
を選んで、全部自作する。index.jsp<%@ page import="org.example.controllers.IndexController" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Spring Test Page</title> </head> <body> <p>${someAttribute}</p> </body> </html>Jettyで起動する
大人の都合でTomcatではなくJettyを使う。
IntelliJ IDEAのUltimate(有償版)であれば、Tomcatがデフォルトで組み込まれているらしい(?)が、Communityなのでどのみち使えない。
Jettyは事前にインストールしておく必要がないので楽。http://zetcode.com/spring/jetty/ を参考に、pom.xmlに以下を足した。
<packaging>war</packaging><dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency><build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.2.2</version> </plugin> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.4.14.v20181114</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build>この状態で
mvn jetty:runを実行し、
http://0.0.0.0:8080
にアクセスすると、無事にHelloWorldが表示される。
maven-compiler-plugin
でソースとターゲットのバージョンを指定しない場合5になるらしく、エラーが起きてしまう。 http://orionb312.hatenablog.com/entry/2018/03/06/233734[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project spring-sample: Compilation failure: Compilation failure: [ERROR] ソース・オプション5は現在サポートされていません。7以降を使用してください。 [ERROR] ターゲット・オプション5は現在サポートされていません。7以降を使用してください。おわり
出来上がったものは以下。
https://github.com/sekikatsu36/spring-sampleとりあえず、Springが動くところは確認できた。
もうちょっとやりたいところ。
- WebPageではないAPIにしたい(Jerseyを使う?)
- デバッグできるようにしたい(今はブレークポイントで止まらない)
- PaaSやSaaSを使いたい
- 投稿日:2020-06-28T17:27:38+09:00
HerokuにBasic認証の導入【Spring Framework】
成果物
準備
↑のHeroku公式ドキュメントにしたがって、Herokuデプロイの準備をすること。
pom.xml・・・ <packaging>war</packaging> ・・・ <dependencies> ・・・ </dependencies> <build> ... <plugins> ... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <phase>package</phase> <goals><goal>copy</goal></goals> <configuration> <artifactItems> <artifactItem> <groupId>com.heroku</groupId> <artifactId>webapp-runner</artifactId> <version>9.0.30.0</version> <destFileName>webapp-runner.jar</destFileName> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> </plugins> </build>※プロジェクトがwarでコンパイルされていること
Procfileweb: java $JAVA_OPTS -jar target/dependency/webapp-runner.jar --port $PORT --enable-basic-auth --basic-auth-user [ユーザー名] --basic-auth-pw [パスワード] target/*.war
- 投稿日:2020-06-28T15:22:45+09:00
DockerをインストールしてJava実行環境をつくる
新しいパソコンでJava実行環境を構築するにあたり、せっかくならDockerでやってみようと思った備忘録
環境
- Windows 10 Home (64bit)
Dockerのインストール
- VirtualBoxとかKitematicとか全部入りの Docker Toolbox [link] をダウンロード
- Setup画面はデフォルト設定でOK、最後の画面で確認し[Install]をクリック
- デスクトップに作成されたDocker Quickstart Terminalのショートカット(>?みたいなやつ)を起動させると色々表示されるので待つ
- 画像のようにクジラの絵が出たら完了
- Dockerターミナルに
docker run hello-world
と入力しEnterを押すと、"hello-world" という名前のDockerイメージをダウンロードしてくれるDockerイメージのダウンロード
そのままターミナルでコマンド入力してもできるけど、今回は視覚的にわかりやすいKitematicを使ってDockerイメージをダウンロードしてみる
- デスクトップのショートカットから Kitematic を起動
- "Setup Initialization" の画面で [USE VIRTUALBOX] を選択、DockerHubアカウントは作成しなくてもOK
- セットアップが完了して表示された画面の検索ボックスにキーワードを入力(今回はjava)
- 出てきた候補から今回は "openjdk" を選び、[CREATE] ボタンを押す
- 左側のContainers欄に "openjdk" が表示され、(先程ターミナルからダウンロードした)"hello-world" と同じように灰色アイコンの表示になったらダウンロード完了
"Connecting to Docker Hub" と表示されダウンロードが進まない場合
- 上の画像左下の DOCKER CLI をクリックしてPowerShellを起動
docker pull scrapinghub/splash
と入力してEnter- しばらく待つと諸々のダウンロードが完了しているので、この状態でKitematicに戻るとダウンロードが完了してた
- ダメなら一度Dockerイメージのダウンロードを中止して、再度 [CREATE] ボタンを押してダウンロードを試みる
Dockerイメージからコンテナをつくる
これも Kitematic でやろうと思ったら、何回 [START] をクリックしてもすぐストップしてしまったので Docker のターミナルに戻って作業
docker run -it --name testcont openjdk:latest
を実行してコンテナを起動状態で作成("testcont" はコンテナの名前)- 完成!
- 投稿日:2020-06-28T14:13:22+09:00
【Android】DataBindingのvariableはcamelCaseで書くのが無難
<variable name="hoge_text" type="String">より、
<variable name="hogeText" type="String">のが困らないと思うっすという話。プチハマったので。
具体例
「被
<include>
ファイルでsnake_caseのvariableを使おうとしたらアクセスできなかった」書いてみるxml
child.xml<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="value_text" type="String" /> </data> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{value_text}"/> </layout>parent.xml<親要素省略> <include layout="@layout/child" android:layout_width="wrap_content" android:layout_height="wrap_content" app:value_text="@{someVariable}"/> </親要素省略>生成されるコード
ChildBindingImpl.javapublic void setValueText(@Nullable java.lang.String ValueText) { this.mValueText = ValueText; synchronized(this) { mDirtyFlags |= 0x2L; } notifyPropertyChanged(BR.value_text); super.requestRebind(); }ParentBindingImpl.java@Nullable private final app.package.name.databinding.ChildBinding mboundView; // ~~~ 略 ~~~ this.mboundView.setValue_text(someVariableGetValue); // <- お前ー!!エラー内容
エラー: シンボルを見つけられません this.mboundView3.setValue_text(someVariableGetValue); ^ シンボル: メソッド setValue_text(String)というわけで、
app:snake_case
属性により生成される関数名が、
<variable name="snake_case">
により生成される関数名と異なるためビルドに失敗します。「じゃあ
app:camelCase
&<variable name="snake_case">
ならどうよ」という気もしますが、
その場合はxmlファイル側で名前の解決が出来ず、BindingImplの生成前にエラーになるようです。書く側としてもxml内でその切り替えはしんどいっす。(自分の場合)対処
冒頭に書いた通りです。
child.xml<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="valueText" type="String" /> </data> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{valueText}"/> </layout>parent.xml<親要素省略> <include layout="@layout/child" android:layout_width="wrap_content" android:layout_height="wrap_content" app:valueText="@{someVariable}"/> </親要素省略>定義からcamelCaseにしましょう。
おまけ
あんまり明言はされてないと思うんですが、
公式のサンプルでも2単語の変数名をさがしました。camelCaseでした。(userList
)<data> <import type="com.example.User"/> <import type="java.util.List"/> <variable name="user" type="User"/> <variable name="userList" type="List<User>"/> </data>このあたり(じゃあresourceのidもcamelCaseにしとく? とか)はGoogle提供のコードでも一貫されていない感があるので、昔から小さく話題になることみたいですね。
- 投稿日:2020-06-28T11:49:09+09:00
Java HTTP Client API のタイムアウト設定
概要
- Java 11 から正式導入された HTTP Client API (java.net.http パッケージ) のタイムアウト設定について接続時の動作を検証する
- HTTP Client API では設定できるタイムアウト値が2種類ある
- 接続タイムアウト: HttpClient.Builder.connectTimeout
- リクエストタイムアウト: HttpRequest.Builder.timeout
検証結果からの考察
- 接続タイムアウト HttpClient.Builder.connectTimeout には接続タイムアウトの許容時間を指定する
- リクエストタイムアウト HttpRequest.Builder.timeout にはリクエスト全体 (接続タイムアウトや読み取りタイムアウトを含めたもの) の許容時間を指定する
公式ドキュメントによる情報
HttpClient.Builder.connectTimeout
HttpClient.Builder (Java SE 14 & JDK 14)
HttpClient.Builder connectTimeout(Duration duration)
このクライアントの接続タイムアウト期間を設定します。
新しい接続を確立する必要がある場合に、指定された duration内に接続を確立できないと、HttpClient::sendはHttpConnectTimeoutExceptionをスローし、HttpClient::sendAsyncはHttpConnectTimeoutExceptionを使用して例外的に完了します。 たとえば、接続を前のリクエストから再利用できる場合など、新しい接続を確立する必要がない場合、このタイムアウト期間は影響しません。パラメータ:
duration - 基礎となる接続の確立を許可する期間戻り値:
このビルダー例外:
IllegalArgumentException - 期間が非正の場合HttpClient.Builder (Java SE 14 & JDK 14)
HttpClient.Builder connectTimeout(Duration duration)
Sets the connect timeout duration for this client.
In the case where a new connection needs to be established, if the connection cannot be established within the given duration, then HttpClient::send throws an HttpConnectTimeoutException, or HttpClient::sendAsync completes exceptionally with an HttpConnectTimeoutException. If a new connection does not need to be established, for example if a connection can be reused from a previous request, then this timeout duration has no effect.Parameters:
duration - the duration to allow the underlying connection to be establishedReturns:
this builderThrows:
IllegalArgumentException - if the duration is non-positiveHttpRequest.Builder.timeout
HttpRequest.Builder (Java SE 14 & JDK 14)
HttpRequest.Builder timeout(Duration duration)
このリクエストのタイムアウトを設定します。 指定されたタイムアウト内にレスポンスを受信しなかった場合、HttpClient::sendまたはHttpClient::sendAsyncからHttpTimeoutExceptionが例外的にHttpTimeoutExceptionを使ってスローされます。 タイムアウトを設定しない場合の影響は、無限期間(永続的ブロック)の設定と同じです。
パラメータ:
duration - タイムアウト期間戻り値:
このビルダー例外:
IllegalArgumentException - 期間が非正の場合HttpRequest.Builder (Java SE 14 & JDK 14)
HttpRequest.Builder timeout(Duration duration)
Sets a timeout for this request. If the response is not received within the specified timeout then an HttpTimeoutException is thrown from HttpClient::send or HttpClient::sendAsync completes exceptionally with an HttpTimeoutException. The effect of not setting a timeout is the same as setting an infinite Duration, i.e. block forever.
Parameters:
duration - the timeout durationReturns:
this builderThrows:
IllegalArgumentException - if the duration is non-positive挙動を検証するソースコード
HttpClientTimeoutTest.java というファイル名で以下の内容を保存する。
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.time.LocalDateTime; public class HttpClientTimeoutTest { public static void main(String[] args) throws Exception { // 接続タイムアウトのテスト (存在しないサーバ、接続タイムアウト3秒) testTimeout("http://127.0.0.2:8000/", Duration.ofSeconds(3), null); Thread.sleep(1000L); // 読み取りタイムアウトのテスト (レスポンスが遅いサーバ、リクエストタイムアウト3秒) testTimeout("http://127.0.0.1:8000/", null, Duration.ofSeconds(3)); Thread.sleep(1000L); // リクエストタイムアウトの影響範囲のテスト (存在しないサーバ、接続タイムアウト5秒、リクエストタイムアウト3秒) testTimeout("http://127.0.0.2:8000/", Duration.ofSeconds(5), Duration.ofSeconds(3)); Thread.sleep(1000L); } /** * タイムアウトのテスト。 * @param url URL * @param connectTimeout HttpClient.Builder.connectTimeout にセットする値 * @param requestTimeout HttpRequest.Builder.timeout にセットする値 */ private static void testTimeout(String url, Duration connectTimeout, Duration requestTimeout) { try { // パラメータを確認 System.out.println("URL: " + url); System.out.println("ConnectTimeout: " + connectTimeout); System.out.println("RequestTimeout: " + requestTimeout); // HttpClient オブジェクトを構築 HttpClient.Builder hcb = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1); if (connectTimeout != null) { hcb.connectTimeout(connectTimeout); // 接続タイムアウトを設定 } HttpClient client = hcb.build(); // HttpRequest オブジェクトを構築 HttpRequest.Builder hrb = HttpRequest.newBuilder(new URI(url)).GET(); if (requestTimeout != null) { hrb.timeout(requestTimeout); // リクエストタイムアウトを設定 } HttpRequest request = hrb.build(); // タイムアウト設定を確認 System.out.println("HttpClient.connectTimeout: " + client.connectTimeout()); System.out.println("HttpRequest.timeout: " + request.timeout()); // HTTP リクエストを投げる System.out.println("HttpClient.send begins: " + LocalDateTime.now()); HttpResponse<String> res = client.send(request, HttpResponse.BodyHandlers.ofString()); } catch (Exception e) { System.out.println("HttpClient.send failed: " + LocalDateTime.now()); e.printStackTrace(); } } }レスポンスが遅い HTTP サーバを用意
リクエストが来てから10秒後にレスポンスを返すような HTTP サーバを Ruby で書く。
server.rb というファイル名で以下の内容を保存する。
require 'webrick' server = WEBrick::HTTPServer.new({ :Port => 8000, :HTTPVersion => WEBrick::HTTPVersion.new('1.1') }) server.mount_proc('/') do |req, res| sleep(10) # 10秒スリープ res.status = 200 res.body = '' end Signal.trap('INT'){server.shutdown} server.startruby コマンドで HTTP サーバのプログラムを実行する。
$ ruby server.rb [2020-06-28 11:03:15] INFO WEBrick 1.6.0 [2020-06-28 11:03:15] INFO ruby 2.7.1 (2020-03-31) [x86_64-darwin19] [2020-06-28 11:03:15] INFO WEBrick::HTTPServer#start: pid=47723 port=8000実行結果
java コマンドで HttpClientTimeoutTest.java を実行する。
Java 11 移行であれば javac によるコンパイル無しで実行できる。
参考: JEP 330: Launch Single-File Source-Code Programs今回の検証環境: macOS Catalina + Java 14 (AdoptOpenJDK 14.0.1+7)
$ java HttpClientTimeoutTest.java URL: http://127.0.0.2:8000/ ConnectTimeout: PT3S RequestTimeout: null HttpClient.connectTimeout: Optional[PT3S] HttpRequest.timeout: Optional.empty HttpClient.send begins: 2020-06-28T11:07:13.619689 HttpClient.send failed: 2020-06-28T11:07:16.703287 java.net.http.HttpConnectTimeoutException: HTTP connect timed out at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:557) at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119) at HttpClientTimeoutTest.testTimeout(HttpClientTimeoutTest.java:59) at HttpClientTimeoutTest.main(HttpClientTimeoutTest.java:13) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:564) at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415) at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192) at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132) Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out at java.net.http/jdk.internal.net.http.MultiExchange.toTimeoutException(MultiExchange.java:508) at java.net.http/jdk.internal.net.http.MultiExchange.getExceptionalCF(MultiExchange.java:455) at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:382) at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:930) at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:907) at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506) at java.base/java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:2152) at java.net.http/jdk.internal.net.http.Http1Exchange.lambda$cancelImpl$9(Http1Exchange.java:482) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) at java.base/java.lang.Thread.run(Thread.java:832) Caused by: java.net.ConnectException: HTTP connect timed out at java.net.http/jdk.internal.net.http.MultiExchange.toTimeoutException(MultiExchange.java:509) ... 10 more URL: http://127.0.0.1:8000/ ConnectTimeout: null RequestTimeout: PT3S HttpClient.connectTimeout: Optional.empty HttpRequest.timeout: Optional[PT3S] HttpClient.send begins: 2020-06-28T11:07:17.713737 HttpClient.send failed: 2020-06-28T11:07:20.720473 java.net.http.HttpTimeoutException: request timed out at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:561) at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119) at HttpClientTimeoutTest.testTimeout(HttpClientTimeoutTest.java:59) at HttpClientTimeoutTest.main(HttpClientTimeoutTest.java:17) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:564) at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415) at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192) at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132) URL: http://127.0.0.2:8000/ ConnectTimeout: PT5S RequestTimeout: PT3S HttpClient.connectTimeout: Optional[PT5S] HttpRequest.timeout: Optional[PT3S] HttpClient.send begins: 2020-06-28T11:07:21.729119 HttpClient.send failed: 2020-06-28T11:07:24.735766 java.net.http.HttpConnectTimeoutException: HTTP connect timed out at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:557) at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:119) at HttpClientTimeoutTest.testTimeout(HttpClientTimeoutTest.java:59) at HttpClientTimeoutTest.main(HttpClientTimeoutTest.java:21) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:564) at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415) at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192) at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132) Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:68) at java.net.http/jdk.internal.net.http.HttpClientImpl.purgeTimeoutsAndReturnNextDeadline(HttpClientImpl.java:1259) at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:888) Caused by: java.net.ConnectException: HTTP connect timed out at java.net.http/jdk.internal.net.http.ResponseTimerEvent.handle(ResponseTimerEvent.java:69) ... 2 more実行結果の検証
接続タイムアウトのテスト (存在しないサーバ、接続タイムアウト3秒)
- 接続タイムアウト: HttpClient.Builder.connectTimeout に3秒を指定
- リクエストタイムアウト: HttpRequest.Builder.timeout は無指定
- 3秒で接続タイムアウトが発生
例外 java.net.http.HttpConnectTimeoutException が発生
例外チェーン
- java.net.http.HttpConnectTimeoutException: HTTP connect timed out
- Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
- Caused by: java.net.ConnectException: HTTP connect timed out
読み取りタイムアウトのテスト (レスポンスが遅いサーバ、リクエストタイムアウト3秒)
- 接続タイムアウト: HttpClient.Builder.connectTimeout は無指定
- リクエストタイムアウト: HttpRequest.Builder.timeout に3秒を指定
- 3秒でリクエストタイムアウトが発生
例外 java.net.http.HttpTimeoutException が発生
例外チェーン (原因例外は無し)
- java.net.http.HttpTimeoutException: request timed out
リクエストタイムアウトの影響範囲のテスト (存在しないサーバ、接続タイムアウト5秒、リクエストタイムアウト3秒)
- 接続タイムアウト: HttpClient.Builder.connectTimeout に5秒を指定
- リクエストタイムアウト: HttpRequest.Builder.timeout に3秒を指定
- 3秒で接続タイムアウトが発生
- 接続タイムアウトに5秒を指定しているのにもかかわらず3秒で接続タイムアウトが発生している
- リクエストタイムアウトに設定した3秒で接続タイムアウトが発生していると思われる
- リクエストタイムアウトは接続タイムアウトの時間込みの値だと考えられる
例外 java.net.http.HttpTimeoutException が発生
例外チェーン
- java.net.http.HttpConnectTimeoutException: HTTP connect timed out
- Caused by: java.net.http.HttpConnectTimeoutException: HTTP connect timed out
- Caused by: java.net.ConnectException: HTTP connect timed out
参考資料
- 投稿日:2020-06-28T11:22:38+09:00
インスタンスメソッドの呼び出しについて
インスタンスメソッドの呼び出しの書式についての備忘録です。
メソッド呼び出しの書式について
- 同じインスタンスに定義されているメソッドの場合は[メソッド名(引数)]
- インスタンスに定義されているメソッドの場合は[変数.メソッド名(引数)]
- staticなメソッドの場合は[クラス名.メソッド名(引数)]
同じインスタンスに定義されているメソッド
Test.javapublic class Test { public void SayHello() { System.out.println("Hello World"); } public void Display() { SayHello(); } }異なるインスタンスに定義されているメソッド
新しくインスタンスを作成して、変数が格納されている参照を使う(hello.SayHello();)。
Hello.javapublic class Hello{ public void SayHello() { System.out.println("Hello World"); } }Test.javapublic class Test { public static void main(final String[] args) { Hello hello = new Hello(); hello.SayHello(); } }staticなメソッド
クラス名の参照を使う(Hello.SayHello();)。
Hello.javapublic class Hello{ public static void SayHello() { System.out.println("Hello World"); } }Test.javapublic class Test { public static void main(final String[] args) { Hello.SayHello(); } }おまけ
後ろにカッコがつかないのはインスタンスのフィールドへのアクセスです。
参考文献
徹底攻略Java SE11 Silver問題集
- 投稿日:2020-06-28T03:55:27+09:00
【Java】parallelStreamでプログラムを10倍速にする
結論
parallelStream()
によってJavaプログラムは10倍速くなる可能性がある環境
- CPU:Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz、2208 Mhz、6 個のコア、12 個のロジカル プロセッサ
- メモリ:16GB
- Java:Amazon Corretto-11.0.3.7.1
検証内容
検証用コード
Main.javaimport java.util.List; import java.util.ArrayList; public class Main { public static void main(String[] args) { List<Integer> n1 = new ArrayList<>(); List<Integer> n2 = new ArrayList<>(); for(int i = 0; i < 500; i++) { n1.add(i); n2.add(i); } long s1 = System.currentTimeMillis(); n1.stream().forEach(i -> { try { Thread.sleep(10); } catch(Exception e) { e.printStackTrace(); } }); long t1 = System.currentTimeMillis(); long s2 = System.currentTimeMillis(); n2.parallelStream().forEach(i -> { try { Thread.sleep(10); } catch(Exception e) { e.printStackTrace(); } }); long t2 = System.currentTimeMillis(); System.out.printf("n1 exec time: %d [ms]\n", t1 - s1); System.out.printf("n2 exec time: %d [ms]\n", t2 - s2); } }取り立てて解説する点はあまりないです。
n1
は順次ストリームで、n2
は並列ストリームで500回Thread.sleep(10)
を呼び出してい ます。try-catchしているのはThread.sleep()
の検査例外に対応するためです。forEach
の中のi
は虚空に消えています。実行結果
n1 exec time: 5298 [ms] n2 exec time: 505 [ms]
n2
はn1
に比べて10倍以上速くなってます!
n1
は10ms × 500回の結果が如実に現れていますね。補足
公式ドキュメントによると、デフォルトの並列スレッド数は、コンピュータで利用できるプロセッサの数と等しいそうです。
For applications that require separate or custom pools, a ForkJoinPool may be constructed with a given target parallelism level; by default, equal to the number of available processors.
並列ストリームでの実行時間が順次ストリームのものの1/10以下になっているところを見るに、物理プロセッサ数ではなく論理プロセッサ数がデフォルト値として適用されているようですね。
「10倍速くなる」...っておま環じゃねーか!! という大変もっともなツッコミはあると思いますが、最近のPCなら大抵8論理プロセッサくらいはあると思うので許してください。注意
各スレッドで共通して参照しているオブジェクトを更新したり削除したりする操作は、基本的にしないほうがいいです(Listに対するaddなど)。
- 投稿日:2020-06-28T03:34:05+09:00
Javaの5つのstaticの違いをさらっと説明する
概要
Javaの「static」には5つの種類がある。しかし、それぞれは似て非なるものである。
本記事では、それぞれのstaticについて、落とし穴にハマらないための要点を説明する。
もっと詳しく知りたい場合は、記事最後の参考資料などを参照されたい。1. staticメソッド
メソッドにstatic修飾子が付与されていると、staticメソッドとなる。
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }staticメソッドは、インスタンスをnewしなくても呼び出すことができるため、一見便利に思える(使いやすそうに見える)。
しかし、単体テストを書きやすくするためには、「決まりきった処理」のみに利用すべきである。2. staticフィールド
フィールドにstatic修飾子が付与されていると、staticフィールドとなる。
public static final double PI = 3.14159265358979323846;上記のように、final修飾子と一緒に利用することで、定数として利用することができる。
final修飾子と一緒に利用することで、プログラムのどこかで値を書き換えられ、誤った計算結果を出力してしまうなどのバグを防ぐことができる。なお、システムの設計によっては、キャッシュの保持などにも利用することができる。
3. 【中級者向け】staticインポート
外部のクラスのstaticフィールドや staticメソッドを、クラス名を指定せずに呼び出すことができるようになる。
import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; public class SampleTest { @Test public void Test() { assertThat("foo", is("foo")); } }上記のサンプルでは、
Assert.assertThat
やMatchers.is
について、クラス名を省略して記述することができている。4. 【中級者向け】staticインナークラス
インナークラスにstatic修飾子が付与されていると、staticインナークラスとなる。
public class Outer { public static class Inner { // staticインナークラス } }非staticなインナークラスは親クラスのフィールドやインスタンスメソッドにもアクセスできる。
このため、親クラスへの参照を保持している。しかし、不必要な親クラスへの参照は、メモリやGC1への悪影響が懸念される。
インナークラスをstaticにできる状況であれば、staticにすべきである。25. 【中級者向け】staticイニシャライザ
下記のブロック内に記載した処理は、そのクラスに初めてアクセスされる時に呼び出される。
static { // 処理 }具体的には、staticフィールドのコレクションの初期化などに利用する3。
public static final List<String> LIST; static { List<String> tmp = new ArrayList<>(); tmp.add("foo"); tmp.add("bar"); LIST = Collections.unmodifiableList(tmp); }参考資料
- 投稿日:2020-06-28T03:34:05+09:00
Javaの5種類のstaticをさらっと説明する
概要
Javaの「static」には5つの種類がある。しかし、それぞれは似て非なるものである。
本記事では、それぞれのstaticについて、落とし穴にハマらないための要点を説明する。
もっと詳しく知りたい場合は、記事最後の参考資料などを参照されたい。1. staticメソッド
メソッドにstatic修飾子が付与されていると、staticメソッドとなる。
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }staticメソッドは、インスタンスをnewしなくても呼び出すことができるため、一見便利に思える(使いやすそうに見える)。
しかし、単体テストを書きやすくするためには、「決まりきった処理」のみに利用すべきである。2. staticフィールド
フィールドにstatic修飾子が付与されていると、staticフィールドとなる。
public static final double PI = 3.14159265358979323846;上記のように、final修飾子と一緒に利用することで、定数として利用することができる。
final修飾子と一緒に利用することで、プログラムのどこかで値を書き換えられ、誤った計算結果を出力してしまうなどのバグを防ぐことができる。なお、システムの設計によっては、キャッシュの保持などにも利用することができる。
3. 【中級者向け】staticインポート
外部のクラスのstaticフィールドや staticメソッドを、クラス名を指定せずに呼び出すことができるようになる。
import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; public class SampleTest { @Test public void Test() { assertThat("foo", is("foo")); } }上記のサンプルでは、
Assert.assertThat
やMatchers.is
について、クラス名を省略して記述することができている。4. 【中級者向け】staticインナークラス
インナークラスにstatic修飾子が付与されていると、staticインナークラスとなる。
public class Outer { public static class Inner { // staticインナークラス } }非staticなインナークラスは親クラスのフィールドやインスタンスメソッドにもアクセスできる。
このため、親クラスへの参照を保持している。しかし、不必要な親クラスへの参照は、メモリやGCへの悪影響1が懸念される。
インナークラスをstaticにできる状況であれば、staticにすべき2である。5. 【中級者向け】staticイニシャライザ
下記のブロック内に記載した処理は、そのクラスに初めてアクセスされる時に呼び出される。
static { // 処理 }具体的には、staticフィールドのコレクションの初期化などに利用する3。
public static final List<String> LIST; static { List<String> tmp = new ArrayList<>(); tmp.add("foo"); tmp.add("bar"); LIST = Collections.unmodifiableList(tmp); }参考資料
- 投稿日:2020-06-28T01:42:52+09:00
モンテカルロ法による各言語の速度比較
はじめに
モンテカルロ法
モンテカルロ法を使用して各言語で円周率を求めることにより各言語の速度比較を行います。
モンテカルロ法とは、1.0>=x>=0, 1.0>=y>=0に任意の(乱数の)点を落とし、その落とされた点が原点(0,0)からの円内に落ちたものと、円外に落ちたものの比を求め、その比が円周率になるというものです。
もちろん、計算で求められた円周率に近似するものの、正確な値はでませんが、プログラミング言語で実装することには意味があると思っています。
理由としては、
・浮動小数点計算の速度が比較できる。
・Loop速度が比較できる。
・メモリの使用は少量であり、ページングは(おそらく)発生しない。
・DiskIOが発生しないため、外部記憶装置の影響を受けにくい。
・正解の円周率は既知の情報であり、デバッグが楽。
などがあります。2020年6月28日 rubyを追加しました。
2020年6月28日 pythonの計算方法を変更しました。
2020年6月28日 rustのループ回数指定に誤りがあったので修正しました。
2020年6月28日 rustを@tatsuya6502 さんご提供のXoroshiro版のソースを追加測定以下、各言語ソース
perl
perlは今まで試した中で非常に高速のイメージがあったため、ベンチマーク(≒基準)として、用意しました。
ソースもシンプルですので、オブジェクト指向等によるオーバーヘッドもなく、高速に動作するはずです。montecarlo.pluse Time::HiRes qw( gettimeofday tv_interval); local $ave = 0; local $time_start, $time_end, $time_split = 0; local $max_times = 10000000; local $test_times = 100; local $cnt = 0; for ( $i = 1 ; $i <= $test_times ; $i++ ) { print($i."\n"); $time_start = [gettimeofday]; $cnt = 0; for ( $j = 0 ; $j < $max_times ; $j++) { $x = rand(); $y = rand(); if ( $x * $x + $y * $y <= 1) { $cnt++; } } $time_split = tv_interval($time_start); print("pi ".(4 * $cnt / $max_times)."\n"); print("split:".$time_split."\n"); $ave += $time_split; } print("AVE:".($ave / $test_times)."\n"); ~ ~c言語
とりあえず、「Cなら速いんじゃね?」ということで用意しました。
厳密にチューニングすればもっと早くなるのかもしれませんが、
コーディングのしやすさでこのくらいで抑えてみました。montecarlo.c#include <stdio.h> #include <stdlib.h> #include <time.h> float montecarlo(); void main() { float ave = 0; int times = 100; for (int i = 1 ; i <= times ; i++){ printf("%03d\n", i ); ave += montecarlo(); } printf("AVE %f\n\n", ave / times); } float montecarlo() { clock_t time_start, time_end; double time_split; int times = 10000000; float x, y; time_start = clock(); int cnt = 0; for ( int i = 0; i < times ; i++) { x = (float)rand() / (float)RAND_MAX; y = (float)rand() / (float)RAND_MAX; if ( (x * x + y * y) <= 1) { cnt++; } } time_end = clock(); time_split = (double)(time_end - time_start) / 1000000; printf("%f\n", 4.0 * cnt / times); printf("time_split : %lf\n", time_split); return(time_split); }go言語
goは前評判も良かったことから、perlと異なるベンチマークとして用意しました。
コンパイル言語ですので、c言語にどれだけ肉薄できるか?という意味でも興味があります。montecarlo.gopackage main import ( "fmt" "time" "math/rand" ) var i, j, cnt int var x, y float64 var time_start, time_end int64 var ave, time_split float64 const MAX_TIMES = 10000000 const TEST_TIMES = 100 func main() { //fmt.Printf("%03d\n", 10) //fmt.Printf("%d\n", time.Now()) //fmt.Printf("%f\n", float64(time.Now().UnixNano()) / 1000000000.0) //fmt.Printf("%f\n", float64(time.Now().UnixNano()) / 1000000000.0) //fmt.Printf("%f\n", float64(time.Now().UnixNano()) / 1000000000.0) rand.Seed(time.Now().UnixNano()) ave := 0.0 for i := 1 ; i <= TEST_TIMES ; i++ { fmt.Printf("%03d times\n", i) time_start := time.Now().UnixNano() cnt := 0 for j := 0 ; j < MAX_TIMES ; j++ { x := rand.Float64() y := rand.Float64() if x * x + y * y <= 1 { cnt++ } } fmt.Printf("pi : %f\n", 4 * float64(cnt) / float64(MAX_TIMES)) time_end := time.Now().UnixNano() time_split := float64(time_end - time_start) / 1000000000 fmt.Printf("time : %f\n", time_split) ave += time_split //ave := time_split } fmt.Printf("AVE : %f\n", ave / TEST_TIMES) }Java
コンパイル言語としては現状で鉄板に位置づけられると思います。(各々ご意見はあるでしょうが・・・。)
VMが間に入ることによりC言語よりは遅いことが予想されますが、過去の経験では致命的なほど遅いわけではないという印象です。montecarlo.javaimport java.util.Random; import java.util.*; class Montecarlo { public static void main(String[] args) { //System.out.println( getNowTime() ); // 円の半径を指定する double iR = 1.0; // 描画回数を指定する int MAX_POINT = 10000000; // テスト回数を指定する int TEST_TIMES = 100; // Random オブジェクトの定義 Random rand = new Random(); // 開始、終了時刻 long time_start, time_end = 0; double time_split; double ave = 0.0; // Random値の待避用変数の定義 double ranValue = 0.0; double POS_X = 0.0; double POS_Y = 0.0; for ( int j = 1 ; j <= TEST_TIMES ; j ++) { //System.out.println("%03d", j); System.out.printf("%03d\n", j); // 開始時刻 time_start = System.nanoTime(); //System.out.println( "start : " + time_start ); // 円弧状に落ちた件数をカウントする int HIT_COUNT = 0; // 出力用ワーク変数の初期化 for ( int i = 0 ; i < MAX_POINT ; i++ ) { POS_X = rand.nextDouble(); POS_Y = rand.nextDouble(); if ( iR >= POS_X * POS_X + POS_Y * POS_Y ) { HIT_COUNT++; } } // System.out.println(HIT_COUNT ); System.out.println( ( 4 * HIT_COUNT / (double)MAX_POINT ) ); // 終了時刻 time_end = System.nanoTime(); //System.out.println( "end : " + time_end ); time_split = (double)(time_end - time_start) / 1000000000.0; //System.out.println( "split : " + (time_end - time_start) ); System.out.println( "split : " + time_split ); ave += time_split; } //System.out.println( getNowTime() ); System.out.println( "AVE : " + ave / TEST_TIMES ); } /// 現在時刻をyyyy/mm/dd hh:mm:ss(jpn) 形式で取得する public static String getNowTime() { return(String.format("%1$tF %1$tT" ,Calendar.getInstance())); } }lua
script言語としては軽いと評判ですので加えてみました。
言語仕様としてはOSに近しい命令を使用するため早いというイメージです。montecarlo.lualocal socket = require("socket") math.randomseed(os.time()) MAX_LOOP = 10000000 TEST_TIMES = 100 ave = 0.0 for i = 1, TEST_TIMES do print(string.format("%03d times", i)) time_start = socket.gettime() cnt = 0 for j = 1 , MAX_LOOP do x = math.random() y = math.random() if x * x + y * y <= 1 then cnt = cnt + 1 end end time_end = socket.gettime() time_split = time_end - time_start print(string.format("pi : %f", 4 * cnt / MAX_LOOP)) print(string.format("split : %f", time_split)) ave = ave + time_split end print(string.format("ave : %f", ave / TEST_TIMES))python
言わずと知れたpythonです。
今回は2系、3系で比較するとともに、pypyでの比較を行います。montecarlo.pyimport random import time import math _times = 10000000 def pi(): _time_start = time.time() _cnt = 0 for i in range(_times): _x = random.random() _y = random.random() #if _x ** 2 + _y **2 <= 1: if _x * _x + _y * _y <= 1: _cnt += 1 print( 4.0 * _cnt / _times ) _time_end = time.time() _time_split = _time_end - _time_start print(_time_split) return _time_split _test = 100 _ave = 0.0 for _i in range(_test): print('{:03d} times'.format(_i+1)) _ave += pi() print('ave: {} s'.format(_ave/_test))Rust
RustはAI,機械学習で注目されている言語とのことです。
pythonよりは早く、Cよりは開発しやすい言語とのことです。Cargo.toml[package] name = "montecarlo" version = "0.1.0" authors = ["ubuntu"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rand = "0.6" floating-duration="0.1.2"src/main.rsfn main() { use rand::Rng; // 乱数用 use std::time::Instant; // 時刻取得用 use floating_duration::TimeAsFloat; // 時刻→floatへの変換用 println!("Hello, world!"); let loop_max = 10000000; // 打点の回数 let test_max = 100; // 平均を出すためのループ let mut x: f32; let mut y: f32; let mut rng = rand::thread_rng();//randseedの設定 let mut split_time: f32 = 0.0; //for _test_count in 1..test_max { for _test_count in 1..=test_max { println!("times:{:?}", _test_count ); let start_time = Instant::now(); let mut count = 0; //for _loot_count in 1..loop_max { for _loot_count in 1..=loop_max { x = rng.gen_range(0., 1.); //乱数0.0~1.0 y = rng.gen_range(0., 1.); if x * x + y * y <= 1.0 { count = count + 1; } } println!("pi:{}", 4.0 * count as f32 / loop_max as f32); // as f32は型キャスト let end_time = start_time.elapsed().as_fractional_secs();// 時間を秒に変換 println!("time:{:?}", end_time ); split_time = split_time + end_time as f32; } println!("AVE:{}", split_time / test_max as f32); }@tatsuya6502 さんご提供のXoroshiro版ソース
Cargo.toml[package] name = "montecarlo" version = "0.1.0" authors = ["ubuntu"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rand = "0.7" rand_xoshiro = "0.4"src/main.rs// 乱数用 use rand::Rng; use rand_xoshiro::{rand_core::SeedableRng, Xoroshiro128Plus}; // 時刻取得用 use std::time::{Instant, SystemTime}; const LOOP_MAX: usize = 10_000_000; // 打点の回数 const TEST_MAX: usize = 100; // 平均を出すためのループ fn main() -> Result<(), Box<dyn std::error::Error>> { // random seedを設定 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)?; let mut rng = Xoroshiro128Plus::seed_from_u64(now.as_millis() as u64); let mut split_time: f32 = 0.0; for _test_count in 1..=TEST_MAX { println!("times:{}", _test_count); let start_time = Instant::now(); let mut count = 0; for _loot_count in 0..LOOP_MAX { // 乱数を生成 [0.0, 1.0) ・・ 0.0以上 1.0未満 let x: f32 = rng.gen_range(0.0, 1.0); let y: f32 = rng.gen_range(0.0, 1.0); if x * x + y * y <= 1.0 { count += 1; } } println!("pi:{}", 4.0 * count as f32 / LOOP_MAX as f32); // as f32は型キャスト let end_time = start_time.elapsed().as_secs_f32(); // 時間を秒に変換 println!("time:{}", end_time); split_time += end_time as f32; } println!("AVE:{}", split_time / TEST_MAX as f32); Ok(()) }Nim
比較用に・・・
montecarlo.nimimport random import times randomize() const TEST_MAX = 10000000 const LOOP_MAX = 100 var x, y = 0.0 var ave = 0.0 for loop in 1..LOOP_MAX: echo $loop & " times" var time_start = cpuTime() var count = 0 for test in 1..TEST_MAX: x = rand(0.0..1.0) y = rand(0.0..1.0) # echo $x # echo $y if ( x * x + y * y <= 1): count += 1 echo "pi:" & repr(float(4.0 * float(count) / float(TEST_MAX))) var time_split = cpuTime() - time_start echo "split" & repr(time_split) ave += time_split echo "AVE:" & repr(ave / float(LOOP_MAX)) ~Ruby
@scivola さんにソースを提供していただきましたのでrubyを追加しました。
montecarlo.rbTIMES = 10000000 def pi time_start = Time.now cnt = 0 TIMES.times do x = rand y = rand if x * x + y * y <= 1 cnt += 1 end end puts 4.0 * cnt / TIMES time_end = Time.now time_split = time_end - time_start puts time_split time_split end test = 100 ave = 0.0 test.times do |i| puts "%03d times" % (i + 1) ave += pi end puts "ave: #{ ave / test } s"結果
perl 5.30 AVE:8.93247789s java 14.0.1 AVE: 1.4161036380199996s c gcc 9.3.0 gcc montecarlo.c -o pi gcc 最適化なし AVE:1.501472s gcc 実行速度の最適化 -O3 AVE:1.425400s python 2.7.18 AVE:14.216013s pypy 7.3.1 with GCC 9.3.0 ave: 0.983056025505 python 3.8.2 AVE:12.485190s pypy3 7.3.1 with GCC 9.3.0 ave: 1.1475282835960388 s go 1.13.8 AVE:2.132493 lua 5.3 AVE:6.715329 rust (cargo 1.44.0) cargo run --release AVE 0.507196783 cargo run AVE 16.449156 Nim 1.0.6 nim c -r montecarlo.nim AVE 7.2063023393 nim c -r -d:release montecarlo.nim AVE:0.32369470732000012020年6月28日 追記
ruby を追加しました。 Ruby 2.7.0dev (2019-12-25 master e1e1d92277) [aarch64-linux] ave: 7.5495061204099985 s pythonのソースを修正しました。 if _x ** 2 + _y **2 <= 1: ① ↓ if _x * _x + _y * _y <= 1: ② ※python 3.8.2の計測 ①再取得 ave: 11.132939925193787 s ② ave: 9.153075976371765 s ※pypy3の計測 ①再取得 ave: 1.0760090994834899 s ② ave: 1.3655683732032775 s あれ? rustのループを修正しました。 for _test_count in 1..test_max { for _loot_count in 1..loop_max { ① ↓ for _test_count in 1..=test_max { for _loot_count in 1..=loop_max { ② cargo run --release の計測 ①再取得 AVE:0.5022585 ② AVE:0.51400733 rustで@tatsuya6502 さんご提供のXoroshiro版ソースについて計測しました。 AVE:0.21163946結論
このレベルであれは
Nimrustが最速との評価になりました。
速度的には、
Nim > rust > pypy > C = java > pypy3 > go > lua > ruby > perl > python2系 > python3系
rust(Xoroshiro版) > Nim > rust > pypy > C = java >= pypy3 > go > lua > ruby > perl > python2系 > python3系となりました。
順当にコンパイル言語>スクリプト言語となっています。
もちろん、私のコーディングが悪く、「もっとこうすれば早くなる」というのはあるかもしれません。
しかし、多少工夫をしたところで1段階変わるだけで、さほど順位の入れ替わりがあるとは考えにくいです。特筆すべきはpythonで、pypyを使用することによりC言語に近しい速度で動作する(可能性)が確認できました(もちろん、そのままのソースがpypyで動かない場合もありますし、ライブラリがpypyに対応していない場合もあります)。
しかしながらpythonの柔軟性のある記述で高速化できるのであれば試してみる価値があるとおもいます。2020年6月28日追記
rubyが予想以上に速くなっており驚きを隠せません。以前試したときはc:ruby=1:20くらいでしたので、今回もそのくらいと予想していましたが見事に裏切られました。
@scivola さんに提案をいただかなければ無視していました。ありがとうございます。pythonは今回のソース修正でpython3 で起動した場合は早くなるものの、pypy3で起動した場合は遅くなるという謎の結果になりました。(識者のコメント希望)
ちなみに本稿のソースではコメントして違いを表していますが、実測のソースでは置き換えていますので、コメントの評価で遅くなったということはありません。(昔のBasicとかでそういうのがあった気がしますが・・・)rustは疑似乱数生成器の違いが如実に現れました。
@tatsuya6502 さんご提供のXoroshiro版ソースにより疑似乱数生成器も適材適所であることが分かりました。
ご指摘ありがとうございました。本稿はたまーに更新します。
よさそうな言語がありましたら追加していこうと思います。