spring=spring ioc+spring aop+spring tx,其中最核心的就是ioc和aop,tx是事务的意思。
其中spring ioc简单理解就是,通过配置文件(或者注解)去帮我们创建对象的工具,创建好的对象丢到一个map中,这个map就是我们常说的spring容器。
IOC概念介绍:https://blog.csdn.net/u012643122/article/details/93380003
匹配方式是指,通过什么手段获取容器里的bean,spring提供了两种方式获取bean。
一、类型查找
<T> T getBean(Class<T> beanClass)
二、beanName查找
Object getBean(String beanName)
单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例。
@Component,@Service,@Controller,@Repository等等注解在类上的注入器,默认都是单例的。
原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例
对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效
对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效
对于每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效
其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。
如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。
看这位博主写的吧:https://www.cnblogs.com/zrtqsk/p/3735273.html
从实现上来说,是两种,一种是构造注入,一种是set注入。
从使用上来说,是两种,一种是xml,一种是注解。
以上2x2就是总共4种(不是2+2,是2x2,这是不一样的
)。
但是现在都是spring boot一般没人用xml注入了。
一般来说,spring boot默认的注解注入是set注入,如果想使用注解+构造注入,请看下面代码:
@Component
public class Bbb{
}
@Component
public class A{
private Bbb b;
@Autowired(required = true)
public CustomerDao(@Qualifier(value="bbb") Bbb b) {
this.b=b;
}
}
1.在构造函数上使用@Autowired(required = true)
。
2.在参数上使用@Qualifier(value="bbb")
接收要注入的属性。
循环依赖就是A依赖B,B又依赖A,形成一个环形的封闭依赖路径。
spring中,如果发生循环依赖,会报BeanCurrentlyInCreationExcention
错误。
官网说明:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependency-resolution
解决循环依赖问题:
1.构造注入的循环依赖,无论使用什么办法都无法解决。
2.set注入的循环依赖解决办法如下:
2.1 使用单例模式
单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,只要我们将互相依赖的bean作用域设置为单例,spring就自动会解决循环依赖。
2.2 非单例模式下使用延迟加载
使用@Lazy 注解延迟互相依赖的其中一个bean的加载(只需要Lazy其中一个就行了,当然循环依赖的两边都弄Lazy也可以),从而解决Spring在初始化bean的时候不知道先初始化哪个的问题。
@Getter
@Setter
@Scope("prototype")
@Component
public class TestCom {
@Autowired
private TestCom2 testCom2;
}
@Getter
@Setter
@Scope("prototype")
@Component
public class TestCom2 {
@Autowired
@Lazy
private TestCom testCom;
}
3.网上说的allow-circular-references=true不要信
网上说,SpringBoot 从 2.6 开始默认不允许出现 Bean 循环引用,如果需要循环依赖需要在全局配置文件设置允许循环依赖为true。我觉得这就是瞎扯,我明明试了上面两种方式都能解决,根本不需要配置什么spring.main.allow-circular-references=true
,哪里禁止了?
来看看别人说的:
开始--------------------------
SpringBoot 从 2.6 开始默认不允许出现 Bean 循环引用。而且这个是在Bean 定义上也就是类上就不允许出现循环引用。
如果需要循环依赖需要在全局配置文件设置允许循环依赖为true。
spring.main.allow-circular-references=true
或者修改启动方式:
new SpringApplicationBuilder(MyApplication.class).allowCircularReferences(true).run(args);
结束--------------------------
spring中单例的对象创建依赖于DefaultSingletonBeanRegistry,在DefaultSingletonBeanRegistry类中,定义的singletonObjects
、earlySingletonObjects
、singletonFactories
,三个map用来缓存单例对象的。
第一级缓存(也叫单例池) singletonObjects:存放已经经历了完整生命周期的Bean对象。
第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整。
第三级缓存:Map< String, ObjectFactory> singletonFactories,存放可以生成Bean的工厂
spring三级缓存解决循环依赖的过程:
aop就是面向切面编程,通过java动态代理或者asm字节码编辑技术,横向的为我们的代码增加额外的功能。
spring aop详解(注解版):https://blog.csdn.net/u012643122/article/details/126132724
事务传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 |
PROPAGATION SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
PROPAGATION MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问 TransactionManager |
PROPAGATION NOT SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用] TATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
PROPAGATION NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_ REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务 |
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
事务隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。 |
ISOLATION_READ_UNCOMMITTED | 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。 |
ISOLATION_READ_COMMITTED | 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。 |
ISOLATION_REPEATABLE_READ | 该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。 |
ISOLATION_SERIALIZABLE | 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 |
设置Spring的事务隔离级别不一定会产生效果,因为Spring事务隔离级别依赖于你使用的数据库,我们需要根据自己使用数据库真实隔离级别来设置Spring的事务隔离级别。
数据库事务隔离级别:https://blog.csdn.net/u012643122/article/details/92836000
有三种方式:
Spring三种事务实现方式(实操):https://blog.csdn.net/u012643122/article/details/106686859
上面这篇文章有:https://blog.csdn.net/u012643122/article/details/106686859
注解是spring中大量使用的标识方案,spring注解不是spring boot才有的,spring boot是用基于spring注解来代替配置文件。
用来标识类的成员变量,或者set方法,自动从spring容器中寻找对象并注入成员变量中,先匹配类型,再匹配beanName,如果类型匹配,匹配到多个对象则需要根据@Qualifier指定beanName来区分,不然会报错。
@Autowired有一个required参数,如果为true,则必须在容器中匹配到对应的对象,找不到就报错,如果为false,找不到对象就忽略了。默认是true。
boolean required() default true;
不是spring的东西,是java自带的,spring也对这个注解进行了扫描,spring中也可以用,和@Autowired效果类似。
@Resource有两个重要属性name和type:
1、如果指定了name,type,则从Spring容器中找一个名称和类型相当应的一个bean,找不到则报错。
2、如果只指定了name,则从Spring容器中找一个名称和name一样的bean,找不到则报错。
3、如果只指定了type,则从Spring容器中找一个类型和type一样的bean,找不到或者找到多个则报错。
4、如果没有指定参数,则默认找字段名称装配,找不到则按类型装配,找不到则报错。
spring将name属性解析为beanName,而type属性则被解析成为bean的类型,所以如果使用name属性,则使用by name的自动注入策略,如果使用type属性则使用by type的自动注入策略。如果都没有指定,则通过反射机制使用by name自动注入策略。
@Inject不是Spring的东西,是Google的轻量级IOC框架javax.inject
的东西,遵循了Java EE 6 规范 JSR 330 – Dependency Injection for Java,Spring的IOC也是JSR330的一种实现,Spring框架的@Autowired等同于javax.inject
的@Inject。
@Inject和@Autowired查找顺序一致,只是@Inject不能设置required属性。
@Autowired和@Resource最大的区别:@Resource先查找名称,再查找类型,和@Autowired是反过来的。
声明一个类为组件类,将被Spring容器扫描并管理。
@Component是所有Spring容器Bean相关注解的顶级注解,什么@Configuration、@Service、@Controller等等都是它的子注解,只要是@Component子注解都会被Spring的组件扫描器扫描进(容器)来。
@Component(value="xxx")
有一个value参数,用于指定这个类生成的对象在容器里的beanName。
示例:
@Component(value="myUtil555")//默认是单例
public class MyUtil {
public void ppp(){
System.out.println("hello");
}
}
@Component//默认是单例
public class Service{
@Autowired
private MyUtil myUtil555;
@Qualifier("myUtil555")
@Autowired
private MyUtil myUtil;
public void sss(){
System.out.println("hello"+((myUtil555==myUtil)?"我是单例":"我不是单例"));
}
}
和@Component没区别,唯一的区别就是名字不一样,业务层的对象不用@Component而用@Service体现的是一种编码规范。
和@Component没区别,唯一的区别就是名字不一样,DAO层的对象不用@Component而用@Repository体现的是一种编码规范。
声明一个方法为bean生产者,一般在@Configuration注解的类里声明,也可以在@Component注解的类里声明(但非常不建议这样使用
)。
@Bean接收一个核心参数value用来设置bean的beanName,如果不传,则默认使用方法名作为beanName。
@Configuration+@Bean
和@Component+@Bean
的区别:
@Configuration中所有带@Bean注解的方法生成的对象默认作用域是singleton
。
@Component中所有带@Bean注解的方法生成的对象默认作用域是prototype
。
@Bean一般用来手动生成一些对象,因为@Component不适合所有场景,因为有的对象时第三方库,我们没办法去给它加@Component。
用习惯之后,我现在连@Component都懒得用了,全部用@Bean来造对象,自己的组件也@Bean来造。
声明一个类为配置类,将被Spring容器扫描并管理。
一般配合@Bean
一起使用。
@Configuration
声明的类本身会在spring容器中生成一个单例对象,并且类中的@Bean
方法也返回单例对象。
下面代码将生成两个单例对象(autoConfiguration和xxxService):
public class XxxService {
}
@Configuration//autoConfiguration是单例
public class AutoConfiguration {
@Bean//xxxService也是单例
public XxxService xxxService() {
return new XxxService();
}
}
public class XxxDao{
}
@Configuration
public class MyConfig{
@Bean(name="xxxDao")//将返回的UserDao对象加入Spring容器,默认beanName是方法名
public XxxDao createXxxDao(){
return new XxxDao();
}
@Bean(name="xxxService")
public XxxService createXxxService(){
return new XxxService();
}
}
既不想用@Component又不想用@Bean能不能创建对象到Spring容器中呢?
答案是能,可以通过@Import+ImportSelector。
ImportSelector导入选择器,本身只是一个普通接口,接口就一个selectImports方法声明。但是通过@Import注解,可以将任意类生成对象到Spring容器中。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {XxxService.class.getName(), XxxDao.class.getName()};
}
}
@Configuration
@Import(MyImportSelector.class)
public class AutoConfiguration {
}
@ComponentScan注解是spring用来替代以前spring的application.xml配置文件中的扫描包设置:
@ComponentScan扫描的是@Component和@Component的子注解(@Configuration也是@Component的子注解)注解的类,创建这些类的实例,并把实例丢进spring容器中管理。
@SpringBootApplication已经继承了@ComponentScan,不过默认扫描的是@SpringBootApplication注解的类所在的包及子包。
如果我们有类需要加载进spring容器但又不在启动类包下,那么可以使用@ComponentScan进行覆盖(会覆盖默认的扫描包,导致spring boot默认扫描无效):
@ComponentScan({"com.xxx.xxx","org.yyy.yyy"})
@SpringBootApplication
public class App{
public static void main(String[] args){
SpringApplication.run(App.class, args);
}
}
要说一下,使用properties注入成员变量值(@ConfigurationProperties、@Value),是需要在启动类加注解@EnableAutoConfiguration,因为@SpringBootApplication已经继承了@EnableAutoConfiguration,所以可以不用管。
标识一个类为配置类,类的属性值从application.properties(或application.yml)文件中查找并注入,并像@Component一样(如果配置了@EnableConfigurationProperties)生成该类的实例放入容器中,默认为单例。
spring默认不会扫描@ConfigurationProperties,需要有@EnableConfigurationProperties(或@Component)被扫描之后才会扫描@EnableConfigurationProperties。
@ConfigurationProperties有个核心参数prefix,用来匹配application.properties对应的前缀。
示例代码:
JwtAuthProp.java
/**
* 认证服务端 属性
*/
@Data
@NoArgsConstructor
@ConfigurationProperties(prefix = PREFIX)
public class JwtAuthProp {
public static final String PREFIX = "jwt-auth";
private AuthInfo authInfo;
@Data
public static class AuthInfo {
/**
* 过期时间,单位秒
*/
private Integer expire = 7200;
/**
* 加密 服务使用
*/
private String privateKey;
/**
* 解密
*/
private String publicKey;
}
}
application.properties
jwt-auth.authInfo.privateKey=private.key64
jwt-auth.authInfo.publicKey=public.key64
也可以不和@EnableConfigurationProperties配合,而直接在@Component上使用:
@Component
@ConfigurationProperties(prefix = "b")//支持注入properties参数
public class User{
private String name;
private Integer age;
//get set...
}
application.properties
b.name=zhangsan
b.age=10
启用配置,告诉spring的组件扫描器将@ConfigurationProperties扫描进容器。
此注解需要配置在有@Component注解的类,或有@Configuration的类上。
@Configuration
@EnableConfigurationProperties(JwtAuthProp.class)//什么都不干,声明一下JwtAuthProp对象就有了。
public class AutoConfiguration {
}
public class JwtUtil {
/**
* 认证服务端使用,如 authority-server
* 生成和 解析token
*/
private static JwtAuthProp jwtAuthProp = SpringContextHolder.getBean(JwtAuthProp.class);
}
@Value可以用来标识一个成员变量的值是从单个属性是从application.properties获取并注入的。
@Value(value = "${b.name}")
private String xx;
@Value(value = "${b.age}")
private int yy;
application.properties
b.name=zhangsan
b.age=10
SpringAOP注解:https://blog.csdn.net/u012643122/article/details/126132724
声明一个类为控制器。
声明一个类为控制器,并且所有方法全部带有@ResponseBody
。
声明一个方法为控制器的请求映射,核心参数path和method。
path标识要映射的url路径,必填,method标识要映射的请求方法,非必填,默认什么GET、POST之类的所有HTTP请求都能接收。
和@RequestMapping相同,只接收POST请求。
和@RequestMapping相同,只接收GET请求。
和@RequestMapping相同,只接收PUT请求。
和@RequestMapping相同,只接收DELETE请求。
声明一个参数的值是从请求头中获取。
@RequestMapping("/hello")
public Object hello(@RequestHeader(value="token") String token){
//...your code...
}
声明一个参数的值是从请求参数中获取。
@RequestParam可以接收URL?
后面的xxx=10&yyy=20
这部分数据,也可以接收form-data
和application/x-www-form-urlencoded
的数据。
示例代码:
@RequestMapping("/hello")
public Object hello(@RequestParam User param){
//...your code...
}
声明一个参数的值是从请求体中获取,如果用@RequestBody标识一个参数为实体bean对象,那么发送请求的一方需要将请求设置为:Content-Type=application/json
,并在请求体中写入{"username":"zs","age":55}
这样的json数据。
示例代码:
@RequestMapping("/hello")
public Object hello(@RequestBody User param){
//...your code...
}
声明一个参数的值是从请求的URL中获取,核心参数value,需要在RequestMapping中用{}
声明。
@RequestMapping(value="/hello/{userId}/{imageId}")
public Object hello(@PathVariable(value="userId") int userId,@PathVariable(value="imageId") int imageId){
//...your code...
}
声明一个参数的值是从Cookie中获取。
@RequestMapping("/hello")
public Object hello(@CookieValue(value="token") String token){
//...your code...
}
标识一个方法的返回值为消息响应体,多用于向请求者返回json字符串,在方法直接返回是可以返回Object类型的任意对象,spring mvc会自动将对象转为json。
标识一个控制器或者请求响应的方法忽略Cross问题,注解在类上表示控制器的所有方法均无视Cross问题,标识在控制器的方法上则只有这个方法无视Cross问题。
异常处理器,需要和@RestControllerAdvice或@ControllerAdvice配合使用,使用aop的方式来统一处理Controller层面的异常。
@RestControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler({Exception.class}) //申明捕获那个异常类
public String myHandler(Exception e) {
return "{\"msg\":\"系统异常:"+e.getMessage()+"\"}";
}
}
标识一个类为SpringBoot启动类。
核心参数:scanBasePackages,要扫描的包,包内有@Component之类的类会被扫描进spring容器。
@SpringBootApplication(scanBasePackages={"tang.zhiyin.base","tang.zhiyin.cms"})