Spring

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的框架。

Spring重要的模块

  • Spring Core: 核心类库,提供IOC服务;
  • Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
  • Spring AOP :AOP服务;
  • Spring JDBC : Java数据库连接。
  • Spring MVC:提供面向Web应用的Model-View-Controller实现。
  • Spring ORM : 用于支持Hibernate等ORM工具。
  • Spring Web : 为创建Web应用程序提供支持。
  • Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。

2.Spring常用注解

2.1 @SpringBootApplication

Guide 哥:这个注解是 Spring Boot 项目的基石,创建 SpringBoot 项目之后会默认在主类加上。

@SpringBootApplication
public class SpringSecurityJwtGuideApplication { public static void main(java.lang.String[] args) {             SpringApplication.run(SpringSecurityJwtGuideApplication.class, args); }
}

我们可以把 @SpringBootApplication 看作是 @Configuration 、 @EnableAutoConfiguration 、 @ComponentScan 注解的集合。

根据 SpringBoot 官网,这三个注解的作用分别是:

  • @EnableAutoConfiguration :启用 SpringBoot 的自动配置机制
  • @ComponentScan : 扫描被 @Component ( @Service , @Controller )注解的
    bean,注解默认会扫描该类所在的包下所有的类。
  • @Configuration :允许在 Spring 上下文中注册额外的 bean 或导入其他配置类

2.2 Spring Bean 相关

2.2.1 @Autowired

自动导入对象到类中,被注入的类同样要被 Spring 容器管理比如:Service 类注入到 Controller 类中。

@Service public class UserService { ......
}@RestController
@RequestMapping("/users")
public class UserController { @Autowired private UserService userService; ......
}

2.2.2 @Component , @Repository , @Service , @Controller

一般使用 @Autowired 注解让 Spring 容器帮我们自动装配 bean。要想把类通过@Autowired 注解标识成可自动装配的 bean 的类,可以采用以下注解实现:

  • @Component :通用注解,可标注任意类为 Spring 组件。用于不知道Bean属于哪个 层
  • @Repository : 对应持久层即Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
  • @Controller : 对应控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。

2.2.3 @RestController

@RestController 注解是 @Controller和 @ ResponseBody 的合集,表示这是个控制器
bean,将函数的返回值(JSON 或 XML 形式数据)直接填入HTTP 响应体中。

单独使用 @Controller 不加 @ResponseBody 的话一般使用在要返回一个视图的情况,这种情况属于比较传统的 Spring MVC 的应用,对应于前后端不分离的情况。

2.2.4 @Scope

声明 Spring Bean 的作用域。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象

使用方法:

@Bean
@Scope("singleton")
public Person personSingleton() { return new Person();
}

四种常见的 Spring Bean 的作用域:

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。

2.2.5 Configuration

用来声明配置类,可以使用 @Component 注解替代

@Configuration
public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); }
}

2.3 处理常见的 HTTP 请求类型

2.3.1 GET 请求

请求从服务器获取特定资源。举个例子: GET /users (获取所有学生)

@GetMapping(“users”) 等价于@RequestMapping(value="/users",method=RequestMethod.GET)

@GetMapping("/users")
public ResponseEntity<List<User>> getAllUsers() { return userRepository.findAll();
}

2.3.2 POST 请求

在服务器上创建一个新的资源。举个例子: POST /users (创建学生)

@PostMapping(“users”)等价于@RequestMapping(value="/users",method=RequestMethod.POST)

@PostMapping("/users")
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateRequest userCreateRequest) { return userRespository.save(user);
}

2.3.3 PUT 请求

更新服务器上的资源(客户端提供更新后的整个资源)。举个例子: PUT /users/12 (更新编号为 12 的学生)

@PutMapping("/users/{userId}") 等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.PUT)

@PutMapping("/users/{userId}")
public ResponseEntity<User> updateUser(@PathVariable(value = "userId")
Long userId, @Valid @RequestBody UserUpdateRequest userUpdateRequest)
{ ......
}

2.3.4 DELETE 请求

从服务器删除特定的资源。举个例子: DELETE /users/12 (删除编号为 12 的学生)

@DeleteMapping("/users/{userId}") 等价于@RequestMapping(value="/users/{userId}",method=RequestMethod.DELETE)

@DeleteMapping("/users/{userId}")
public ResponseEntity deleteUser(@PathVariable(value = "userId") Long userId)
{ ......
}

2.3.5 PATCH 请求

更新服务器上的资源(客户端提供更改的属性,可以看做作是部分更新),使用的比较少

一般实际项目中,我们都是 PUT 不够用了之后才用 PATCH 请求去更新数据。

@PatchMapping("/profile")
public ResponseEntity updateStudent(@RequestBody
StudentUpdateRequest studentUpdateRequest) {        studentRepository.updateDetail(studentUpdateRequest); return ResponseEntity.ok().build();
}

2.4 前后端传值

掌握前后端传值的正确姿势,是开始 CRUD 的第一步!

2.4.1 @PathVariable 和 @RequestParam

@PathVariable 用于获取路径参数, @RequestParam 用于接收参数。

@GetMapping("/klasses/{klassId}/teachers")
public List<Teacher> getKlassRelatedTeachers(
@PathVariable("klassId") Long klassId,
@RequestParam(value = "type", required = false) String type )
{ ...
}

