[SpringBoot2.4.3] よく使われるannotation

@Controller
画面遷移用のコントローラーに付与

@RestController
リクエストを受け付けるコントローラークラス

@RequestMapping(“path”)
マッピングするURLの接頭辞を設定

@GetMapping(“path”)
GETメソッドを受け取るメソッド

@PostMapping(“path”)
POSTメソッドを受け取る為のメソッドに付与

@Service
サービスクラス

@ComponentScan
特定のアノテーションが付与されたクラスのBeanをDIに登録

@Bean
DIコンテナに管理させたいBeanを生成するメソッドに付与

@Data
コンパイル時に、setter, getter, toString, equals, hashCodeなどのメソッド生成

@Autowired
特定のアノテーションを付与したクラスのインスタンスを使用できるようにする

@ModelAttribute
返り値は自動的にmodelに追加

@Validated
Bean Validationアノテーションが評価され、結果がBindingResultに格納

@PathVariable
Rest形式のパラメータを受け取る

@RequestParam
リクエストパラメータを受け取る

@Entity
JPAエンティティ

@Table(name=”table name”)
エンティティに対応するテーブル名を指定

@GeneratedValue
auto increment

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator=”シーケンス名”)
SEQ_IDで自動採番

@Transactional
クラス内に付与するとDBのトランザクション制御

@AllArgsConstructor
全フィールドを引数にもつコンストラクタ生成

@NoArgsConstructor
引数がないコンストラクタ

@Column
カラムに名前や制約を設定

@Query(“JPQL”)
データへのアクセスを自作する際に使用

@NotNull, @NotEmpty, @NotBlank, @Size(min=,max), @Email, @AssertTrue, @AssertFalse, @Pattern, @DateTimeFormat

@SpringBootApplication
Spring Bootの様々な設定を自動的に有効にする

@EnableAutoConfiguration
Spring Bootの様々な設定を自動的に有効にする

@Configuration
JavaConfig用のクラスであることを示す

@Qualifier(“Bean name”)
同じ型のBeanがDIコンテナに複数登録されている場合に適用する

アノテーションをきちんと理解しないと、SpringBootは使いこなせんな。

[SpringBoot2.4.3] Scheduling

Controller

package com.example.demo;

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

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

@SpringBootApplication
@EnableScheduling
public class HelloController {
	
	private static final SimpleDateFormat
	fmt = new SimpleDateFormat("HH:mm:ss");
	
	@Scheduled(fixedRate = 5000)
	public void reportTime() {
		System.out.println(fmt.format(new Date()));
	}
	
	public static void main(String[] args) {
		SpringApplication.run(HelloController.class, args);
	}
}

==========================
CONDITION EVALUATION DELTA
==========================

Positive matches:
—————–

TaskSchedulingAutoConfiguration#taskScheduler matched:
– @ConditionalOnBean (names: org.springframework.context.annotation.internalScheduledAnnotationProcessor; SearchStrategy: all) found bean ‘org.springframework.context.annotation.internalScheduledAnnotationProcessor’; @ConditionalOnMissingBean (types: org.springframework.scheduling.annotation.SchedulingConfigurer,org.springframework.scheduling.TaskScheduler,java.util.concurrent.ScheduledExecutorService; SearchStrategy: all) did not find any beans (OnBeanCondition)

Negative matches:
—————–

None

Exclusions:
———–

None

Unconditional classes:
———————-

None

16:02:28
16:02:33
16:02:38
16:02:43
16:02:48
16:02:53
16:02:58
16:03:03
16:03:08
16:03:13
16:03:18
16:03:23
16:03:28
16:03:33
16:03:38

なんやこれは、すげえ

[SpringBoot2.4.3] MessageSource

application.propertiesの設定
メッセージを messages_ja.properties で設定できるようにする
application.properties

spring.messages.basename=messages
spring.messages.cache-seconds=-1
spring.messages.encoding=UTF-8

messages_ja.properties

