2023/4/15 更新
对spring进行重新认识
1、spring是什么?
spring是一个开源的java框架 用于提高开发效率和系统的可维护性 它的显著特点是支持IoC和AOP,可以很方便的对数据库进行访问。
2、Spring主要组件的功能
Core Container
spring-core : Spring框架基本的核心工具类
spring-beans : 提供对bean的创建、配置、管理等功能的支持
spring-context : 提供对国际化、事件传播、资源加载等功能的支持
spring-expression : 提供对表达式语言SpEL的支持 只依赖于core模块 不依赖于其它模块
AOP
spring-aspects 为AspectJ的集成提供支持
spring-aop 提供面向切面的编程实现
spring-instrument 提供为JVM添加代理的功能
Data Access/Integration
spring-jdbc 提供对数据库访问的抽象JDBC
spring-tx 提供对事务的支持
spring-orm 提供对Hibernate JPA iBatis等ORM框架的支持(解决面向对象与关系型数据库存在的互不匹配现象的技术,主要包括Hibernate iBATIS mybatis eclipseLink JFinal)
Spring Web
spring-web 对Web功能的实现提供一些最基础的支持
spring-webmvc 提供对Spring mvc的实现
spring-websocket 提供对WebSocket的支持 WebSocket可以让客户端和服务段双向通信
spring-webflux 提供对WebFlux的支持 WebFlux是Spring Framework 5.0中引入的新的响应式框架
3、什么是MVC?
MVC是一种架构
spring MVC提供对MVC的支持
4、spring boot是什么?
Spring的开发配置非常繁琐 Spring boot用于简化Spring的开发 尤其是减少配置文件
IoC容器
4.15更新 重新理解IoC
为什么叫控制反转?按理说,在程序中生成一个对象与否是我们也就是程序员来控制的。
比如你需要new一个对象,那么就应该写 A a = new A();
如果发现 A 这个类没有B不行,会报错,那么还需要把B对象也实例化出来,才能让程序跑起来。
这样做恐怕在现在的项目是非常难用的,一个service的对象实例化,恐怕光是new各种依赖的对象就会让程序员疯掉了。
那么如果让Spring来帮我们管理创建对象的过程呢?
IoC容器帮助我们把需要实例化的对象给创建好并且存储起来了 需要的时候直接提供给需要的对象,直接组装就完事了。这就是在使用spring的过程中常见的依赖注入的操作。
以前使用XML来配置bean,现在我们更多的使用各种注解来告诉Spring,哪些对象是需要被自动装配的,比如@Component @Bean 注入的时候则使用@Autowired @Resource来进行依赖注入,这样就大大简化了代码,也减少了各种繁琐的操作。
什么是Spring bean?
Bean就是被IoC容器管理的对象
org.springframework.bean和org.springframework.context实现了IoC。
注入Bean的注解有@Autowired 和@Resource
Autowired是spring提供的注解,Resource是JDK提供的注解
容器是一种为某种特定组件的运行提供必要支持的一个软件环境 IoC指的是Inversion of Control
组件在IoC容器中被装配出来需要某种“注入”机制
比如某个组件需要datasource
它自己并不会去创建datasource
而是等待外部去注入datasource
这样不同的组件就可以实现资源的共享 另一个组件同样可以注入datasource
依赖注入方法1 set函数
public class BookService {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
方法2 构造方法
public class BookService {
private DataSource dataSource;
public BookService(DataSource dataSource) {
this.dataSource = dataSource;
}
}
方法3 在配置文件中组装bean
<bean id="userService" class="com.itranswarp.learnjava.service.UserService">
<property name="mailService" ref="mailService" />
</bean>
在编写好配置文件后 可以加载配置文件自动生成bean
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
取出bean
// 获取Bean:
UserService userService = context.getBean(UserService.class);
// 正常调用:
User user = userService.login("bob@example.com", "password");
除了ApplicationContext
之外,还有一种容器叫BeanFactory
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MailService mailService = factory.getBean(MailService.class);
Annotation配置
使用配置文件的方法非常繁琐 我们可以使用注解来简化这个过程
@Component
public class MailService {
...
}
使用该注解定义了一个Bean 默认名称为mailservice
接着给另一个bean添加注解@Autowired
@Component
public class UserService {
@Autowired
MailService mailService;
...
}
使用@Autowired
可以简化注入
@Configuration
为配置类
@ComponentScan
告诉容器自动搜索当前类所在的包以及子包 把标注为@component
的Bean自动创建出来 根据@Autowired
进行装配
使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:
- 每个Bean被标注为@Component并正确使用
@Autowired
注入; - 配置类被标注为
@Configuration
和@ComponentScan
; - 所有Bean均在指定包以及子包内。
注入List
如何把接口相同但实现不同的类注入到list中
首先定义一个接口
public interface Validator {
void validate(String email, String password, String name);
}
接着对该接口进行不同的实现
@Component
public class EmailValidator implements Validator {
public void validate(String email, String password, String name) {
if (!email.matches("^[a-z0-9]+\\@[a-z0-9]+\\.[a-z]{2,10}$")) {
throw new IllegalArgumentException("invalid email: " + email);
}
}
}
@Component
public class PasswordValidator implements Validator {
public void validate(String email, String password, String name) {
if (!password.matches("^.{6,20}$")) {
throw new IllegalArgumentException("invalid password");
}
}
}
@Component
public class NameValidator implements Validator {
public void validate(String email, String password, String name) {
if (name == null || name.isBlank() || name.length() > 20) {
throw new IllegalArgumentException("invalid name: " + name);
}
}
}
我们在注入这些bean时这样写
@Component
public class Validators {
@Autowired
List<Validator> validators;
public void validate(String email, String password, String name) {
for (var validator : this.validators) {
validator.validate(email, password, name);
}
}
}
所有类型为Validator的Bean会配装为一个List注入进来
使用oder注解可以指定list中bean的顺序
使用@Autowired(required=false)
来表示该注入是可选的
创建第三方bean
我们可以在@Configuration
类中编写一个java方法创建并返回它 使用@Bean
注解
@Configuration
@ComponentScan
public class AppConfig {
// 创建一个Bean:
@Bean
ZoneId createZoneId() {
return ZoneId.of("Z");
}
}
初始化和销毁
@PostConstruct
用于初始化 @PreDestroy
用于销毁
@Component
public class MailService {
@Autowired(required = false)
ZoneId zoneId = ZoneId.systemDefault();
@PostConstruct
public void init() {
System.out.println("Init mail service with zoneId = " + this.zoneId);
}
@PreDestroy
public void shutdown() {
System.out.println("Shutdown mail service");
}
}
这样的Bean的执行流程为
1、构造方法
2、根据@Autowired
注入
3、初始化
4、销毁容器
别名
类型相同的Bean 在创建Bean时可以使用别名区分
@Configuration
@ComponentScan
public class AppConfig {
@Bean("z")
ZoneId createZoneOfZ() {
return ZoneId.of("Z");
}
@Bean
@Qualifier("utc8")
ZoneId createZoneOfUTC8() {
return ZoneId.of("UTC+08:00");
}
}
在注入时
@Component
public class MailService {
@Autowired(required = false)
@Qualifier("z") // 指定注入名称为"z"的ZoneId
ZoneId zoneId = ZoneId.systemDefault();
...
}
可以把某个bean指定为@Primary
@Configuration
@ComponentScan
public class AppConfig {
@Bean
@Primary // 指定为主要Bean
@Qualifier("z")
ZoneId createZoneOfZ() {
return ZoneId.of("Z");
}
@Bean
@Qualifier("utc8")
ZoneId createZoneOfUTC8() {
return ZoneId.of("UTC+08:00");
}
}
没有指定bean的名字时将注入primary的bean
FactoryBean
spring提供了工厂模式 使用FactoryBean
接口
@Component
public class ZoneIdFactoryBean implements FactoryBean<ZoneId> {
String zone = "Z";
@Override
public ZoneId getObject() throws Exception {
return ZoneId.of(zone);
}
@Override
public Class<?> getObjectType() {
return ZoneId.class;
}
}
注入配置
使用@PropertySource
可以自动读取配置文件 再使用@Value
注入
@Configuration
@ComponentScan
@PropertySource("app.properties") // 表示读取classpath的app.properties
public class AppConfig {
@Value("${app.zone:Z}")
String zoneId;
@Bean
ZoneId createZoneId() {
return ZoneId.of(zoneId);
}
}
"${app.zone}"
表示读取key为app.zone的value,如果key不存在,启动将报错;"${app.zone:Z}"
表示读取key为app.zone的value,但如果key不存在,就使用默认值Z。
条件装配
根据注解@Profile
可以决定在当前环境下是否创建bean
使用注解Conditionnal
则可以要求在满足某个条件时才创建bean
4.15更新
什么是spring AOP
面向切面编程,将与业务无关,但一些公用的部分封装起来,比如事务处理、日志管理、权限控制等等。
一些常见术语:
AspectJ是目前java生态系统中最完整的AOP框架,并且相对简单易用。
AspectJ定义的通知类型有
- Before:前置通知
- After:目标对象的方法调用之后触发
- AfterReturning:返回通知,目标对象的方法调用完成,返回结果值后触发
- AfterThrowing:异常通知,目标对象的方法运行中抛出/触发异常后触发。
- Around:环绕通知,在目标对象的方法调用前后触发。
待更新具体例子
什么是spring mvc?
MVC模式下 我们一般把后端拿项目分为Service层 Dao层 Entity层 Controller层