如果我们请求的 url 是: /klasses/{123456}/teachers?type=web
那么我们服务获取到的数据就是: klassId=123456,type=web 。

2.4.2 @RequestBody

用于读取Content-Typeapplication/json 格式的数据 的Request 请求(可能是
POST,PUT,DELETE,GET 请求)中body 部分,接收到数据之后会自动将数据绑定到 Java
实体上去。或用Map<String,Object>对象接收。

@PostMapping("/save")
public void save(@RequestBody QuestionVo vo) {System.out.println(vo.getId());
}
public class Question {private String id;private String title;private String content;省略get\set方法...
}

一个请求方法只可以有一个 @RequestBody ,但是可以有多个 @RequestParam 和@PathVariable 。

2.5 读取配置信息

很多时候我们需要将一些常用的配置信息比如线程池、微信认证的相关配置信息等等放到配置文件中。

数据源 application.yml 内容如下:

wuhan2020: 2020年初武汉爆发了新型冠状病毒,疫情严重,
但是,我相信一切都会过去!武汉加油!中国 加油! my-profile: name: Guide哥 email: koushuangbwcx@163.com library: location: 湖北武汉加油中国加油 books: - name: 天才基本法 description: 二十二岁的林朝夕在父亲确诊阿尔茨海默病这天,得知自己暗恋多年的校园男神裴之 即将出国深造的消息——对方考取的学校,恰是父亲当年为她放弃的那所。 - name: 时间的秩序description: 为什么我们记得过去,而非未来?时间“流逝”意味着什么?是我们存在于时间之 内,还是时间存在于我们之中?卡洛·罗韦利用诗意的文字,邀请我们思考这一亘古难题——时间的本质。

2.5.1 @value (常用)

使用 @Value("${property}") 读取比较简单的配置信息:

@Value("${wuhan2020}")
String wuhan2020;

2.5.2 @ConfigurationProperties (常用)

通过 @ConfigurationProperties 读取配置信息并与 bean 绑定。
可以像使用普通的 Spring bean 一样,将其注入到类中使用。

@Component
@ConfigurationProperties(prefix = "library")
class LibraryProperties { @NotEmpty private String location; private List<Book> books; @Setter @Getter @ToString static class Book { String name; String description; }省略getter/setter ......
}

2.5.3 PropertySource (不常用)

@PropertySource 读取指定 properties 文件

@Component
@PropertySource("classpath:website.properties")
class WebSite { @Value("${url}") private String url; 省略getter/setter ......
}

2.6 参数校验

JSR(Java Specification Requests) 是一套 JavaBean参数校验的标准,它定义了很多常用的校验注解,可以将这些注解加在JavaBean 的属性上面,就可以进行校验了。

2.6.1 一些常用的字段验证的注解

  • @NotEmpty 被注释的字符串的不能为 null 也不能为空
  • @NotBlank 被注释的字符串非 null,并且必须包含一个非空白字符
  • @Null 被注释的元素必须为 null
  • @NotNull 被注释的元素不为 null
  • @AssertTrue 被注释的元素为 true
  • @AssertFalse 被注释的元素为 false
  • @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
  • @Email 被注释的元素必须是 Email 格式。
  • @Min(value) 被注释的元素必须是一个数字,值必须大于等于指定的最小值
  • @Max(value) 被注释的元素必须是一个数字,值必须小于等于指定的最大值
  • @DecimalMin(value) 被注释的元素必须是一个数字,值必须大于等于指定的最小值
  • @DecimalMax(value) 被注释的元素必须是一个数字,值必须小于等于指定的最大值
  • @Size(max=, min=) 被注释的元素的大小必须在指定的范围内
  • @Digits (integer, fraction) 被注释的元素必须是一个数字,值必须在可接受的范围内
  • @Past 被注释的元素必须是一个过去的日期
  • @Future 被注释的元素必须是一个将来的日期

2.6.2 验证请求体(RequestBody)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person { @NotNull(message = "classId 不能为空") private String classId; @Size(max = 33) @NotNull(message = "name 不能为空") private String name;
}

在需要验证的参数上加@Valid 注解,如果验证失败,它将抛出MethodArgumentNotValidException

@RestController
@RequestMapping("/api")
public class PersonController { @PostMapping("/person") public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) { return ResponseEntity.ok().body(person); }

2.6.3 验证请求参数(Path Variables 和 Request Parameters)

在类上加上 Validated 注解,可以告诉 Spring 去校验方法参数。

@RestController
@RequestMapping("/api") @Validated
public class PersonController { @GetMapping("/person/{id}") public one<Integer> gettwo(@Valid @PathVariable("two") @Max(value = 5,message = "超过 id 的范围了") Integer id) { return one.ok().body(two); }
}

2.7 全局处理 Controller 层异常

介绍一下Spring 项目必备的全局处理 Controller 层异常。

  1. @ControllerAdvice :注解定义全局异常处理类
  2. @ExceptionHandler :注解声明异常处理方法
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {   //请求参数异常处理@ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {...... }
}

2.8 JPA 相关

2.8.1 创建表

  • @Entity 声明一个类对应一个数据库实体。
  • @Table 设置表明
@Entity @Table(name = "role")
public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String description; 省略getter/setter......
}

