SPRING 扩展组件
1、spring data
2、spring security
3、自动配置
bean装配、属性注入
配置HTTPS
自定义keystore - 使用RSA
$ keytool -keystore mykeys.jks -genkey -alias tomcat -keyalg RSA
在配置文件中加载https
# 配置SSL # ssl: # key-store: classpath:mykeys.jkd # key-store-password: lipengfei # key-password: lipengfei
4、配置(注入)属性
package com.jony.boot5.boottest.util; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import javax.validation.constraints.Max; import javax.validation.constraints.Min; @Data @Component @ConfigurationProperties(prefix = "taco.orders") /** * 使用bean 配置为一个配置类 */ @Validated // 编译验证 public class OrderProps { @Max(value = 30) @Min(value = 5) private int pageSize; }
taco:
orders:
pageSize: 10
配置属性元数据
1)依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
2)文件(idea自动提示可创建)
{ "properties": [ { "name": "taco.orders.pageSize", "type": "java.lang.String", "description": "Description for taco.orders.pageSize." }, { "name": "taco.orders.rowNum", "type": "java.lang.String", "description": "Description for taco.orders.rowNum." } ] }
3)profile (配置模式切换)
spring.profiles.active={profile-name}
创建模式文件-规则 application-{profile-name}.properties 或 application-{profile-name}.yml
在YAML 中可以通过‘---’三横线创建profiles 模式
taco: orders: rowNum: 20 pageSize: 20 # yml 支持 使用 application-{profie名}.xml 和在同一个文件中设置数据(使用三个分割线 ---) --- spring: profiles: prod server: port: 8080 taco: orders: pageSize: 10
在环境变量中设置profiles
linux :export SPRING_PROFILES_ACTIVE=prod,dev
windows:
在windows环境下通过jar启动会加载环境变量,在idea开发环境中不生效
4)bean 的profile 创建使用@profile注解
5、rest 服务
通过rest服务实现前后端分离
使用postman测试接口服务
put 与patch
put - 针对全部替换
patch - 针对部分更新
hibernate - id-uuid 序列
@Id @GenericGenerator(name = "idGenerator", strategy = "uuid") @GeneratedValue(generator = "idGenerator") private String id;
@PrePersist 、@PreUpdate
hibernate缓存ResultSet
@PrePersist 在数据持久化(create)的情况下调用
@PreUpdate 在数据库中数据发生变化下调用
package com.jony.boot5.boottest.controller; import com.jony.boot5.boottest.entity.Country; import com.jony.boot5.boottest.repository.CountryRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.Optional; @RestController //处理rest请求,accept 内容为json格式 @RequestMapping(value = "/rest", produces = {"application/json"}) //允许跨域请求 @CrossOrigin(origins = {"*"}) public class RestfulController { @Autowired private CountryRepository repository; @GetMapping public Iterable findAll() { return repository.findAll(); } @GetMapping(path = "/{id}") public ResponseEntity<Country> findById(@PathVariable("id") String id) { Optional<Country> optional = repository.findById(id); // 返回状态码 if (optional.isPresent()) { // 返回ok return new ResponseEntity<Country>(optional.get(), HttpStatus.OK); } //返回404 return new ResponseEntity<>(null, HttpStatus.NOT_FOUND); } /** * 处理post请求 * * @return */ @PostMapping(consumes = {"application/json"}) // consumes 指定用户输入的json数据 @ResponseStatus(code = HttpStatus.CREATED) // 返回created 状态码 public Country postCountry(@RequestBody Country country) { Country result = repository.save(country); return result; } @PutMapping(path = "/{id}",consumes = {"application/json"}) /** * 全部更新或插入 */ public Country putCountry(@RequestBody Country country,@PathVariable("id") String id){ country.setId(id); return repository.save(country); } @PatchMapping(path = "/{id}") /** * 更新 */ public Country patchCountry(@PathVariable("id")String id,@RequestBody Country patch){ Country country = repository.findById(id).get(); if(country!=null){ // IF 判断设置 } return repository.save(country); } }
针对restful - csrf 伪造
解决方法:
1、实现RequestMacher 接口
2、每次请求中设置csrf 值
3、使用oauth2
package com.jony.boot5.boottest.util; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @Component /** * 注册bean 重写matcher 组件 */ @ConfigurationProperties(prefix = "custom.csrf") public class CsrfRequestMacher implements RequestMatcher { private final HashSet<String> allowedMethods = new HashSet<>( Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS")); private Set<String> excludeUrls; public void setExcludeUrls(Set<String> excludeUrls) { this.excludeUrls = excludeUrls; } @Override public boolean matches(HttpServletRequest request) { // 如果是设置在允许范围内的post或put 等请求则直接放过 for (String exclude:excludeUrls) { if(request.getServletPath().startsWith(exclude)){ return false; } } // if (excludeUrls.contains(request.getServletPath())) { // return false; // } return !this.allowedMethods.contains(request.getMethod()); } }
6、超媒体链接(数据显示自身信息与链接)
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-hateoas</artifactId> </dependency>
关于HATEOAS 稳定新版本更改部分接口的名称
新建资源类
package com.jony.boot5.boottest.entity; import lombok.Data; import org.hibernate.annotations.GenericGenerator; import org.springframework.hateoas.RepresentationModel; import javax.persistence.*; import java.sql.Timestamp; //JPA entity @Data //@NoArgsConstructor(access=AccessLevel.PRIVATE) @Entity //@Table(name = "country") public class CountryResource extends RepresentationModel { private String countryname; private String countrycode; private byte[] img; private Timestamp now; @Column(name = "update_date") private Timestamp update; @PrePersist void createDate() { now = new Timestamp(System.currentTimeMillis()); } @PreUpdate void updateDate(){ update = new Timestamp(System.currentTimeMillis()); } }
package com.jony.boot5.boottest.entity.assembler; import com.jony.boot5.boottest.controller.RestfulController; import com.jony.boot5.boottest.entity.Country; import com.jony.boot5.boottest.entity.CountryResource; import org.springframework.hateoas.server.RepresentationModelAssembler; import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport; /** * 资源装配器 */ public class CountryResourceAssembler extends RepresentationModelAssemblerSupport<Country, CountryResource> { /** * Creates a new {@link RepresentationModelAssemblerSupport} using the given controller class and resource type. * * @param controllerClass must not be {@literal null}. * @param resourceType must not be {@literal null}. */ public CountryResourceAssembler(Class<?> controllerClass, Class<CountryResource> resourceType) { super(controllerClass, resourceType); } public CountryResourceAssembler() { super(RestfulController.class,CountryResource.class); } @Override protected CountryResource instantiateModel(Country entity) { return new CountryResource(entity); } @Override public CountryResource toModel(Country entity) { return createModelWithId(entity.getId(),entity); } }
@GetMapping("/resource") // public CollectionModel<EntityModel<Country>> getAllOnLinks() { public CollectionModel<CountryResource> getAllOnLinks() { // 获取资源列表 List<Country> countries = repository.findAll(); // 包装返回数据 // CollectionModel<EntityModel<Country>> resources = CollectionModel.wrap(countries); // 使用装配器为每一条数据添加链接 // 在包装的过程中转换资源 CollectionModel<CountryResource> resources = new CountryResourceAssembler().toCollectionModel(countries); // 添加linkes // resources.add( // WebMvcLinkBuilder.linkTo(RestfulController.class) // .slash("resource") // .withRel("resources") // ); resources.add( linkTo(methodOn(getClass()).getAllOnLinks()) .withRel("resources") ); return resources; }
配置rest 自动端点
加载依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency>
只需实体与repository 接口即可生成对应接口
package com.jony.boot5.boottest.repository; import com.jony.boot5.boottest.entity.RestData; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.web.bind.annotation.CrossOrigin; @CrossOrigin(origins="*") public interface RestDataRepository extends PagingAndSortingRepository<RestData,String> { }
配置rest api 基础端点
spring:
data:
rest:
base-path:/api
获取可访问的rest资源描述
http://localhost:8080/api
{ "_links": { "restdata": { "href": "http://localhost:8080/api/rs{?page,size,sort}", "templated": true }, "countries": { "href": "http://localhost:8080/api/countries" }, "sysUsers": { "href": "http://localhost:8080/api/sysUsers" }, "profile": { "href": "http://localhost:8080/api/profile" } } }
针对rest 资源设置别名
package com.jony.boot5.boottest.entity; import lombok.Data; import org.hibernate.annotations.GenericGenerator; import org.springframework.data.rest.core.annotation.RestResource; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.PrePersist; import java.sql.Timestamp; @Data @Entity @RestResource(path = "rs",rel = "restdata") public class RestData { @Id @GenericGenerator(strategy = "uuid",name = "ugen") @GeneratedValue(generator = "ugen") private String id; private String name; private Timestamp createDate; @PrePersist void init(){ createDate = new Timestamp(System.currentTimeMillis()); } }
分页与排序(repository 集成 pagingAndSortingRepository)
URL 查询分页(sort 针对一个属性,页数从0开始)
localhost:8080/api/rs?page=0&size=2&sort=name,desc
配置自定义端点(特殊业务逻辑)
package com.jony.boot5.boottest.controller; import com.jony.boot5.boottest.entity.RestData; import com.jony.boot5.boottest.repository.RestDataRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.rest.webmvc.RepositoryRestController; 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 java.util.Optional; @RepositoryRestController public class RestfulDataController { @Autowired private RestDataRepository repository; @GetMapping(path = "/rs/sign/{id}") public ResponseEntity<RestData> takeRestData(@PathVariable("id") String id){ Optional<RestData> data = repository.findById(id); return new ResponseEntity<RestData>(data.get(),HttpStatus.OK); } }
在path中第一个分隔path 必须与api 中已经存在的资源相符(匹配已有的rest服务端点),否则不会成功注册
返回的数据要么使用ResponseEntity 封装,要么加上@RespouseBody注解