Spring Learning
Overview of Spring-boot
pom.xml
<parent>...</parent>
: Specify the parent POM (Project Object Model) for the project.<groupId>...</groupId>
: Group identifier.<artifactId>...</artifactId>
: Artifact identifier.<version>...</version>
: Version number.<scope>...</scope>
: specify the scope of a dependency.<scope>compile</scope>
(default): The dependency is available during compilation, testing and runtime.<scope>provided</scope>
: The dependency is availabel during compilation and testing.<scope>runtime</scope>
: The dependency is availabel during runtime and testing.<scope>test</scope>
: The dependency is only availabel at testing.
Spring boot version is included in the POM of
<parent>...</parent>
.
Java version is included in
<properties>...</properties>
.
Spring boot plugin is included in
<build><plugins>...</plugins></build>
.
Boot-strap class (Or main class)
@SpringBootApplication
=
@SpringBootConfiguration
+
@EnableAutoConfiguration
+ ComponentScan
Spring MVC HTTP
注解 | 经典用途 |
@GetMapping | 读取资源数据 |
@PostMapping | 创建资源 |
@PutMapping | 更新资源 |
@PatchMapping | 更新资源 |
@DeleteMapping | 删除资源 |
@RequestMapping | 通用请求处理 |
Spring JDBC
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
在src/main/resources/application.properties
中添加MySQL数据库连接配置:
spring.datasource.url=jdbc:mysql://<公网IP>:3306/AutoVisualDB
spring.datasource.username=zt
spring.datasource.password=12345
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
创建User实体和存储库接口
这里以创建User实体为例,然后User的持久化接口为UserRepository。
User.java
import java.util.Arrays;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.annotation.Id;
import lombok.Data;
@Data
@Table("User") // 指定映射到MySQL中的表名为User
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
@Id
private Long id;
private String username;
private String password;
private String fullname;
private String street;
private String city;
private String state;
private String zip;
private String phone;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public User(String username, String password, String fullname, String street, String city, String state, String zip, String phone) {
this.username = username;
this.password = password;
this.fullname = fullname;
this.street = street;
this.city = city;
this.state = state;
this.zip = zip;
this.phone = phone;
}
}
UserRepository.java
MySQL建表代码
创建MyQuery实体和存储库接口
MyQuery.java
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.annotation.Id;
import lombok.Data;
@Data
@Table("MyQuery")
public class MyQuery {
@Id
private Long id;
private String question;
@Column("predictedSQL")
private String predictedSQL;
@Column("visPattern")
private String visPattern;
@Column("dbPath")
private String dbPath;
@Column("dbName")
private String dbName;
public MyQuery() {}
public MyQuery(String question, String predictedSQL, String visPattern, String dbPath, String dbName) {
this.question = question;
this.predictedSQL = predictedSQL;
this.visPattern = visPattern;
this.dbPath = dbPath;
this.dbName = dbName;
}
}
MyQueryRepository.java
Spring Security
添加依赖并配置数据库连接
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
上述依赖一旦添加,基本的安全配置会被自动初始化,所有的HTTP请求路径都需要认证。
Spring Security默认开启CSRF保护(Cross-Site Request Forgery),它要求所有状态变更的HTTP方法(如POST、PUT、DELETE等)都必须验证CSRF令牌。所以想直接通过curl发送资源更改的请求是会被Forbidden的。CSRF令牌在httpSession会话创立后、在GET请求时服务器下发,存储在Cookie中。所以我认为只要启用了Spring Security以及不禁用CSRF的前提下,不执行登录则没有会话,继而也不会有服务器下发的CSRF令牌,故而不可能通过curl直接对服务器资源进行修改。
SecurityConfig
下述代码的身份验证方案是基于会话的(Session-based):在登录成功后,服务器会创建一个Session,然后将JSESSIONID通Cookie返回给客户端,在后续的请求过程中客户端会携带此Cookie让服务端验证。
下述代码的.formLogin()
表单登录是Session-based方案的典型特征。
框架代码
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.core.userdetails.UserDetailsService;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(CustomUserDetailsService customUserDetailsService) {
return customUserDetailsService;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
.mvcMatchers("/autovisual").hasRole("USER") // 访问/autovisual路径时,只有身份为ROLE_USER的用户才能访问
.anyRequest().permitAll() // 其他路径均可以自由访问,无需身份验证
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/autovisual", true) // 登录成功后强制导航到/autovisual路径
.and()
.build();
}
}
上述三个@Bean
在注册后都是在Spring
Security框架中自动注入的。
需要自定义实现UserDetailsService接口
简洁实现代码
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(UserRepository userRepo) {
return username -> {
User user = userRepo.findByUsername(username);
if (user != null) {
return user;
}
throw new UsernameNotFoundException("User '" + username + "' not found");
};
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
.mvcMatchers("/autovisual").hasRole("USER") // 访问/autovisual路径时,只有身份为ROLE_USER的用户才能访问
.anyRequest().permitAll() // 其他路径均可以自由访问,无需身份验证
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/autovisual", true) // 登录成功后强制导航到/autovisual路径
.and()
.build();
}
}
这里使用lambda表达式简洁实现函数式接口UserDetailsService
。
Registration
Registration Controller
import datainsights.data.UserRepository;
@Controller
@RequestMapping("/register")
public class RegistrationController {
private UserRepository userRepo;
private PasswordEncoder passwordEncoder;
public RegistrationController(
UserRepository userRepo, PasswordEncoder passwordEncoder) {
this.userRepo = userRepo;
this.passwordEncoder = passwordEncoder;
}
@GetMapping
public String registerForm() {
return "registration";
}
@PostMapping
public String processRegistration(RegistrationForm form) {
userRepo.save(form.toUser(passwordEncoder));
return "redirect:/login";
}
}
注册表单视图
存放于路径./src/main/resources/templates/registration.html
<!DOCTYPE html>
<html xmlns = "http://www.w3.org/1999/xhtml" xmlns:th = "http://www.thymeleaf.org">
<head>
<title>User Rehistration</title>
</head>
<body>
<h1>Register</h1>
<img th:src = "@{/images/LoginImage.png}"/>
<form method = "POST" th:action = "@{/register}" id = "registerForm">
<label for = "username">Username: </label>
<input type = "text" name = "username"/><br/>
<label for = "password">Passward: </label>
<input type = "password" name = "password"/><br/>
<label for = "confirm">Confirm Password: </label>
<input type = "password" name = "confirm"/><br/>
<label for = "fullname">Full name: </label>
<input type = "text" name = "fullname"/><br/>
<label for = "street">Street: </label>
<input type = "text" name = "street"/><br/>
<label for = "city">City: </label>
<input type = "text" name = "city"/><br/>
<label for = "state">State: </label>
<input type = "text" name = "state"/><br/>
<label for = "zip">Zip: </label>
<input type = "text" name = "zip"/><br/>
<label for = "phone">Phone: </label>
<input type = "text" name = "phone"/><br/>
<input type = "submit" value = "Register">
</form>
</body>
</html>
点击视图中的Register按钮,这应该会触发RegisterController.java
中的POST,然后将表单中的数据绑定到形参form中。
Registration Form
import datainsights.User;
import lombok.Data;
@Data
public class RegistrationForm {
private String username;
private String password;
private String fullname;
private String street;
private String city;
private String state;
private String zip;
private String phone;
public User toUser(PasswordEncoder passwordEncoder) {
return new User(
username, passwordEncoder.encode(password),
fullname, street, city, state, zip, phone);
}
}
这是用于接收表单数据的类,form的类型。
常用身份校验方案
Session-based:上述代码正是基于Session-based进行身份校验的。
Token-based:客户端在登录服务端之后会获得一个Token令牌,在之后的请求中携带这个Token令牌。三方登录的身份验证也是使用的这种方式,由三方授权服务器向客户端返回Token令牌。
Spring RESTful
Rest Controller
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.ResponseStatus;
import tacos.Taco;
import tacos.data.TacoRepository;
@RestController
@RequestMapping(path = "/api/tacos", produces = "application/json")
@CrossOrigin(origins = {"http://tacocloud:8080", "http://tacocloud.com"})
public class TacoController {
private TacoRepository tacoRepository;
public TacoController(TacoRepository tacoRepository) {
this.tacoRepository = tacoRepository;
}
@GetMapping(params = "recent")
public Iterable<Taco> recentTacos() {
PageRequest page = PageRequest.of(0, 12, Sort.by("createdAt").descending());
return tacoRepository.findAll(page).getContent();
}
@PostMapping(consumes = "application/json")
@ResponseStatus(HttpStatus.CREATED)
public Taco postTaco(@RequestBody Taco taco) {
return tacoRepository.save(taco);
}
@PutMapping(path = "/{orderId}", consumes = "application/json")
public TacoOrder putOrder(@PathVariable("orderId") Long orderId, @RequestBody TacoOrder order) {
order.setId(orderId);
return tacoRepository.save(order);
}
@PatchMapping(path = "/{order}", consumes = "application/json")
public TacoOrder patchOrder(@PathVariable("orderId") Long orderId, @RequestBody TacoOrder patch) {
TacoOrder order = tacoRepository.findById(orderId).get();
if (patch.getDeliveryName() != null) {
order.setDeliveryName(patch.getDeliveryName());
}
}
@DeleteMapping("/{orderId}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteOrder(@PathVariable("orderId") Long orderId) {
try {
tacoRepository.deleteById(orderId);
} catch (EmptyResultDataAccessException e) {}
}
}
@RestController
注解可能相当于@Controller
+
@ResponseBody
的作用。它除了有Controller的作用之外,还要求其类中所定义的方法都要把返回值写入响应体。
@RequestMapping(path = "/api/tacos", produces = "application/json")
中的path="/api/tacos"
定义了客户端为访问类中的方法所必须的请求头;produces="application/json"
限定了类中方法的返回响应体应该是json格式。
@CrossOrigin
public Iterable recentTacos()部分
从服务器端检索数据。
当我使用curl localhost:8080/api/tacos?recent
时,会得到返回的json格式内容。
public Taco postTaco(@RequestBody Taco taco)部分
发送数据到服务器端。
postTaco()
方法会处理对/api/tacos
的POST请求。consumes = "application/json"
意味着请求的输入需要为json格式文件。
@RequestBody Taco taco
请求体中的json被绑定到参数taco上。
如果我的taco实体由Taco.java和Ingredient.java定义。则相应的json格式文件应为:
{
"name": "Super Veggie Taco",
"ingredients": [
{
"id": "1",
"name": "Flour Tortilla",
"type": "WRAP"
},
{
"id": "2",
"name": "Grilled Peppers",
"type": "VEGGIES"
}
]
}
通过命令行发送该POST请求(目前还未验证过下述代码的可行性):
curl -X POST http://localhost:8080/api/tacos \
-H "Content-Type: application/json" \
-d '{"name": "Super Veggie Taco", "ingredients": [{"id": "1", "name": "Flour Tortilla", "type": "WRAP"}, {"id": "2", "name": "Grilled Peppers", type": "VEGGIES"}]}'
@ResponseStatus(HttpStatus.CREATED)
将请求成功且成功创建了一个资源的HTTP状态201传递给客户端。
public TacoOrder putOrder(@PathVariable(“orderId”) Long orderId, @RequestBody TacoOrder order) 部分
public TacoOrder patchOrder(@PathVariable(“orderId”) Long orderId, @RequestBody TacoOrder patch) 部分
public void deleteOrder(@PathVariable(“orderId”) Long orderId)部分
从服务器端删除数据。
@ResponseStatus(HttpStatus.NO_CONTENT)
请求成功且没有资源内容的HTTP状态码为204。
Rest Data
GET存储库
Rest Data是用来暴露存储库API的。要完成API的暴露,需要设置以下两步。
- 在pom.xml中添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
- 在目标存储库上添加注解:(例如)
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource(path = "myqueries")
public interface MyQueryRepository extends PagingAndSortingRepository<MyQuery, Long> {
}
之后就可以通过命令行curl "localhost:8080/myqueries"
返回存储库内容了。
调整上述API:在src\main\resources\application.properties
中添加下述代码后即可通过新的APIcurl "localhost:8080/repo-api/myqueries"
访问存储库内容。
保护Restful API
JMS异步消息
引入依赖
Apache ActiveMQ Artemis 是 Apache ActiveMQ 的更新版。在添加下述依赖后,Spring-boot会自动创建一个JmsTemplate的Bean。
Artemis
配置代理
代理的配置与引入的依赖相对应。
Artemis 代理
在默认情况下,Spring会假定Artemis代理在localhost的61616端口运行。
使用JMS发送消息
tacos.messaging
JmsOrderMessagingService.java
package tacos.messaging;
import javax.jms.JMSException;
import javax.jms.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
import tacos.TacoOrder;
@Service
public class JmsOrderMessagingService implements OrderMessagingService {
private JmsTemplate jms;
@Autowired
public JmsOrderMessagingService(JmsTemplate jms) {
this.jms = jms;
}
@Override
public void sendOrder(TacoOrder order) {
jms.convertAndSend("tacocloud.order.queue", order,
this::addOrderSource);
}
private Message addOrderSource(Message message) throws JMSException {
message.setStringProperty("X_ORDER_SOURCE", "WEB");
return message;
}
}
@Service
是一个标识服务类的注解,表示这个类是Spring管理的Bean,它是一种特殊的@Component
注解。
jms.convertAndSend()
方法:
对order进行转换:将
TacoOrder
的实例转化为json格式。转换器在MessagingConfig.java
中声明为了Bean。对消息进行后处理:给消息增加了一个自定义头部。
发送消息到目的地。
MessagingConfig.java
package tacos.messaging;
import java.util.HashMap;
import java.util.Map;
import javax.jms.Destination;
import org.apache.activemq.artemis.jms.client.ActiveMQQueue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import tacos.TacoOrder;
@Configuration
public class MessagingConfig {
@Bean
public MappingJackson2MessageConverter messageConverter() {
MappingJackson2MessageConverter messageConverter =
new MappingJackson2MessageConverter();
messageConverter.setTypeIdPropertyName("_typeId");
Map<String, Class<?>> typeIdMappings = new HashMap<String, Class<?>>();
typeIdMappings.put("order", TacoOrder.class);
messageConverter.setTypeIdMappings(typeIdMappings);
return messageConverter;
}
@Bean
public Destination orderQueue() {
return new ActiveMQQueue("tacocloud.order.queue");
}
}
上述代码将消息转换器和目的地都声明为了Bean。
在消息转换器中,setTypeIdPropertyName("_typeId")
决定了待转换对象的全限定类名存储在json文件中的”_typeId”目录下。setTypeIdMappings(typeIdMappings)
建立了String
到全限定类名的映射,这样可以提高发送端和接收端消息传递的灵活性。
使用JMS接收消息(Pull model)
tacos.kitchen
JmsOrderReceiver.java(原始Message)
这里能接收到消息的原始Message。
package tacos.kitchen.messaging.jms;
import javax.jms.Message;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
import tacos.TacoOrder;
import tacos.kitchen.OrderReceiver;
@Profile("jms-template")
@Component
public class JmsOrderReceiver implements OrderReceiver {
private JmsTemplate jms;
private MessageConverter converter;
@Autowired
public JmsOrderReceiver(JmsTemplate jms, MessageConverter converter) {
this.jms = jms;
this.converter = converter;
}
@Override
public TacoOrder receiveOrder() {
Message message = jms.receive("tacocloud.order.queue");
return (TacoOrder) converter.fromMessage(message);
}
}
JmsOrderReceiver.java(仅载荷)
这里仅接收到消息的载荷。
package tacos.kitchen.messaging.jms;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
import tacos.TacoOrder;
import tacos.kitchen.OrderReceiver;
@Profile("jms-template")
@Component
public class JmsOrderReceiver implements OrderReceiver {
private JmsTemplate jms;
public JmsOrderReceiver(JmsTemplate jms) {
this.jms = jms;
}
@Override
public TacoOrder receiveOrder() {
return (TacoOrder) jms.receiveAndConvert("tacocloud.order.queue");
}
}
MessagingConfig.java
package tacos.kitchen.messaging.jms;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import tacos.TacoOrder;
@Profile({"jms-template", "jms-listener"})
@Configuration
public class MessagingConfig {
@Bean
public MappingJackson2MessageConverter messageConverter() {
MappingJackson2MessageConverter messageConverter =
new MappingJackson2MessageConverter();
messageConverter.setTypeIdPropertyName("_typeId");
Map<String, Class<?>> typeIdMappings = new HashMap<String, Class<?>>();
typeIdMappings.put("order", TacoOrder.class);
messageConverter.setTypeIdMappings(typeIdMappings);
return messageConverter;
}
}
@Profile({"jms-template", "jms-listener"})
表示当Spring应用程序运行时的活动配置文件包含”jms-template”或”jms-listener”中的任意一个时,MessagingConfig类中的配置才会被加载和生效。
可以通过下述命令行运行Spring时激活该Profile:
此外,我觉得上面代码再配置一个MessagingConfig多余,因为它里面声明为Bean的消息转换器在消息发送器的MessageConfig里面已经声明过了。
使用JMSListener接收消息(Push model)
tacos.kitchen
OrderListener.java
package tacos.kitchen.messaging.jms.listener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import tacos.TacoOrder;
import tacos.kitchen.KitchenUI;
@Profile("jms-listener")
@Component
public class OrderListener {
private KitchenUI ui;
@Autowired
public OrderListener(KitchenUI ui) {
this.ui = ui;
}
@JmsListener(destination = "tacocloud.order.queue")
public void receiveOrder(TacoOrder order) {
ui.displayOrder(order);
}
}
@JmsListener
会监听目的地的消息。当消息到达时,receiveOrder()
方法会被自动调用,消息载荷会充当参数order
KitchenUI.java
package tacos.kitchen;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import tacos.TacoOrder;
@Component
@Slf4j
public class KitchenUI {
public void displayOrder(TacoOrder order) {
// TODO: Beef this up to do more than just log the received taco.
// To display it in some sort of UI.
log.info("RECEIVED ORDER: " + order);
}
}
@Component
是将类标记为Spring
Bean的通用注解,其派生注解有@Service
,@Controller
,@Repository
。
@Bean
通常用于注解配置类@Configuration
中的方法,它适用于更精细化地对Bean进行创建和控制。
Kafka异步消息
先启动ZooKeeper再启动Kafka。
添加Kafka依赖
配置代理
Spring知识点
Bean的生命周期
扫描找到Bean -> 注入对象和值,然后实例化 -> Bean的销毁。
环境变量Environment
import org.springframework.core.env.Environment;
public class MyClass {
@Autowired
protected Environment environment;
public void myMethod() {
System.out.println(environment.getProperty("OPENAI_BASE_URL"));
}
}
上述代码中的environment变量可以感受到运行命令中的环境变量,例如下面的运行命令中的参数abc就会被上述代码打印出来。
environment变量还可以感受到配置文件application.properties中的变量,例如:
OPENAI_BASE_URL=abc
Spring注解
@Slf4j
@Slf4j
注解用于自动在一个类中生成一个日志处理对象log
,以便于在代码中直接使用log.debug()
,
log.info()
, log.error()
等方法。
下述两种代码是完全等价的。
@Data
@Data
=@Getter
+@Setter
+@ToString
+@EqualsAndHashCode
+@RequiredArgsConstructor
@Getter
:为所有字段生成getter方法。@Setter
:为所有非final
字段生成setter方法。@ToString
:为所有字段生成toString方法。@EqualsAndHashCode
:为获得注解的类生成.equals()
和.hashCode()
方法。@RequiredArgsConstructor
:为所有final的字段,以及标记为@NonNull
且未初始化的字段生成一个构造函数。
下述两段代码是等价的。
不使用@Data注解
import java.util.Objects;
public class User {
private final String id;
private String name;
private int age;
public User(String id) {
this.id = id;
}
public String getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age &&
Objects.equals(id, user.id) &&
Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
@Builder
@Builder
能快速实现建造者设计模式。
类级别@Builder
方法级别@Builder
@Configuration、@Bean
@Configuration
注解的类是配置类,用于定义Bean的创建。@Bean
注解的方法会被注册到Spring的应用上下文(ApplicationContext)中,可以被@Autowired
注入使用。@Bean()
中也可以指定别名,如@Bean(myAIService)
。好像别名不是通过这种方式指定
带有@Component
,@Service
,@Repository
,@Controller
注解的类都会被Spring扫描并注册为Bean。
@Autowired
@Autowired
注解的字段会被自动注入Spring容器中已有的Bean。
当有多个实现类时,需要额外为@Autowired选择实例的注入。假设ApiService有两个实现类OpenAIService和QwenAIService
@SpringBootApplication
@SpringBootApplication
= @Configuration
+
@EnableAutoConfiguration
+ @ComponentScan
@EnableAutoConfiguration
:启用Spring-boot的自动配置机制。例如项目中如果引入了spring-boot-starter-data-mongodb,那么Spring 会自动配置 MongoDB 的连接。@ComponentScan
:启用组件扫描,自动发现并注册Bean。它会自动扫描被注解类所在包及其子包的组件(@Component、@Service、@Controller等)
案例分析:
package com.tencent.supersonic;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(scanBasePackages = {"com.tencent.supersonic", "dev.langchain4j"},
exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@EnableScheduling
@EnableAsync
public class StandaloneLauncher {
public static void main(String[] args) {
SpringApplication.run(StandaloneLauncher.class, args);
}
}
@EnableScheduling
启用Spring的定时任务,表明该应用需要执行定时任务(如数据同步、缓存刷新等)。
@EnableAsync
启用Spring的异步方法执行功能,表明该应用需要处理异步任务(如邮件发送、文件处理等),提高系统吞吐量。