2.8.2 创建主键

  • @Id :声明一个字段为主键。还需定义主键的生成策略。

通过 @GeneratedValue 使用 JPA 内置的四种主键生成策略。

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

JPA 使用枚举定义了 4 中常见的主键生成策略,如下:

public enum GenerationType { /*使用一个特定的数据库表格来保存主键 持久化引擎通过关系数据库的一张特定的表格来生成主键*/ TABLE, /*在某些数据库中,不支持主键自增长,比如Oracle、PostgreSQL其提供了一种叫做"序列 (sequence)"的机制生成主键 */ SEQUENCE, // 主键自增长IDENTITY, /*把主键生成策略交给持久化引擎(persistence engine), 持久化引擎会根据数据库在以上三种主键生成 策略中选择其中一种 */ AUTO
}

@GeneratedValue 注解默认使用AUTO,一般使用 MySQL,使用 IDENTITY 策略比较普遍

2.8.3 设置字段类型

  • @Column 声明字段。
//设置userName属性对应的数据库字段名为 user_name,长度为 32,非空
@Column(name = "user_name", nullable = false, length=32)
private String userName;//设置字段类型并且加默认值
Column(columnDefinition = "tinyint(1) default 1")
private Boolean enabled;

2.8.4 指定不持久化字段

  • @Transient :声明不需要与数据库映射的字段,不保存进数据库 。
Entity(name="USER")
public class User { ...... @Transient private String secrect;
}

2.8.5 声明大字段

  • @Lob:声明某个字段为大字段。
@Lob
//指定 Lob 类型数据的获取策略, FetchType.EAGER表示非延迟加载,而 FetchType. LAZY 表示 延迟加载 ;
@Basic(fetch = FetchType.EAGER)
//columnDefinition 属性指定数据表对应的 Lob 字段类型
@Column(name = "content", columnDefinition = "LONGTEXT NOT NULL")
private String content;

2.8.6 创建枚举类型的字段

  • @Enumerated:修饰枚举字段。
public enum Gender { MALE("男性"), FEMALE("女性"); private String value; Gender(String str){ value=str; }
}
@Entity
@Table(name = "role")
public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; //数据库里面对应存储的是 MAIL/FEMAIL。@Enumerated(EnumType.STRING) private Gender gender; 省略getter/setter......
}

2.8.7 增加审计功能

{待续}

2.8.8 删除/修改数据

  • @Modifying :提示 JPA 是修改操作,需配合 @Transactional 注解使用

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
@Modifying
@Transactional(rollbackFor = Exception.class)
void deleteByUserName(String userName);
}

2.8.9 关联关系

  • @OneToOne 声明一对一关系
  • @OneToMany 声明一对多关系
  • @ManyToOne 声明多对一关系
  • MangToMang 声明多对多关系

举例一对多关系:通过ID,查询学生信息,然后通过学生查询对应的成绩信息

实体类:

@Entity
@Table(name="tb_student")
public class Student implements Serializable {private static final long serialVersionUID = 1L;@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="student_id")private int studentId;@Column(name="student_name")private String studentName;@Column(name="student_pwd")private String studentPwd;//建立学生与成绩的关系,一个学生可以有多门成绩,一门成绩只能属于一个学生,一对多的关系。//一个学生可以有多成绩,所以我们需要使用集合来储存成绩@OneToMany//不管是什么关系,必须要指定关联关系的外键@JoinColumn(name = "student_id")private List<Result> results;省略get/set方法

测试:

//需求:通过ID,查询学生信息,然后通过学生查询对应的成绩信息
@Test
public void findById(){//第一步:获得操作对象EntityManager entityManager = JpaUtils.getEntityManager();//第二步:操作-查询//查询学生信息Student student = entityManager.find(Student.class, 1);System.out.println("学生名:"+student.getStudentName());//通过学生的信息找到成绩信息List<Result> results = student.getResults();for (Result result : results) {System.out.println("科目:"+result.getResultSubject()+",成绩:"+result.getResultScore());}//第三步:提交关闭entityManager.close();
}

2.9 事务

  • @Transactional:一般用在可以作用在或者方法上。
    类:表示该类所有的 public 方法都配置相同的事务
    方法:当类配置了 @Transactional ,方法也配置了 @Transactional ,方法的事务会覆
    盖类的事务配置信息

在要开启事务的方法上使用 @Transactional 注解即可!

@Transactional(rollbackFor = Exception.class)
public void save() { ......
}

不配置 rollbackFor 属性,那么事物只会在遇到 RuntimeException 的时候才会回滚,加上可以让事物在遇到非运行时异常时也回滚。

2.10 json 数据处理

2.10.1 过滤 json 数据

  • @JsonIgnoreProperties :作用于类,用于过滤掉特定字段(不返回或者不解析)。

2.10.2 格式化 json 数据

  • @JsonFormat:格式化 json 数据。
@JsonFormat(shape=JsonFormat.Shape.STRING,
pattern="yyyy-MM- dd'T'HH:mm:ss.SSS'Z'", timezone="GMT") private Date date;

2.10.3 扁平化对象

  • @JsonUnwrapped :扁平化对象
