0%

文章字数:324,阅读全文大约需要1分钟

application.properties是保存spring配置的地方。springBoot提供了很多模块,只需要在pom中引入模块化的starter POMs(spring-boot-starter-*,ru dara-jpa这些spring整合的模块)。除了这些模块的应用外,还能有其他特殊的使用场景。

自定义属性及加载

1
2
//key=value 类型
xxx.demo.test=test

在变量上加上注解,自动加载值

1
2
3
//属性名
@Value("${xxx.demo.test}")
private String testValue

参数间引用

1
2
xxx.demo.test=test
xxx.demo.info=this is a ${xxx.demo.test}

随机数

1
2
3
4
5
6
7
8
9
10
# 随机字符串
xxx.demo.test=${random.value}
# 随机int
xxx.demo.test=${random.int}
# 随机long
xxx.demo.test=${random.long}
# 10以内随机数
xxx.demo.test=${random.int(10)}
# 10-20间随机数
xxx.demo.test=${random.init(10,20)}

屏蔽命令行修改属性值

java -jar xxx.jar --server.port=888等价于在配置文件中设置xxx.jar,--是对于配置属性赋值的标识。

  • 屏蔽修改属性
    1
    SpringApplication.setAddCommandLineProperties(false)

    多环境配置

    就是生产、测试、开发环境都用各自的配置文件

  • application-dev.properties:开发环境
  • application-test.properties:测试环境
  • application-prod.properties:生产环境
    在主配置文件application.propertiesspring.profiles.active配置机体需要那一份配置。(rest,dev,prod)
    也可以命令行选则
    1
    java -jar xxx.jar --spring.profiles.active=test

文章字数:352,阅读全文大约需要1分钟

spring提供了org.springframework.test这个对于JUnit简单封装的框架,可以在spring搭建的项目上方便的进行单元测试。

依赖

自动创建的项目默认添加,底层默认使用Junit4

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@RunWith(SpringRunner.class)//设置启动器
@SpringBootTest(classes={Application.class})//指定启动类
//@SpringApplicationConfiguration(classes=Application.class)//1.4.0之前版本
public class ApplicationTests{
@Test
public void test1(){
//..测试内容
}

@Test
public void testAsser(){
//JUnit4.1.2后Assert过时,TestCase.assertEquals成为替代
//(期望结果,条件语句)
//("提示",true,条件语句)
TestCase.assertEquals(1, 1);
}

@Before
public testBefore(){
//测试前执行
}

@After
public testAfter(){
//测试结束后
}
}

JUnit基本注解使用

  1. @BeforeClass在所有测试方法前执行一次,一般是初始化
  2. @AfterClass在所有测试方法后执行一次
  3. @Before每个测试方法执行前执行,一般用于初始化或者重置数据
  4. @After每个测试方法执行之后执行
  5. @Test(timeout = 1000)测试方法超过1000毫秒后算是超时,测试失败
  6. @Test(expected = Exception.class)测试方法期望得到的异常类,如果方法执行没有抛出指定异常,测试失败。
  7. @Ignore("not redy yet")执行测试时忽略此方法/类
  8. @Test一般测试用例
  9. @RunWith选择Runner调试代码,不同Runner有各自的功能。如果只是普通的java程序,忽略@RunWith

整理自原文


文章字数:390,阅读全文大约需要1分钟

graphql是一个结构化查询语言,对应的传统方式是rest api

我的理解

  1. rest api一样,graphql主要是为了满足前后端交互需求而产生的一种规范。

  2. 不同的是rest api在创建时就指定了接收的参数以及返回的数据格式,而graphql则可以根据前端使用的不同的传入参数返回之前可能需要好多个rest api才能返回的数据。

rest api获取信息

1
2
# 获取id为1的人员信息
GET /person/1

graphql

1
2
3
4
5
6
# 获取id为1的人员的firstName
{
person(id:1){
firstName
}
}

graphql能够指定信息的输出,以及输出格式。
比如后端有学生,班级两部分信息。

  • rest api获取信息只能根据前端需求,传入能够定位到对象的信息(如id)然后再放回指定的信息格式。仅需要学生名字和需要所有信息的接口需要分开写。
  • graphql则只用把通过id获取对象的方法给graphql框架,前端通过graphql查询语言就能够在指定的范围内查询数据并且指定输出的格式。