key=\u3053\u3093\u306B\u3061\u306F\u3002

Controller

String message = msg.getMessage("key",null,Locale.JAPAN);

@RequestMapping(value="/msg", method=RequestMethod.GET)
public Map<String, String> msg(Locale locale){
	String message = msg.getMessage("key", null, locale);
	return Collections.singletonMap("message", message);
}

なるほどー

[SpringBoot2.4.3] Mustacheを使う

MustacheとはSpring Bootのテンプレートエンジンです。

pom.xml

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

controller

package com.example.demo;

import java.util.Date;
import java.util.Map;

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

@Controller
public class HelloController {
	
	@RequestMapping("/hello-mst")
	public String hello(
			@RequestParam(defaultValue="World")String name,
			Map<String, Object> model
			) {
		model.put("name", name);
		model.put("date", new Date());
		return "hello-mst";
	}
}

html

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Hello Mustache</title>
</head>
<body>
	<div>
		<p><b>Message:</b>Hello, {{name}}</p>
		<p><b>Date:</b>{{date}}</p>
	</div>
</body>
</html>

あら、うまくいかんね。

[SpringBoot2.4.3] バリデーション

package com.example.demo;

import javax.validation.Valid;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
	
	@RequestMapping(value="/address", method=RequestMethod.POST)
	public Address create(@Valid @RequestBody Address address) {
		return address;
	}
	
	public static class Address {
		
		@NotEmpty
		@Size(min=7, max=7)
		public String zip;
		
		@NotEmpty
		public String address;
	}
}

[SpringBoot2.4.3] ファイルアップロード

htmlファイル
src/main/resources/public/file-upload.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>File Upload</title>
	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css">
</head>
<body>
<div class="">
	<form id="form" enctype="multipart/form-data">
		<p><input type="file" name="file"></p>
		<p><input type="button" id="upload" value="upload"></p>
	</form>
	<span id="result" style="padding:3px"></span>

</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.1/umd/popper.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.0.0-alpha1/js/bootstrap.min.js"></script>
<script
  src="https://code.jquery.com/jquery-3.5.1.js"
  integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
  crossorigin="anonymous"></script>
<script>
$(function(){
	$('#upload').click(function(){
		var formData = new FormData(
			$('#form').get()[0]
		);
		$.ajax({
			url:'/upload',
			method:'post',
			data:formData,
			processData:false,
			contentType:false,
			cache: false
		}).done(function(data,status,jqxhr){
			$('#result').text('結果: 成功');
		}).fail(function(data, status, jqxhr){
			$('#result').text('結果: 失敗');
		});
	});
});
</script>
</body>
</html>

Controller

package com.example.demo;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.servlet.http.HttpServletResponse;

import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
public class HelloController {
	
	@RequestMapping(value="/upload", method=RequestMethod.POST)
	public void handle(
			HttpServletResponse response,
			@RequestParam MultipartFile file
			) {
		if(file.isEmpty()) {
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return;
		}
		try {
			BufferedInputStream in = new BufferedInputStream(file.getInputStream());
			BufferedOutputStream out = new BufferedOutputStream(
					new FileOutputStream(file.getOriginalFilename()));
		} catch (IOException e) {
			throw new RuntimeException("Error uploading file.", e);
		}
	}

publicに置くと、routingしなくて良いのか。。

ほう、こんなんなってるのか。。

[SpringBoot2.4.3] Serviceクラスを使用する

.com.example.demo.service
RandomNumberService.java

package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class RandomNumberService {
	
	public int zeroToNine() {
		return (int)(Math.random() * 10);
	}
}

HelloController.java

package com.example.demo;

import java.util.Collections;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.service.RandomNumberService;

@RestController
public class HelloController {
	
	@Autowired
	RandomNumberService random;