@Getter
@Setter
@ToString
public class Account { @JsonUnwrapped private Location location; @JsonUnwrapped private PersonInfo personInfo;@Getter @Setter @ToString public static class Location { private String provinceName; private String countyName; }@Getter @Setter @ToString public static class PersonInfo { private String userName; private String fullName; }
}

未扁平化之前:

{ "location": { "provinceName":"湖北", "countyName":"武汉" },"personInfo": { "userName": "coder1234", "fullName": "shaungkou" }
}

扁平对象之后:

{ "provinceName":"湖北", "countyName":"武汉", "userName": "coder1234", "fullName": "shaungkou"
}

2.11 测试相关

@ActiveProfiles:作用于测试类上,声明生效的 Spring 配置文件。
@Test:声明为测试方法
@Transactional:测试方法的数据会回滚,避免污染测试数据。
@WithMockUser:Spring Security 提供的,用来模拟一个真实用户,并且可以赋予权限。

@SpringBootTest(webEnvironment = RANDOM_PORT)
@ActiveProfiles("test")
@Slf4j
public abstract class TestBase { @Test @Transactional @WithMockUser(username = "user-id-18163138155", authorities = "ROLE_TEACHER") void should_import_student_success() throws Exception { ...... }
}

3. Spring事务

3.1 什么是事务?

事务是逻辑上的一组操作,要么都执行,要么都不执行。支持 innodb 数据库引擎

3.2 事物的特性(ACID)


原子性: 动作要么全部完成,要么完全不起作用;
一致性: 执行事务前后,数据保持一致;
隔离性: 并发访问数据库时,每个事务都应该与其他事务隔离开来,防止数据损坏;
持久性: 事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也没有任何影响。

3.3 详谈 Spring 对事务的支持

3.3.1. Spring 支持两种方式的事务管理

1、编程式事务管理(通过 TransactionTemplate 或者 TransactionManager 手动管理事务,实际应用中很少使用)

使用 TransactionTemplate 进行编程式事务管理的示例代码如下:

@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { try {// .... 业务代码 } catch (Exception e){ //回滚 transactionStatus.setRollbackOnly(); } } });
}

使用 TransactionManager 进行编程式事务管理的示例代码如下:

@Autowired
private PlatformTransactionManager transactionManager;
public void testTransaction() { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try {// .... 业务代码 transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); }
}

2、声明式事务管理(推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于 @Transactional 的全注解方式使用最多)。)

1、使用 @Transactional 注解进行事务管理的示例代码如下:

@Transactional(isolation=Isolation.REPEATABLE_READ,
readOnly=false,propagation=Propagation.REQUIRED)public void aMethod { //do something B b = new B(); C c = new C(); b.bMethod(); c.cMethod();
}

2、使用XML文件配置事务:

<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><!--配置哪些方法使用什么样的事务,配置事务的传播特性--><tx:method name="add" propagation="REQUIRED"/><tx:method name="delete" propagation="REQUIRED"/><tx:method name="update" propagation="REQUIRED"/><tx:method name="search*" propagation="REQUIRED"/><tx:method name="get" read-only="true"/><tx:method name="*" propagation="REQUIRED"/></tx:attributes>
</tx:advice>

配置将通知织入目标

<!--配置aop织入事务-->
<aop:config><aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*(..))"/><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

3.3.2 Spring 事务管理接口介绍

  • PlatformTransactionManager : 事务管理接口
  • TransactionDefinition : 事务属性(事务隔离级别、传播行为、超时、只读、回滚规 则)。
  • TransactionStatus : 事务运行状态。

1、PlatformTransactionManager:事务管理接口

Spring 并不直接管理事务,而是提供了多种事务管理器 。Spring 事务管理器的接口是:
PlatformTransactionManager 。(可以看作是事务上层的管理者)

通过这个接口,Spring 为各个平台如 JDBC( DataSourceTransactionManager )、
Hibernate( HibernateTransactionManager )、JPA( JpaTransactionManager )等都提供了对应的事务管理器,但没有具体的实现。接口中有获得事物(getTransaction)方法,提交事物(commit)方法,回滚事物(rollback)方法。

为什么要定义PlatformTransactionManager接口?
将事务管理行为抽象出来,然后不同的平台去实现它,这样可以保证提供给外部的行
为不变,方便我们扩展。

2、TransactionDefinition:事务属性

事务管理器接口通过 getTransaction方法来得到一个事务,这个方法的参数是 TransactionDefinition 类,这个类中定义了一些事务属性(隔离级别、传播行为、超时、只读、回滚规则)。

3、TransactionStatus:事务状态

事物管理接口的getTransaction() 方法返回一个TransactionStatus 对象; TransactionStatus 接口用来记录事务状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息。

TransactionStatus 接口内容如下:

public interface TransactionStatus{ boolean isNewTransaction(); // 是否是新的事物 boolean hasSavepoint(); // 是否有恢复点 void setRollbackOnly(); // 设置为只回滚 boolean isRollbackOnly(); // 是否为只回滚 boolean isCompleted; // 是否已完成 }

3.3.3 事务属性详解

实际业务开发中,一般使用 @Transactional 注解来开启事务

(1)、事务传播行为: 为了解决业务层方法之间互相调用的事务问题。默认PROPAGATION_REQUIRED