搭建环境

  1. 基于 spring-boot 2.0

    1
    2
    3
    4
    5
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.RELEASE</version>
    </parent>
  2. 引入依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-spring-boot-starter</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>com.graphql-java</groupId>
<artifactId>graphql-java-tools</artifactId>
<version>4.3.0</version>
</dependency>

实现

  1. 定义服务入口

即有哪些内容可以操作

resource/graphql/root.graphqls

Query查询入口
Mutation修改入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Query {
findAllAuthors: [Author]!
countAuthors: Long!
findOneAuthor(id: Long!): Author

findAllBooks: [Book]!
countBooks: Long!
}
type Mutation {
newAuthor(firstName: String!, lastName: String!) : Author!

newBook(title: String!, isbn: String!, pageCount: Int, authorId: Long!) : Book!
saveBook(input: BookInput!): Book!
deleteBook(id: ID!) : Boolean
updateBookPageCount(pageCount: Int!, id: Long!) : Book!
}

resource/graphql/scheme.graphqls

具体的字段定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Author {
id: Long!
createdTime: String
firstName: String
lastName: String
books: [Book]
}
input BookInput {
title: String!
isbn: String!
pageCount: Int
authorId: Long
}
type Book {
id: Long!
title: String!
isbn: String!
pageCount: Int
author: Author
}
  1. 对于定义的入口规则实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Query implements GraphQLQueryResolver {
private AuthorRepository authorRepository;
private BookRepository bookRepository;

public Author findOneAuthor(Long id) {
Optional<Author> opt = authorRepository.findById(id);
return opt.isPresent() ? opt.get() : null;
}
public List<Author> findAllAuthors() {
return authorRepository.findAll();
}
public Long countAuthors() {
return authorRepository.count();
}
public List<Book> findAllBooks() {
return bookRepository.findAll();
}
public Long countBooks() {
return bookRepository.count();
}
}

定义了的Query都要实现


文章字数:1159,阅读全文大约需要4分钟

Spring Cloud的组件有很多,主要记录常用的核心组件的使用场景及原理

一、Eureka

Eureka是微服务架构的注册中心,负责服务的注册与发现。

  1. 使用场景: 微服务架构的各个服务系统部分可能分布在多台机器上,Eureka的作用就是使各个服务直接能够相互调用。

  2. 原理: 每个服务中都集成了一个Eureka Client组件,这个组件负责将当前服务信息发送到Eureka Server中。Eureka Server是注册中心,保存了各个服务的地址信息。各组件可以通过Eureka Client获取其它服务的地址信息,并进行请求。

二、Feign

Feign是一个动态代理,利用aop将连接其它服务,发送网络请求,判断返回值,接收结果等一系列动作封装好。

  1. 使用场景: 调用其它微服务的服务时,接口上使用注解@FeignClient就能创建出一个相关服务的动态代理。接口中声明需要调用的微服务方法,地址等信息。业务中直接调用此接口,Feign就能自动完成远程调用该服务。

  2. 原理: 首先创建一个接口,规范调用的方法。接口上声明@FeignClient并传入服务名,Feign就能通过Eureka获取到该服务的位置。接口方法声明的RequestMapping等和SpringMVC相同的注解则可以定位到方法的具体位置,方法的声明则指定了远程方法的具体格式。最后利用aop将这些动作封装起来。

三、Ribbon

客户端负载均衡,区别于Nginx的服务端负载均衡。客户端负载均衡是访问者(消费者)决定如何访问服务。默认负载均衡算法是轮询算法,一个服务有多台机器上运行时按顺序每次请求分发到各个服务器上。

  1. 使用场景: 当访问量特别大的时候,一台机器无法满足访问需求。这个时候就可以搭建多台机器,所有的机器都运行同一个服务。这些服务运行之后都会注册到注册中心。消费端访问服务时就可以使用Ribbon将访问量平摊到这些机器上。

  2. 原理: 首先从Eureka Server上获取服务注册表,找到服务部署在那些机器上,监听那个端口。然后使用内置算法选出一台机器。最后使用Feign构造并发起请求。

四、Hystrix

