springboot + maven + java vs springboot + gradle + kotlin
快速搭建:https://start.spring.io/
springclould:netflix:
java:
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.0modelVersion> <groupId>com.flygroupId> <artifactId>springboot01artifactId> <version>1.0-SNAPSHOTversion> <parent> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-parentartifactId> <version>2.0.1.RELEASEversion> <relativePath/> parent> <properties> <java.version>1.8java.version> properties> <dependencies> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> dependency> <dependency> <groupId>tk.mybatisgroupId> <artifactId>mapper-spring-boot-starterartifactId> <version>2.0.3version> dependency> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <scope>runtimescope> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-testartifactId> <scope>testscope> dependency> dependencies> project>
kotlin:
build.gradle:
buildscript {
ext {
kotlinVersion = '1.2.71'
springBootVersion = '2.1.1.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
}
}
apply plugin: 'kotlin'
apply plugin: 'kotlin-spring'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
compileTestKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}
repositories {
mavenCentral()
}
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
implementation('com.fasterxml.jackson.module:jackson-module-kotlin')
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("tk.mybatis:mapper-spring-boot-starter:2.0.3")
implementation("mysql:mysql-connector-java:5.1.6")
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
java:8088
kotlin:8089
application.yml:
server:
port: 8088
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mybatisdemo
username: root
password: root
hikari: # jdbc默认连接池
maximum-pool-size: 20
minimum-idle: 10
mybatis:
type-aliases-package: com.fly.pojo
logging:
level:
com.fly.service: error
App:
java:
@SpringBootApplication @MapperScan("com.fly.mapper") public class App { public static void main(String[] args){ SpringApplication.run(App.class,args); } } kotlin: @SpringBootApplication @MapperScan("com.fly.mapper") class Application{ @Bean fun restTemplate():RestTemplate = RestTemplate() } fun main(args: Array) { // SpringApplication.run(Application::class.java,*args) runApplication (*args) }
pojo.User:
java:
@Data @Table(name = "user") public class User { @Id @KeySql(useGeneratedKeys = true)//插入的表以自增列为主键 @GeneratedValue(strategy = GenerationType.IDENTITY)//主键生成策略 @Column(name = "id") private Integer id; private String username; private Date birthday; private String sex; private String address; }
kotlin:
@Table(name = "user")
data class User(
@Id
@KeySql(useGeneratedKeys = true)
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
var id: Int? = null,
var username: String? = null,
var birthday: Date? = null,
var sex: String? = null,
var address: String? = null
)
UserMapper:
java:
public interface UserMapper extends Mapper{ }
kotlin:
interface UserMapper:Mapper
UserService:
java:
@Service public class UserService { @Resource private UserMapper userMapper; public ListselectAll(){ return userMapper.selectAll(); } @Transactional public void insert(User user){ userMapper.insert(user); } }
kotlin:
@Service
class UserService {
@Resource
lateinit var userMapper:UserMapper
fun selectAll():List
@Transactional
fun insert(user: User) = userMapper.insert(user)
}
UserController:
java:
@RestController public class UserController { @Resource private UserMapper userMapper; private final UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @GetMapping("/") public ListselectAll(){ return userService.selectAll(); } @GetMapping("/{id}") public User selectById(@PathVariable("id")int id){ return userMapper.selectByPrimaryKey(id); } }
kotlin:
@RestController
class UserController {
@Autowired
lateinit var userService: UserService
@Autowired
lateinit var restTemplate:RestTemplate
@GetMapping
fun selectAll(): List
@GetMapping("/{id}")
fun httpSelectAll(@PathVariable("id") id:Int):User?{
return restTemplate.getForObject("http://localhost:8088/"+id, User::class.java)
}
}
MyInterceptor:
java:
/** * 拦截器 */ @Slf4j public class MyInterceptor implements HandlerInterceptor { // private Logger log = LoggerFactory.getLogger(MyInterceptor.class); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("preHandle......"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("postHandle....."); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("afterCompletion....."); } }
kotlin:
class MyInterceptor :HandlerInterceptor{
var log:Logger = LoggerFactory.getLogger(MyInterceptor::class.java)
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
log.info("preHandle......")
return true
}
override fun postHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any, modelAndView: ModelAndView?) {
log.info("postHandle......")
}
override fun afterCompletion(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception?) {
log.info("afterCompletion......")
}
}
MvcConfig:
java:
/** * 注册拦截器 */ @Configuration public class MvcConfig implements WebMvcConfigurer { @Bean public MyInterceptor myInterceptor(){ return new MyInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { //注册拦截器,添加拦截路径 registry.addInterceptor(myInterceptor()).addPathPatterns("/**"); } }
kotlin:
@Configuration
class MvcConfig :WebMvcConfigurer{
@Bean
fun myInterceptor():MyInterceptor = MyInterceptor()
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(myInterceptor()).addPathPatterns("/**")
}
}
测试:
java:
@RunWith(SpringRunner.class) @SpringBootTest(classes = App.class)//()内容可省略 public class UserServiceTest { @Autowired private UserService userService; @Test public void test() { Listlist = userService.selectAll(); for (User user : list) { System.out.println(user); } } @Test public void test1(){ User user = new User(); user.setUsername("测试a"); user.setBirthday(new Date()); user.setSex("1"); user.setAddress("A"); userService.insert(user); } }
kotlin:
@RunWith(SpringRunner::class)
@SpringBootTest(classes = arrayOf(Application::class))
class UserServiceTest {
@Autowired
lateinit var userService:UserService
@Test
fun test(){
val list:List
for (user in list) {
println(user)
}
}
@Test
fun test1(){
val user = User()
user.username = "Kotlin111"
user.address = "K"
user.sex = "1"
user.birthday = Date()
userService.insert(user)
}
}
netflix:
Eureka:注册中心
Zuul:服务网关
Ribbon:负载均衡
Feign:服务调用
Hystix:熔断器
Eureka:注册中心
grade+java搭建eureka服务,客户端分别为上面的java+kotlin
eureka服务端:
build.gradle:
buildscript {
ext {
springBootVersion = '2.1.1.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.fly'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven { url "https://repo.spring.io/milestone" }
}
ext['springCloudVersion'] = 'Greenwich.RC2'
dependencies {
implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server')
testImplementation('org.springframework.boot:spring-boot-starter-test')
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
application.yml:
server:
port: 8761 # 默认8761
spring:
application:
name: eureka-server
eureka:
client:
register-with-eureka: false # 注册自己的信息到EurekaServer
fetch-registry: false # 拉取其它服务的信息
service-url:
defaultZone: http://127.0.0.1:${server.port}/eureka
server:
eviction-interval-timer-in-ms: 60000 # 扫描失效服务的间隔时间 失效剔除 单位是毫秒,生成环境不要修改
enable-self-preservation: false # 关闭自我保护 默认为true
EurekaApplication:
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args){
SpringApplication.run(EurekaApplication.class,args);
}
}
客户端:
java:
pom.xml:
application.yml:
server:
port: 8088
spring:
application:
name: springboot01
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mybatisdemo
username: root
password: root
hikari: # jdbc默认连接池
maximum-pool-size: 20
minimum-idle: 10
mybatis:
type-aliases-package: com.fly.pojo
logging:
level:
com.fly.service: error
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:8761/eureka
registry-fetch-interval-seconds: 30 # 默认值 获取服务列表
instance:
prefer-ip-address: true # 当其它服务获取地址时提供ip而不是hostname
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
# 修改服务续约的行为
lease-expiration-duration-in-seconds: 90 # 服务失效时间,默认值90秒
lease-renewal-interval-in-seconds: 30 # 服务续约(renew)的间隔,默认为30秒
instance-id: ${spring.application.name} + ${server.port} # 默认显示的信息${hostname}:${spring.application.name}:${server.port}
App部分:
@SpringBootApplication
//@EnableEurekaClient //开启eureka客户端
@EnableDiscoveryClient //客户端,支持多种
@MapperScan("com.fly.mapper")
public class App {
服务的消费方:
build.gradle:
implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:2.0.2.RELEASE')
application.yml:
server:
port: 8089
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mybatisdemo
username: root
password: root
application:
name: demo
main:
allow-bean-definition-overriding: true # 同名bean覆盖
mybatis:
type-aliases-package: com.fly.pojo
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka
registry-fetch-interval-seconds: 30 # 默认值 获取服务列表
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
Application.kt:
@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.fly.mapper")
class Application{
@Bean
fun restTemplate():RestTemplate = RestTemplate()
}
fun main(args: Array
// SpringApplication.run(Application::class.java,*args)
runApplication
}
UserService:
@Autowired
lateinit var restTemplate: RestTemplate
@Autowired
lateinit var discoveryClient:DiscoveryClient
fun queryUserById(id: Int):User?{
// 根据服务名称,获取服务实例
// 因为只有一个UserService,因此我们直接get(0)获取
val instances = discoveryClient.getInstances("springboot01").get(0)
// 获取ip和端口信息
val url:String = "http://"+instances.host + ":" + instances.port +"/"+ id
return restTemplate.getForObject(url, User::class.java)
}
UserController:
@GetMapping("/user/{id}")
fun eurekaQueryUserById(@PathVariable("id") id: Int):User?{
return userService.queryUserById(id)
}
Ribbon:负载均衡:
负载均衡:
spring-cloud 的 netfix已经包含,无需再导入
org.springframework.cloud:spring-cloud-starter-netflix-ribbon
Application.kt:
@Bean
@LoadBalanced // 开启负载均衡
fun restTemplate():RestTemplate = RestTemplate()
UserService:
// 负载均衡 使用 @LoadBalanced
fun queryUserById(id: Int):User?{
// url直接写服务名,LoadBalancerInterceptor会根据服务名获取服务实例的ip与端口
val url = "http://springboot01/"
return restTemplate.getForObject(url+id, User::class.java)
}
重试机制
build.gradle:
// 重试机制
implementation('org.springframework.retry:spring-retry:1.2.2.RELEASE')
application.yml部分:
server:
port: 8089
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mybatisdemo
username: root
password: root
application:
name: demo
main:
allow-bean-definition-overriding: true # 同名bean覆盖
cloud:
loadbalancer:
retry:
enabled: true # 开启Spring Cloud的重试功能
springboot01: # 服务名称 负载均衡规则的配置入口
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 默认轮训 值为IRule的实现类
ConnectTimeout: 250 # Ribbon的连接超时时间
ReadTimeout: 1000 # Ribbon的数据读取超时时间
OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
MaxAutoRetries: 1 # 对当前实例的重试次数
Hystix:熔断器:
熔断器:
build.gradle:
//熔断器
implementation('org.springframework.cloud:spring-cloud-starter-netflix-hystrix:2.0.2.RELEASE')
Application.kt:
//@SpringBootApplication
//@EnableCircuitBreaker //熔断
//@EnableDiscoveryClient //注册客户端
//@EnableEurekaClient
@SpringCloudApplication //包含上面前3
UserService部分:
@Service
//@DefaultProperties(defaultFallback = "queryUserByIdFallBack")
class UserService {
@Autowired
lateinit var restTemplate: RestTemplate
// 负载均衡 使用 @LoadBalanced
@HystrixCommand(fallbackMethod = "queryUserByIdFallBack") //失败回滚 默认是1000毫秒
// @HystrixCommand(commandProperties =
// [HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000")] //超时时间
// )
// @HystrixCommand
fun queryUserById(id: Int):User?{
val begin:Long = System.currentTimeMillis()
// url直接写服务名,LoadBalancerInterceptor会根据服务名获取服务实例的ip与端口
val url = "http://springboot01/"
val user:User? = restTemplate.getForObject(url+id, User::class.java)
val end:Long = System.currentTimeMillis()
val t:Long = end - begin
println("访问时间:$t")
return user
}
fun queryUserByIdFallBack():User?{
val user = User()
user.username = "服务器太拥挤了!!!"
return user
}
fun queryUserByIdFallBack(id: Int):User?{
val user = User()
user.username = "服务器太拥挤了!!!"
return user
}
配置文件方式配置:
application.yml:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 # 设置hystrix的超时时间为3000ms
服务提供方模拟超时:
@GetMapping("/")
public List
return userService.selectAll();
}
@GetMapping("/{id}")
public User selectById(@PathVariable("id")int id) throws InterruptedException {
Thread.sleep(2000L); //模拟超时
return userMapper.selectByPrimaryKey(id);
}
断路器配置:
@HystrixCommand(commandProperties =
[
//具体参数位置 HystrixCommandProperties.java
/* 状态机的三种状态
* Closed:关闭状态(断路器关闭),所有请求都正常访问
* Open:打开状态(断路器打开),所有请求都被降级,hystix会对请求情况进行计数,当失败的百分比达到阀值
* ,则触发熔断,断路器完全关闭
* Half Open:半开状态,Closed状态不是永久的,关闭后会进入休眠,随后断路器自动进入半开状态,
* 此时会释放部分请求通过。若请求是健康的,则会完全打开断路器,否则保持关闭,再进行休眠计时
*
* */
HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"), //执行超时时间 默认1000
HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 触发熔断器的最小请求次数 默认20
HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 休眠时长 默认5000毫秒
HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50") //触发熔断的失败请求最小占比 默认50%
] //超时时间
)
Feign:服务调用:
Feign:(伪装)
build.gradle:
// feign
implementation('org.springframework.cloud:spring-cloud-starter-openfeign:2.0.2.RELEASE')
Application.kt:
@EnableFeignClients // 开启Feign功能
feign,熔断器设置:
UserFeignClient:
@FeignClient(value = "springboot01",fallback = UserFeignClientFallback::class,configuration = [FeignConfig::class])
interface UserFeignClient {
@GetMapping("/{id}")
fun feignQueryUserById(@PathVariable("id")id:Int): User
}
UserFeignClientFallback:
@Component
class UserFeignClientFallback:UserFeignClient {
override fun feignQueryUserById(id: Int): User {
val user = User()
user.username = "未知用户???"
return user
}
}
UserController:
@Resource
lateinit var userFeignClient: UserFeignClient
@GetMapping("/feignUser/{id}")
fun feignQueryUserById(@PathVariable("id") id: Int):User{
return userFeignClient.feignQueryUserById(id)
}
负载均衡,熔断器,日志设置:
application.yml:
feign:
hystrix:
enabled: true # 开启Feign的熔断功能 默认关闭
ribbon: # feign的ribbon的配置 也可以使用上面ribbon的配置方式
ConnectTimeout: 250 # 连接超时时间(ms)
ReadTimeout: 3000 # 通信超时时间(ms)
# OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
# MaxAutoRetriesNextServer: 1 # 同一服务不同实例的重试次数
# MaxAutoRetries: 1 # 同一实例的重试次数
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000 # 设置hystrix的超时时间为 ms
logging: # 日志级别
level:
com.fly: debug
请求压缩:
feign:
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型 默认
min-request-size: 2048 # 设置触发压缩的大小下限 默认
response:
enabled: true # 开启响应压缩
Zuul:服务网关:
面向服务:
pom.xml:
ZuulApplication:
@SpringBootApplication
@EnableZuulProxy //开启Zuul的功能
@EnableDiscoveryClient
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
application.yml:
server:
port: 9901
spring:
application:
name: zuul
#zuul:
# routes:
# springboot01: # 路由id,随意写 将 /user/**开头的请求,代理到http://127.0.0.1:8087
# path: /user/** # 这里是映射路径
## url: http://127.0.0.1:8087 # 映射路径对应的实际url地址
# serviceId: springboot01 # 指定服务名称 ,会利用Ribbon进行负载均衡访问
# 简化的路由配置
#zuul:
# routes:
# springboot01: springboot01/** # 服务名与映射地址相同可以省略
# 路由前缀
zuul:
# prefix: /api # 添加路由前缀
routes:
springboot01:
path: /user/**
serviceId: springboot01
retryable: true # 重试 默认false
# ignored-services: # 忽略的服务
# - springboot02
# - springboot03
# 熔断
ribbon: # ribbon的配置 详见 AbstractRibbonCommand.java
ConnectTimeout: 250 # 连接超时时间(ms) 默认1000
ReadTimeout: 2000 # 通信超时时间(ms) 默认1000
OkToRetryOnAllOperations: true # 是否对所有操作重试
MaxAutoRetriesNextServer: 1 # 同一服务不同实例的重试次数 默认1
MaxAutoRetries: 0 # 同一实例的重试次数 默认0
# 负载均衡
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 # 熔断超时时长:6000ms > (ReadTimeout+ConnectTimeout)*2,ribbon默认重试1次
# ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1)
# (250 + 2000)* (0+1)*(1+1)
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:8761/eureka
registry-fetch-interval-seconds: 30 # 默认值 获取服务列表
instance:
prefer-ip-address: true # 当其它服务获取地址时提供ip而不是hostname
ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
自定义过滤器:
/**
* 自定义过滤器
*/
@Component
public class LoginFilter extends ZuulFilter {
@Override
public String filterType() {//返回字符串,代表过滤器的类型
/**
* - pre:请求在被路由之前执行
* - routing:在路由请求时调用
* - post:在routing和errror过滤器之后调用
* - error:处理请求时发生错误调用
*/
// return "pre";
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {//通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;//4
}
@Override
public boolean shouldFilter() {//返回一个`Boolean`值,判断该过滤器是否需要执行。返回true执行,返回false不执行
return true;
}
@Override
public Object run() throws ZuulException {//过滤器的具体业务逻辑
// 获取Zuul提供的请求上下文对象
RequestContext context = RequestContext.getCurrentContext();
// 从上下文中获取request对象
HttpServletRequest request = context.getRequest();
String token = request.getParameter("user-token");
//判断是否存在
if (StringUtils.isBlank(token)){
context.setSendZuulResponse(false);
context.setResponseStatusCode(HttpStatus.FORBIDDEN.value());//403
}
return null;
}
}