  • propagation_requierd:支持当前事物,如果当前不存在事务,就新建一个事务,如果已存在,则加入到这个事务中。
  • propagation_supports:支持当前事务,如果当前不存在事务,就不使用事务。
  • propagation_mandatory:支持当前事务,如果当前不存在事务,抛出异常。
  • propagation_required_new:支持当前事务,如果当前存在事务,把当前事务挂起,创建一个新事务。
  • propagation_not_supported:以非事务方式运行,如果当前存在事务,挂起当前事务。
  • propagation_never:以非事务方式运行,存在,抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

(2)、事务隔离级别

  • TransactionDefinition.ISOLATION_DEFAULT :
    使用数据库默认的隔离级别,MySQL默认采用的REPEATABLE_READ(可重读)隔离级别,Oracle 默认采用的 READ_COMMITTED 隔离级别.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:
    最低隔离级别,允许读取未提交的数据变更,可能会导致脏读、幻读或不可重复读。使用这个隔离级别很少,
  • TransactionDefinition.ISOLATION_READ_COMMITTED :
    允许读取并发事务已经提交的数据,可以阻止脏读,可能发生幻读或不可重复读。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ :
    对同一字段的多次读取结果都是一致的,除非数据是被本身事务所修改,可以阻止脏读和不可重复读,可能发生幻读。
  • TransactionDefinition.ISOLATION_SERIALIZABLE :
    最高隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

(3)、事务超时属性
事务所允许执行的最长时间,如果超过该时间事务还没有完成,则回滚事务。

(4)、事务是否只读

  1. 执行单条查询语句,没必要启用事务只读,数据库默认支持 SQL 执行期间的读一致性;
  2. 执行多条查询语句,例如统计查询,报表查询这种场景下,多条查询 SQL 必须保证读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务只读

(5)、 事务回滚规则
定义了哪些异常会导致事务回滚哪些不会。默认情况下,事务只有遇到运行期(RuntimeException 的子类)异常和Error才会回滚;在遇到检查型(Checked)异常时不会回滚

@Transactional(rollbackFor= MyException.class)

3.3.4 @Transactional 注解使用详解

属性名 说明
propagation 事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过
isolation 事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过
timeout 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
readOnly 指定事务是否为只读事务,默认值为 false。
rollbackFor 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。

@Transactional 原理:@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用CGLIB 动态代理。

4. Spring IOC和 AOP详解

4.1 什么是 IOC

IOC (Inversion of control )控制反转。是一种设计思想,通过IOC容器(Spring 框架) 来帮助我们实例化对象

4.1.1 为什么叫控制反转

控制: 指的是对象创建(实例化、管理)的权力
反转: 控制权交给外部环境(Spring框架、IOC容器)

4.2 IOC 解决了什么问题

IOC 主要解决的是对象之间的耦合问题。我们将对象的控制权(创建、管理)交给IOC容器去管理,我们在使用的时候直接向IOC容器 “要” 就可以了。

4.3 IOC 和 DI 的区别

IOC 和 DI 描述的是同一件事情(对象实例化以及依赖关系的维护),只不过角度不同。

  • IOC (Inversion of control ) 控制反转/反转控制。是站在对象的角度,对象实例化以及管理的权限(反转)交给了容器。
  • DI (Dependancy Injection)依赖注入。是站在容器的角度,容器会把对象依赖的其他对象注入。例如:对象A实例化过程中因为声明了一个B类型的属性,那么就需要容器把B对象注入到A中。

4.4 什么是AOP

AOP:Aspect oriented programming
面向切面编程,将那些与业务无关,却被业务模块共同调用的公共业务(例如事务处理、日志管理、权限控制等)封装起来,当执行原有业务时,会把公共业务也加进来,实现公共业务的重复利用。减少重复代码,降低模块间的耦合度,提高了可拓展性和可维护性。

4.4.1 Aop在Spring中的作用

提供声明式事务;允许用户自定义切面

以下名词需要了解下:

  • JoinPoint(连接点):目标对象中,所有可以增强的方法,就是spring允许通知(Advice)的地方。
  • Pointcut(切入点):目标对象中,已经被增强的方法。调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法。
  • Advice(通知/增强) :增强方法的代码、想要的功能。
  • Target(目标对象):被代理对象,被通知的对象,被增强的类对象。
  • Proxy(代理):将通知织入到目标对象之后形成的代理对象
  • aspect(切面):切入点+通知


SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice

4.4.2 使用Spring实现Aop

导入依赖包

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.4</version>
</dependency>

第一种方式:通过 Spring API 实现

首先编写我们的业务接口和实现类

public interface UserService {public void add();public void delete();public void update();public void search();}
public class UserServiceImpl implements UserService{@Overridepublic void add() {System.out.println("增加用户");}@Overridepublic void delete() {System.out.println("删除用户");}@Overridepublic void update() {System.out.println("更新用户");}@Overridepublic void search() {System.out.println("查询用户");}
}

然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强

public class Log implements MethodBeforeAdvice {//method : 要执行的目标对象的方法//objects : 被调用的方法的参数//Object : 目标对象@Overridepublic void before(Method method, Object[] objects, Object o) throws Throwable {System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");}
}public class AfterLog implements AfterReturningAdvice {//returnValue 返回值//method被调用的方法//args 被调用的方法的对象的参数//target 被调用的目标对象@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable {System.out.println("执行了" + target.getClass().getName()+"的"+method.getName()+"方法,"+"返回值:"+returnValue);}
}

最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--注册bean--><bean id="userService" class="com.kuang.service.UserServiceImpl"/><bean id="log" class="com.kuang.log.Log"/><bean id="afterLog" class="com.kuang.log.AfterLog"/><!--aop的配置--><aop:config><!--切入点 expression:表达式匹配要执行的方法--><aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/><!--执行环绕; advice-ref执行方法 . pointcut-ref切入点--><aop:advisor advice-ref="log" pointcut-ref="pointcut"/><aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/></aop:config></beans>

测试

public class MyTest {@Testpublic void test(){ApplicationContext context = newClassPathXmlApplicationContext("beans.xml");UserService userService = (UserService) context.getBean("userService");userService.search();}
}

第二种方式:自定义类来实现Aop

目标业务类不变依旧是userServiceImpl

第一步 : 写一个自己的切入类

public class DiyPointcut {public void before(){System.out.println("---------方法执行前---------");}public void after(){System.out.println("---------方法执行后---------");}}

去spring中配置

<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="DiyPointcut" class="com.kuang.config.DiyPointcut"/><!--aop的配置-->
<aop:config><!--第二种方式:使用AOP的标签实现--><aop:aspect ref="DiyPointcut"><aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/><aop:before pointcut-ref="diyPonitcut" method="before"/><aop:after pointcut-ref="diyPonitcut" method="after"/></aop:aspect>
</aop:config>

第三种方式:使用注解实现

第一步:编写一个注解实现的增强类

package com.kuang.config;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;@Aspect
public class AnnotationPointcut {@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")public void before(){System.out.println("---------方法执行前---------");}@After("execution(* com.kuang.service.UserServiceImpl.*(..))")public void after(){System.out.println("---------方法执行后---------");}@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")public void around(ProceedingJoinPoint jp) throws Throwable {System.out.println("环绕前");System.out.println("签名:"+jp.getSignature());//执行目标方法proceedObject proceed = jp.proceed();System.out.println("环绕后");System.out.println(proceed);}
}

第二步:在Spring配置文件中,注册bean,并增加支持注解的配置

<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>

5. Spring中 Bean 的作用域与生命周期

首先说一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;

Spring上下文中的Bean生命周期也类似,如下:

  1. 实例化Bean:
    对于BeanFactory容器,当客户向容器请求一个未初始化的bean或初始化bean时需要注入另一个未初始化的依赖时,容器会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

  2. 设置对象属性(依赖注入):
    实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通 过BeanWrapper提供的设置属性的接口完成对象属性注入。

  3. 处理Aware接口:
    Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:

    (1)如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。
    (2) 如果Bean实现了BeanFactoryAware接口,调用setBeanFactory()方法,传入BeanFactory对象的实例。
    (3)如果Bean实现了ApplicationContextAware接口,调用setApplicationContext()方法,传入Spring上下文;

  4. BeanPostProcessor:
    如果想对Bean进行一些自定义的处理,让Bean实现BeanPostProcessor接口,执行 postProcessBeforeInitialization()方法

  5. InitializingBean 与 init-method:
    如果Bean在Spring配置文件中配置了 init-method 属性,会自动调用其配置的初始化方法。

  6. 如果这个Bean实现了BeanPostProcessor接口,会调用postProcessAfterInitialization()方法;这个方法是在Bean初始化结束时调用的,可以被应用于内存或缓存技术;

以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。

  1. DisposableBean:
    当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法

  2. destroy-method:
    如果Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

6. SpringMVC 工作原理详解

6.1 先来看一下什么是 MVC 模式

6.2 SpringMVC 简单介绍

SpringMVC 框架是以请求为驱动,围绕 Servlet设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是 DispatcherServlet,它是一个Servlet,顶层是实现的Servlet接口。

6.3 SpringMVC 使用

需要在 web.xml 中配置 DispatcherServlet 。并且需要配置 Spring 监听器ContextLoaderListener

<listener> <listener-class>org.springframework.web.context.ContextLoaderListener </listener-class>
</listener> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class> <!-- 如果不设置init-param标签,则必须在/WEB-INF/下创建xxx-servlet.xml文件,其中xxx 是servlet-name中配置的名称。 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/springmvc-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern>
</servlet-mapping>

6.4 SpringMVC 工作原理(重要)

流程说明(重要):

  1. 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
  2. DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
  3. 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter适配器处理。
  4. HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑。
  5. 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
  6. ViewResolver 会根据逻辑 View 查找实际的 View。
  7. DispaterServlet 把返回的 Model 传给 View(视图渲染)。
  8. 把 View 返回给请求者(浏览器)

6.5 SpringMVC 重要组件说明

1、前端控制器DispatcherServlet 框架提供(
作用:Spring MVC 的入口函数。接收请求,响应结果,相当于转发器,中央处理器。

2、处理器映射器HandlerMapping 框架提供
作用:根据请求的url查找Handler。
HandlerMapping负责根据用户请求找到Handler即处理器(Controller)

3、处理器适配器HandlerAdapter 框架提供
作用:按照特定规则去执行Handler。

4、处理器Handler 自己开发
作用:Handler 是后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理

5、视图解析器View resolver 框架提供
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view)
View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染并将处理结果通过页面展示给用户。

6、视图View 自己开发
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)