隔离、熔断、降级的框架。主要用于服务挂了,导致其它服务未接收相应也卡住。

  1. 使用场景: 防止一个服务挂了之后其它访问的服务也产生连锁反应,保存挂了的服务在重启时间内的被访问数据。

  2. 原理: 首先是隔离Hystrix使用很多独立的线程池分割不同的服务,其中一个服务不可能占用所有的线程并影响其他服务。其次是熔断,检测到服务无法访问,这个针对服务的访问请求在特定时间内会直接返回。这样就不用到超时才返回。最后是降级,每次有请求访问已经挂了的服务就会向数据库中插入一条信息,记录访问的详细情况。方便在服务恢复之后手动恢复宕机期间的数据。

五、Zuul

微服务网关,根据请求的特征将请求分发到相应的服务中。

  1. 使用场景: 方便前端,安卓设备等访问服务。后端程序的调用可以使用Eureka,前端则需要一个统一的入口。有了统一的入口之后降级,限流,认证授权,安全等都可以统一处理。

  2. 原理: 根据请求的特征分发到相应的微服务中。


文章字数:142,阅读全文大约需要1分钟

请求被拦截都会报这个错,前端报错405 Method not allowed。版本问题,springMVC可能隐藏部分报错信息

部分信息spring没有显示出来的可以看这里,而我的问题是url写错了一个字母。

查看详细信息:

  • 进入springMVC核心seveltDispatcherServlet.java
  • 在核心方法doDispatch的异常抛出处打断点
    1
    2
    3
    catch(Exception ex){
    dispatchException = ex;
    }
  • debug处ex->detailMessage=”详细信息”*

问题排查步骤:

  1. url是否正确
  2. 访问方法是否正确
  3. 发送的数据和接收的数据类型是否匹配

文章字数:81,阅读全文大约需要1分钟

摘抄自原文

传统io输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
      @RequestMapping("/download")