	@RequestMapping("/random")
	public Map<String, Integer>random(){
		int value = random.zeroToNine();
		return Collections.singletonMap("value", value);
	}
}

http://localhost:8080/random
{“value”:2}

autowiredで呼び出すのか。。

bootstrap5でタブメニューを作りたい

nav-tabsとnav-itemで作る

<ul class="nav nav-tabs">
		<li class="nav-item">
			<a class="nav-link active">タブ1</a>
		</li>
		<li class="nav-item">
			<a class="nav-link">タブ2</a>
		</li>
		<li class="nav-item">
			<a class="nav-link">タブ3</a>
		</li>
		<li class="nav-item">
			<a class="nav-link">タブ4</a>
		</li>
	</ul>

これでもうタブが出来る

	<ul class="nav nav-tabs">
		<li class="nav-item">
			<a href="#tab1" class="nav-link active" data-toggle="tab">タブ1</a>
		</li>
		<li class="nav-item">
			<a href="#tab2" class="nav-link" data-toggle="tab">タブ2</a>
		</li>
		<li class="nav-item">
			<a href="#tab3" class="nav-link" data-toggle="tab">タブ3</a>
		</li>
		<li class="nav-item">
			<a href="#tab4" class="nav-link" data-toggle="tab">タブ4</a>
		</li>
	</ul>
	<div class="tab-content">
		<div id="tab1" class="tab-pane active">
			タブ1のコンテンツが入ります
		</div>
		<div id="tab2" class="tab-pane">
			タブ2のコンテンツが入ります
		</div>
		<div id="tab3" class="tab-pane">
			タブ3のコンテンツが入ります
		</div>
		<div id="tab4" class="tab-pane">
			タブ4のコンテンツが入ります
		</div>
	</div>

おお、かなり凄え

### Bootstrap入門本
Bootstrapは、あんまり本を買って勉強するイメージはないけど、多用されている場合はあった方が良いかも..

[SpringBoot2.4.3] Jsonを返すシンプルなスクリプト

HelloController.java

package com.example.demo;

import java.util.Collections;
import java.util.Map;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

	@RequestMapping("/hello")
	public Map<String, String>hello(){
		return Collections.singletonMap("message", "Hello, World!");
	}
}

ここまでは特に何でもありません。

[SpringBoot2.4.3] ログイン機能を実装する2

psql -U root test

CREATE TABLE employee (
id SERIAL NOT NULL,
name varchar(255),
password varchar(255),
PRIMARY KEY(id)
);

EmployeeMapper.java

@Select({
	"select * from employee where name = #{name} limit 1"
})
Employee selectByName(String name);

SecurityConfig.java

package com.example.demo.security;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/webjars/**", "/css/**");
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.antMatchers("/login").permitAll()
				.anyRequest().authenticated()
			.and()
			.formLogin()
				.loginProcessingUrl("/login")
				.loginPage("/login")
				.failureUrl("/login?error")
				.defaultSuccessUrl("/menu", true)
				.usernameParameter("name")
				.passwordParameter("password")
			.and()
			.logout()
				.logoutSuccessUrl("/login");
	}
	
	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
}

LoginUserDetails.java

package com.example.demo.security;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;

import com.example.demo.domain.Employee;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper=false)
public class LoginUserDetails extends User {
	private final Employee employee;
	
	public LoginUserDetails(Employee employee, String role) {
		super(employee.getName(), employee.getPassword(), AuthorityUtils.createAuthorityList(role));
		this.employee = employee;
	}
}

LoginUserDetailsService.java

package com.example.demo.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.example.demo.Employee;
import com.example.demo.mybatis.mapper.EmployeeMapper;

@Service
public class LoginUserDetailsService implement UserDetailsService {
	@Autowired
	EmployeeExample employeeExample;
	
	@Autowired
	EmployeeMapper employeeMapper;
	
	@Override
	public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
		Employee employee = employeeMapper.selectByName(name);
		
		if (employee == null) {
			throw new UsernameNotFoundException("Wrong email or password");
		}
		
		String role = "ROLE_ADMIN";
		
		return new LoginUserDetails(employee, role);
	}
}