7. Spring中都用到了那些设计模式?

7.1 工厂设计模式

Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象。

两者对比:

  • BeanFactory :延迟注入(使用bean 时才注入),占用内存更少,程序启动速度更快,仅提供了最基本的依赖注入支持。
  • ApplicationContext :一次性注入所有 bean 。ApplicationContext 扩展了 BeanFactory还有额外更多功能,所以一般开发人员使用 ApplicationContext更多。

7.2 单例设计模式

在系统中,比如:线程池、缓存、注册表等对象。我们只需要一个实例,如果有多个实例可能会导致:程序的行为异常、资源使用过量、或者不一致性等结果。Spring 中 bean 的默认作用域就是 singleton(单例)的。

使用单例模式的好处:

  • 对于频繁使用的对象,可以省略创建所对象花费的时间。
  • 由于 new 操作的次数减少,可以减轻 GC 压力,缩短 GC停顿时间。

7.3 代理设计模式

7.3.1 代理模式在 AOP 中的应用

Spring AOP 就是基于动态代理的。如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象;如果没有实现接口,Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成代理对象的子类作为代理。

7.3.2 Spring AOP 和 AspectJ AOP 有什么区别?

  • Spring AOP 属于运行时增强,基于代理(Proxying),更简单。
  • AspectJ 是编译时增强。基于字节码操作(Bytecode Manipulation), 功能更强大速度更快。

7.4 模板方法模式

模板方法模式是一种行为设计模式,它定义一个操作中算法的骨架,而将一些步骤延迟到子类中。

Spring 中 jdbcTemplate 、 hibernateTemplate 等以 Template 结尾,对数据库操作的类,它们使用了模板模式。Spring 并没有使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,也增加了灵活性。

7.5 观察者模式

观察者模式是一种对象行为型模式。它表示对象与对象之间具有依赖关系,当一个对象发生改变的时候,这个对象所依赖的对象也会做出反应。Spring事件驱动模型就是观察者模式很经典的一个应用

7.5.1 Spring 的事件流程总结

  1. 定义一个事件: 继承自ApplicationEvent类,并且写相应的构造函数;
  2. 定义一个事件监听者:实现 ApplicationListener 接口,重写 onApplicationEvent() 方
    法;
  3. 使用事件发布者发布消息: 通过 ApplicationEventPublisher 的 publishEvent() 方法发
    布消息。

例子:

// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{ private static final long serialVersionUID = 1L; private String message; public DemoEvent(Object source,String message){super(source); this.message = message; }public String getMessage() { return message; } // 定义一个事件监听者,实现ApplicationListener接口,
//重写 onApplicationEvent() 方法; @Component
public class DemoListener implements ApplicationListener<DemoEvent>{ //使用onApplicationEvent接收消息 @Override public void onApplicationEvent(DemoEvent event) { String msg = event.getMessage(); System.out.println("接收到的信息是:"+msg); }
}// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {@Autowired ApplicationContext applicationContext; public void publish(String message){ //发布事件 applicationContext.publishEvent(new DemoEvent(this, message)); }
}

当调用 DemoPublisher 的 publish() 方法的时候,比如 demoPublisher.publish(“你好”) ,控制
台就会打印出: 接收到的信息是:你好 。

7.6 适配器模式

适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的类可以一起工作,其别名为包装器(Wrapper)。

7.6.1 spring AOP中的适配器模式

Spring AOP 的实现是基于代理模式,但是 Spring AOP 的 通知(Advice) 使用了适配器模式。Advice 常用的类型有: BeforeAdvice (目标方法调用前置通知)、 AfterAdvice (目标方法调用后置通知)等,每个类型Advice(通知)都有对应的拦截
器: MethodBeforeAdviceInterceptor 、 AfterReturningAdviceAdapter 、 AfterReturningAdviceInterceptor 。Spring预定义的通知要通过对应的适配器,适配成
MethodInterceptor 接口(方法拦截器)类型的对象(如: MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice )。

7.6.2 spring MVC中的适配器模式

在Spring MVC中, HandlerAdapter 适配器作为接口,具体的 适配器实现类 用于对 目标类 进行适配, Controller 作为需要适配的类。

为什么要在 Spring MVC 中使用适配器模式?
Spring MVC 中的 Controller 种类很多,不同类型的Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式, DispatcherServlet直接获取对应类型的 Controller ,需要自行判断哪个Controller与请求适配。

7.7 装饰者模式

装饰者模式可以动态地给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。简单点说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。


Spring 中配置 DataSource 的时候,在少修改原有类代码的情况下,动态切换不同的数据源就要用到装饰者模式

7.8 总结

Spring 框架中用到了哪些设计模式?

  • 工厂设计模式 : Spring使用工厂模式通过 BeanFactory 、 ApplicationContext 创建 bean 对 象。
  • 代理设计模式 : Spring AOP 功能的实现。
  • 单例设计模式 : Spring 中的 Bean 默认都是单例的。
  • 模板方法模式 : Spring 中 jdbcTemplate 、 hibernateTemplate 等以 Template 结尾的对数数库操作的类,它们使用了模板模式。
  • 包装器设计模式 : 配置 DataSource时,少修改原有类代码的情况下,动态切换不同的数据源就要用到装饰者模式
  • 观察者模式: Spring 事件驱动模型,使用了观察者模式。
  • 适配器模式 :Spring AOP 的通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配 Controller 。