public String download( String fileName ,String filePath, HttpServletRequest request, HttpServletResponse response){
response.setContentType("text/html;charset=utf-8");
try {
request.setCharacterEncoding("UTF-8");
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
java.io.BufferedInputStream bis = null;
java.io.BufferedOutputStream bos = null;

String downLoadPath = filePath; //注意不同系统的分隔符
// String downLoadPath =filePath.replaceAll("/", "\\\\\\\\"); //replace replaceAll区别 *****
System.out.println(downLoadPath);

try {
long fileLength = new File(downLoadPath).length();
response.setContentType("application/x-msdownload;");
response.setHeader("Content-disposition", "attachment; filename=" + new String(fileName.getBytes("utf-8"), "ISO8859-1"));
response.setHeader("Content-Length", String.valueOf(fileLength));
bis = new BufferedInputStream(new FileInputStream(downLoadPath));
bos = new BufferedOutputStream(response.getOutputStream());
byte[] buff = new byte[2048];
int bytesRead;
while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
bos.write(buff, 0, bytesRead);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (bis != null)
try {
bis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (bos != null)
try {
bos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return null;
}

springmvc提供的ResponseEntit

1
2
3
4
5
6
7
8
9
10
11
   @RequestMapping("/download")  
public ResponseEntity<byte[]> export(String fileName,String filePath) throws IOException {

HttpHeaders headers = new HttpHeaders();
File file = new File(filePath);

headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", fileName);

return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
}

文章字数:240,阅读全文大约需要1分钟

session和cookie

http是无状态协议,为了能够维持状态,采用了sessioncookie的机制。session保存在服务器端,用户访问时会带上sessionId以判断是那个session的信息。

springSession

  1. springSession使用第三方存储实现集群session管理(redis,mongodb,jdbc),使用AOP技术,几乎可以做到透明替换原生session
  2. 方便拓展Cookie和自定义session相关的Listener Filter
  3. 方便和spring Security集成

sprinBoot集成使用Redis的SpringSession

依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
1
2
3
4
@Configuration
@EnableRedisHttpSession //扫描到这个即可
public class HttpSessionConfig {
}

配置连接

1
2
3
4
5
spring:
redis:
host: localhost
port: 6379
database: 0

关键源码

  1. springSessionRepositoryFilter拦截HttpSession的拦截器
  2. AbstractSecurityWebApplicationInitializerAbstractHttpSessionApplicationInitializer初始化器,拥有很高优先级

根据用户名查找用户所属的session

1
2
3
4
5
6
7
8
9
10
11

//索引name (springSecurity似乎集成了这一步,直接索引了用户名)
request.getSession().setAttribute(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,username);

//根据索引名和索引值查找sessionId(结果是个map,索引名固定索引值就是用户名)
@Autowired
FindByIndexNameSessionRepository<? extends ExpiringSession>
sessionRepository;
public Map findByUserName(String userName){
Map<String, ? extends ExpiringSession> usersSession = sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,username);
}

文章字数:721,阅读全文大约需要2分钟

三种redis中保存session的key

  1. spring:session:sessions:sessionId
  2. spring:session:expirations:时间戳
  3. spring:session:sessions:expires:sessionId

spring:session:是三个键公用的前缀

sessions

spring:session:sessions:sessionId的数据类型是hash类型,其中保存着session相关信息和session的数据。默认TTL是35分钟

1
2
3
4
"lastAccessedTime": 1523933008926,/*2018/4/17 10:43:28*/    #最后访问时间
creationTime": 1523933008926, /*2018/4/17 10:43:28*/ #创建时间
maxInactiveInterval": 1800, #失效时长
sessionAttr:xxx:"666" #session的值 sessionAttr:名字:“值”

expirations:

  1. redis的过期处理优先级很低,如果有很多过期的键就会出现键已经过期但是未删除
  2. 确保redis的键过期的方式有两个:访问过期的键、手动删除
  3. 基于以上两点,spring会定时手动清除过期的键。
    spring:session:expirations:时间戳中保存的数据为set类型,其中保存着时间戳所在的一分钟之内过期的session,有数据访问就移除,添加到下一个时间段的set中。使用专门的键保存过期需要确保删除的session可以避免全局查找,然后一个个访问失效时间等信息。
    用户快速操作可能会带来并发问题,在两个时间段都进行了请求。session的过期时间应该向后推两次,但是可能第一次向后推还没完成第二次就完成了(两次操作处于时间段节点)这样就造成session过期时间存在于两个时间段中。
    此时如果直接删除会造成时间差,比预计删除早了一分钟。所以不能使用删除,使用hasKey命令访问过期的key,让redis自动删除。

sessions:expires

spring:session:sessions:expires:sessionId的值为空,sessions中保存着session的具体值,sessions:expires则是代表session的有效时间。
spring需要在知道session过期后拿到过期session的详细信息并进行操作。然而redis的键空间通知keyspace notifications只会说明那个键过期了,无法访问过期的键。于是设置了sessions:expires代表session过期时间,这个键过期了代表session过期。存储信息的sessions键则多存活5分钟,以便过期后访问信息。
spring中监听过期事件:

1
2
3
4
# 配置
org.springframework.session.data.redis.config.ConfigureNotifyKeyspaceEventsAction
# 过期事件
SessionExpiredEvent

文章字数:479,阅读全文大约需要1分钟

事务传播指的是一个事务被另一个事务调用时事务的进行方式

使用

1
2
3
4
5
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
methodB();
// do something
}

七种级别

传播行为 含义
PROPAGATION_REQUIRED 当前方法必须在事务中运行。如果存在事务,在此事务中运行,否则启动新事务
PROPAGATION_SUPPORTS 当前方法不需要事务上下文,但如果有就走当前事务中运行
PROPAGATION_MANDATORY 方法必须在事务中运行,不存在当前事务,抛异常
PROPAGATION_REQUIRED_NEW 当前方法必须运行在它自己的事务中,一个新的事物将被启动。如果存在当前事务,在改方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransationManager
PROPAGATION_NEVER 表示当前方法不应该云心在事务上下文中。如果当前正有一个事物在运行,则抛异常
PROPAGATION_NESTED 如果当前存在事务,方法嵌套在事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样

文章字数:100,阅读全文大约需要1分钟

spring默认单例创建bean,也可以设置多例创建

区别

  1. 单例只创建一个,多例使用几次创建几次

  2. 创建时间,单例在IOC运行时创建,多例使用创建。单例设置懒加载也可以在使用时创建。

多例和懒加载

多例

1
2
3
4
5
@Scope("prototype")
@Bean
public Person person(){
return new Persion("张三");
}

懒加载

1
2
3
4
5
@Bean
@Lazy
public Person person(){
return new Persion("张三");
}