回到 Spring
简介
本系列用最快的速度过一遍 Spring Cloud,Spring 组件和微服务技术。
网上现有很多教程都讲的太细太复杂,重点是太长了,不适合快速入门。本系列将尽量简化,只讲最重要的部分。
本文假设你会最基础的 Spring Web,只要会用 Spring Web 开发一个简单的 RESTful 服务就可以了。此外,你需要会使用 docker,因为我们会用到 docker 来启动一些服务,而且 k8s 中也不会讲 docker。当然,你可以把这些服务部署到云上或本机,但这系列文章中只会提供 docker-compose 文件。
但是在讲解 Spring Cloud 之前,我们需要学习一些速成 Spring Boot 可能没讲到的 Spring 的基础知识。
Spring 简介
Spring 是一个 Java 开发框架,它提供了很多功能,比如依赖注入、AOP、事务管理等等。其中最重要的是依赖注入,这是 Spring 的核心功能。
Spring 包含了很多包,例如 Spring Web、Spring Data、Spring Security 等等。
依赖注入
Spring 的依赖注入是通过Bean
实现的,具体而言,你可以创建许多Config
类,每个Config
类都可以有多个Bean
方法,每个Bean
方法都规定了某一类对象的创建方式。Spring 会在启动时扫描所有的Config
类,然后根据Bean
方法的规则创建对象。
@Configuration
class MyConfig {
@Bean
public MyService myService() {
return new MyService();
}
}
这样,就规定了当MyService
对象使用注入方法创建时,实际上是调用myService
方法创建的。
如果要用依赖注入的方法获得注入的对象,使用 Spring Application Context。
public class SomeClass {
public void someFunc() {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
MyService myService = context.getBean(MyService.class);
}
}
Application Context 包含了所有的Bean
对象,可以通过getBean
方法获得。上面一种使用的是基于 Annotation,即 Configuration 注解类获得 Context 对象,然后基于类对象反射获得 Bean 对象。
Spring 也支持 XML 配置,即在 XML 文件中配置 Bean 对象,然后通过 XML 文件获得 Context 对象,不过这种方法已经不常用了。
<beans>
<bean id="myService" class="com.example.MyService"/>
</beans>
public class SomeClass {
public void someFunc() {
ApplicationContext context = new ClassPathXmlApplicationContext("my-config.xml");
MyService myService = context.getBean(MyService.class);
}
}
这里也可以不基于类对象获得 Bean 对象,而是基于 Bean 的 ID 获得。
MyService myService = (MyService) context.getBean("myService");
当然,注解里也可以改变 Bean 的 ID。
@Bean(name = "myService")
public MyService myService() {
return new MyService();
}
除了手动创建配置类,还可以直接使用@Component
注解。这个注解本身并不做什么,只有一个value
属性。如果不写,默认用类名首字母小写作为 Bean 的 ID。
@Component("myService")
public class MyService {
}
我们常用的一些注解,如@RestController
,@Service
,@Repository
,@Configuration
,都包含了@Component
。
但是,Spring 有另一个注解@ComponentScan
,这个注解会扫描指定包下的所有@Component
注解类,并将其注册为 Bean 对象到当前这个类下。例如,
@ComponentScan("com.example")
@Configuration
public class MyConfig {
}
Bean 的名字就是 Component 的 value 属性。之后就同之前一样获得上下文和对象即可。
事实上,我们每个 Spring 启动的 run 方法都会创建一个 Application Context,这个 Context 包含了所有的 Bean 对象。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
}
}
前面我们说过,context 需要基于Config
类创建,那这里的 Context 的 Config 在哪里?如果你去查看@SpringBootApplication
的源码,你会发现这个注解是这样的,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// ...
}
是的,它本身就有@ComponentScan
注解。此外,如果再去看@SpringBootConfiguration
,你会发现它包含了@Configuration
注解。所以,@SpringBootApplication
包含了@Configuration
和@ComponentScan
。
当然,比起这种传统的创建 Bean 的方法,你应该更熟悉@Resource
、@Autowired
、@Qualifier
、@Inject
等注解。这些注解是 Spring 的依赖注入的核心。
@Inject
和@Autowired
是等价的,是依赖类型注入,分别由 Java 和 Spring 提供。@Resource
和@Qualifier
是依赖名称注入,分别由 Java 和 Spring 提供。一般推荐使用 Spring 提供的注解。
@Autowired
和@Qualifier
的用法基本相同,只是依赖 ID 和类名的区别。
@Component
public class SomeClass {
}
public class AnotherClass {
@Autowired
private final SomeClass someClass;
public void someFunc(@Autowired @Qualifier("someClass") SomeClass someClass) {
this.someClass = someClass;
}
}
根据 Spring 的官方文档,还有 setter 注入法等等。但是,最推荐的方法是使用构建器注入加不可变私有字段。即下面这种方法。
@Component
public class SomeClass {
}
public class AnotherClass {
private final SomeClass someClass;
@Autowired
public AnotherClass(SomeClass someClass) {
this.someClass = someClass;
}
}
或,
public class AnotherClass {
private final SomeClass someClass;
public AnotherClass(@Autowired SomeClass someClass) {
this.someClass = someClass;
}
}
或者,你可以直接使用 lombok 的@RequiredArgsConstructor
注解。
@RequiredArgsConstructor
public class AnotherClass {
private final SomeClass someClass;
}
AOP (Aspect-Oriented Programming)
AOP 即 Aspect-Oriented Programming,面向切面编程。这只是设计模式中模版方法模式的一种实现,即在方法执行前后插入一些代码。
Spring 的 AOP 是基于代理的,即创建一个代理对象,然后在代理对象的方法中插入一些代码。
具体而言,如果要为某个类的某个方法添加切面,即在不改变原有方法的情况下添加一些代码,可以使用@Aspect
注解。
@Aspect
@Component
public class MyAspect {
@Before("com.example.MyService.s")
public void before() {
System.out.println("Before");
}
@After("execution(* com.example.MyService.*(..))", returning = "retVal")
public void after(Object retVal) {
System.out.println("After: " + retVal);
}
}
注意,切面类只是创建了一个代理对象,而不是更改了原对象的方法。如果直接使用原类来创建对象,切面是不会生效的。
@After
和@Before
里的第一个参数是切入点表达式,即要切入的方法。
- execution(* com.example.service..(..)):匹配com.example.service包下所有类的所有方法。
- within(com.example.service..*):匹配com.example.service包及其子包下的所有类的所有方法。
- bean(myBean):匹配名为myBean的bean的所有方法。
- @annotation(org.springframework.transaction.annotation.Transactional):匹配所有带有@Transactional注解的方法。
After 的returning
属性是返回值,可以用来获取返回值。
这样子创建切面类后,Spring 会在运行时创建一个代理对象,然后在代理对象的方法中插入一些代码。
要使用这个代理对象,需要在@SpringBootApplication
类中添加@EnableAspectJAutoProxy
注解。
@SpringBootApplication
@EnableAspectJAutoProxy
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
}
}
这样,使用@Autowired
注入的对象就是代理对象了。
由于 AOP 在实际开发中用的不多,这里就不再展开讲解了。
Value 注解
虽然@Value
注解并不能算是 Spring 的核心功能,但是在实际开发中用的很多,因此这里也简单介绍一下。
@Value
注解可以用来注入配置文件中的属性。
@Component
public class MyService {
@Value("${my.property}")
private String property;
}
这样,Spring 会在启动时读取配置文件中的my.property
属性,并将其注入到property
字段中。这个配置文件就是application.yml
,或bootstrap.yml
(当然,也可以是其他格式的文件)。后者是在之后的 Spring Cloud 中用到的。如果配置文件没有,还会找环境变量,环境变量中,点号会被默认替换为下划线。
my:
property: value
等价于有一个环境变量MY_PROPERTY
,值为value
。
但是,这样子只能注入字符串。如果要注入其它类型,例如一个整数,需要使用 Spring 值表达式,即#{}
。
@Component
public class MyService {
@Value("#{${my.property} + 1}")
private int property;
}
这个含义是,将my.property
的值加一后注入到property
字段中。
Spring Boot 简介
Spring Boot 不是 Spring 的某个模块。而是 Spring 的某个模块加上一些默认配置。以 Spring Data JPA 举例,它的核心功能在org.springframework.data:spring-data-jpa
,即这个 maven repository,而 Spring Boot Starter Data JPA 是 Spring Data JPA 加上一些默认配置,它在org.springframework.boot:spring-boot-starter-data-jpa
,即这个 maven repository。
可以这样打比方,Spring Framework 是钢筋混泥土框架,其中的内饰、家具、电器等等都是 Spring 的各种模块,例如 Spring Web、Spring Data、Spring Security 等等。每个模块的 Spring Boot Starter 包即是包含了装修服务的内饰、家具、电器等等。如果安装完你觉得不满意,你可以自己再做小改动,而不必从头自己完成所有的装修工作。
具体到编程上,Spring Boot Starter 版的工具除了原本的框架外,还提供了一系列默认的 Config 类,如果需要自定义,只需要创建一个 Config 类,然后在其中添加@Bean
注解,覆盖掉默认的 Bean 对象即可。
Spring Boot 配置的优先级顺序是,
- 命令行参数
- application.properties或application.yml(在config子目录或当前目录)
- application.properties或application.yml(在类路径的根目录)
- 通过@PropertySource注解加载的属性文件
- 默认属性(通过SpringApplication.setDefaultProperties指定)
Spring Framework 自己也有许多的配置,例如我们上面讲的@Configuration
、@Bean
等等,因此它也有自己的 Spring Boot Starter,即org.springframework.boot:spring-boot-starter
。
注意,所有的 Spring Boot 都必须统一一个版本,否则会出现各种问题。它们的版本号总是保持一致的。
Spring Cloud 简介
Spring Cloud 与其说是一个统一的框架,不如说是一系列工具的集合。这些工具都是为了解决微服务架构中的一些问题而设计的。
这一系列工具中,有一些类似于编程中的接口,有一些类似于这些接口的实现。例如,Spring Cloud Circuit Breaker 是类似于接口,它有实现 Resilience4j 和 Hystrix。当然,有一些工具是没有统一的接口的,例如服务注册中心,Consul,Nacos,Eureka 是三个实现,但他们不遵循统一的接口。
当我们说 Spring Cloud 时,这也是一个类似与接口的东西,它也有实现,例如 Spring Cloud Netflix,Spring Cloud Alibaba 等等,当然你也可以自己选择一些工具来组合,完成 Spring Cloud 的功能。