补充

1、 Spring基于xml注入bean的几种方式:

(1)Set方法注入;
(2)构造器注入:①通过index设置参数的位置;②通过type设置参数类型;
(3)静态工厂注入;
(4)实例工厂;
详细Spring中bean的注入方式

参考

本文对JavaSpring进行总结并整理,方便以后自己复习。参考网上各大帖子,取其精华整合自己的理解而成。
Spring Boot笔记
Spring-全面详解

JavaSpring全面总结相关推荐

  1. Javaspring 7-13课 Spring Bean

    Javaspring的灵魂,就在于bean的灵活运用.作为Spring核心机制的依赖注入,改变了传统的编程习惯,对组件的实例化不再由应用程序完成,转而交由Spring容器完成,在需要时注入应用程序中, ...

  2. Javaspring 1-6课 基本概念及第一个Javaspring程序

    只会前端还是差点事,开坑后端.参照教程为http://c.biancheng.net/spring/ 首先Javaspring是一个框架,之前一直没搞明白什么是框架,现在的理解框架就是毛坯房,已经设计 ...

  3. java async mysql,java-Spring MVC Async任务同步运行

    我想实现一个异步任务,并创建一个页面,该页面可立即返回并在后台启动该任务.但是,页面将等待后台任务完成,然后仅返回.当我访问/ start时,加载页面需要15秒钟的时间.我正在使用Spring 3.2 ...

  4. java mysql 死锁,java-Spring JPA MySQL和死锁

    我正在研究使用Spring Boot在Java中实现的REST API.我使用了嵌入式内存数据库H2数周,但在某个时候我注意到事务隔离存在问题. 更准确地说,我有一个表,需要在其中跟踪"重复 ...

  5. Javaspring 14-18课 spring AOP

    spring的两个核心思想就是LOC和AOP,前面已经总结了LOC,这一部分则是关于AOP的知识. AOP概述 AOP翻译过来叫做面向切面编程,与面向对象编程类似,是一种编程思想.AOP 的全称是&q ...

  6. 字节跳动社招面试记录,javaspring框架详细设计模板

    Java如何入门? 1.建立好开发环境 首先建立好开发环境非常重要,工欲善其事,必先利其器.做任何开发,首先就是要把这个环境准备好,之后就可以去做各种尝试,尝试过程中就能逐渐建立信心.初学者往往在环境 ...

  7. JavaSpring框架有哪些优势?

    Spring简单地说,就是一种框架,学过编程语言的人都知道,我们在编写程序时将应用于框架. 利用Spring这种框架可以简化编程过程中许多基本的工作,在配置好之后就可以方便地构建业务应用,当然,也不能 ...

  8. 14.Java- Spring

    一.Spring 1. 简介 Spring:春天 ------->给软件行业带来了春天 2002,首次推出了Spring框架的雏形:interface21框架 Spring框架即是以interf ...

  9. java-spring的JdbcTempldate对oracle 的CLob字段进行读和写

    相关介绍- Spring 让 LOB 数据操作变得简单易行(http://www.ibm.com/developerworks/cn/java/j-lo-spring-lob/) 具体代码: Blob ...

最新文章

  1. java标准类的制作
  2. 盘点丨2017年人工智能带火了哪些词
  3. 线程本地数据ThreadLocal
  4. LSTM-pytorch 写诗之位置编码
  5. 【组合数学】生成函数 ( 换元性质 | 求导性质 | 积分性质 )
  6. java笔记之抽象类和接口
  7. 全球及中国防褥疮充气垫行业投资前景展望与“十四五”发展规划建议报告2021年版
  8. c#用canny算子做边缘提取_【图像处理】边缘检测
  9. 输入学生成绩,并按升序排列 Ascending.java
  10. Tomcat根目录下work文件夹的作用
  11. leetcode18.4Sum
  12. Git版本恢复命令reset(转载)
  13. inode结构详解(深层次理解拷贝,删除,移动的本质)以及硬链接和软链接的区别(图文)
  14. Vue 项目断网时跳转到网络错误页面
  15. Scanvenger游戏制作笔记(九)Unity3D创建声音
  16. 微信公众号最佳实践 ( 10.3)获取微信版本及手机系统
  17. 元宇宙虚拟人物风格形象应用场景制作
  18. 前端ECMAScript6个人学习笔记
  19. LATEX 排版问题记录
  20. 我是培训机构出身的程序员,但不敢告诉任何人 !

热门文章

  1. rgw bucket sync
  2. redis数据一致性之延时双删详解
  3. P5587 打字练习
  4. 鼠标上下滑轮时,来回乱跑的骚操作
  5. 100句正能量的句子经典语句
  6. 软考高项计算题公式:PV, EV, AC, CV, SV, CPI, SPI, ETC, EAC,BAC
  7. ad hoc java_Java并发编程--线程封闭(Ad-hoc封闭 栈封闭 ThreadLocal)
  8. 阿里云服务器搭建wordpress个人博客
  9. 我的人工智能之旅——偏斜类问题
  10. 概率与期望——P4316 绿豆蛙的归宿