spring是什么
Spring是一个轻量级的IOC和AOP的容器框架
spring的优点
- 对主流框架的集成提供了支持
- 依赖倒转(DI)机制将对象之间的依赖关系交由给框架处理,降低组件的耦合性
- 提供了AOP技术,支持安全、事务、日志、权限的集中式管理,从而提供了更好的复用
什么是IOC
IOC,Inversion of Controller,控制反转,指将对象的控制权交给Spring框架,由Spring来负责控制对象的生命周期(创建、销毁)和对象的依赖关系。不再需要主动new去创造对象,由spring容器帮我们创建。
什么是DI
IOC可以动态的向某个对象提供它所需要的其他对象,这一点是通过DI(依赖注入)来实现的,即应用程序在运行时依赖IOC容器动态注入对象所需要的外部依赖。DI具体是通过反射实现注入的,反射允许程序在运行时动态生成对象、执行对象方法、改变对象属性等
IOC原理
Spring的Ioc实现原理就是工厂模式+反射机制
反射
https://www.liaoxuefeng.com/wiki/1252599548343744/1264799402020448
什么是反射
反射机制是程序在运行时能够获取自身的信息,只要给定类的名字就可以通过反射机制获得类的信息
反射的作用
1、在运行时访问类的字段信息Field,getField(name)/getDeclaredField(name)
Field包括字段名称 name getName(),字段类型 int… getType(),字段修饰符 public… 是一个整数getModifiers()
对于private字段,可以调用Field.setAccessible(true),一律字段就可访问,当然假如JVM存在 SecurityManager,那就能阻止访问
2、在运行时设置类的字段信息Field.set(Object, Object)
3、在运行时访问类的方法信息Method,getMethod(方法名字,参数类型.class)/getDeclaredMethod(方法名字,参数类型.class)
Method包括方法名称getName(),方法返回值类型getReturnType(),返回方法参数类型getParameterTypes(),返回方法的修饰符getModifiers()
对于private类型的方法,依旧是用Method.setAccessible(true)
4、在运行时用invoke(对象实例,参数)调用方法
5、可以用newInstance()调用无参构造创建新的对象,而对于有参构造可以用getConstructor(参数.class)获得
6、可以实现动态代理
反射的场景
- 读取配置文件
- 加载驱动
反射的优点
提高了程序的灵活性和拓展性,允许程序创建和控制类对象
反射的缺点
- 性能不如直接性代码
- 类信息会泄露,造成不安全,破坏对象的封装
如何获取类实例
┌───────────────────────────┐
│ Class Instance │──────> String
├───────────────────────────┤
│name = "java.lang.String" │
├───────────────────────────┤
│package = "java.lang" │
├───────────────────────────┤
│super = "java.lang.Object" │
├───────────────────────────┤
│interface = CharSequence...│
├───────────────────────────┤
│field = value[],hash,... │
├───────────────────────────┤
│method = indexOf()... │
└───────────────────────────┘
- 可以通过类的静态变量来获取
Class cl= String.class
- 通过实例变量提供的getClass()方法获取
String s="Hello";
Class cls=s.getClass();
- 通过完整类名获取
Class cls=Class.forName("java.lang.String")
如何通过类的实例获取类的字段信息
Class cls=Student.class;
//根据字段名获取某个public类的字段信息(包括父类)
cls.getField("name");
//根据字段名获取某个public类的字段信息(不包括父类)
cls.getDeclaredField("name");
AOP
什么是AOP
AOP是指面向切面编程,能够解决OOP纵向编程中会出现像权限认证、日志、事务处理外围事务导致核心业务代码混乱冗余的问题。将外围事务封装为一个可重用的模块,命名为切面,降低耦合度提高可维护性
AOP的原理
AOP的实现原理在于代理模式,分为静态代理和动态代理。像AspectJ就是静态代理,SpringAOP就是动态代理
- AspectJ是静态代理,也称为编译时增强,会在编译期间生成AOP代理类,并将切面织入Java字节码中,运行的时候就是增强之后的AOP对象
- SpringAOP使用的是动态代理,所谓动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法(反射invoke)
静态代理和动态代理的区别
生成AOP代理对象的时机不同,相对来说Aspect的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器处理,而SpringAOP不需要
SpringAOP实现动态代理的方式(原理)
1、JDK动态代理
- jdk动态代理只提供接口代理,不支持类的代理,所以被代理的类必须要实现接口
- jdk动态代理的核心是invocationHandler接口和proxy类,在获取代理对象时使用Proxy类来动态创建目标代理类
- 当代理对象调用真实对象的方法时,会自动跳转到代理对象关联的invocationHandler对象,其会通过invoke()方法反射来调用目标类中的方法,动态地把业务横切进去
invocationHandler中invoke(Object proxy,Method method,Object[] args)
- proxy是指生成代理的对象
- method是指目标对象的方法,通过反射调用
- args是指目标对象方法的参数
代理步骤
- 通过实现invocationHandler接口创建自己的调用处理器
- 为Proxy类提供要代理对象的类加载器、接口、处理器来创建动态代理对象
- 当代理对象调用真实对象的方法时,会自动跳转到代理对象关联的invocationHandler对象,其会通过invoke()方法反射来调用目标类中的方法,动态地把业务横切进去
创建接口
public interface Subject {
public void SayHello(String name);
}
创建接口实现类
public class SubjetcImpl implements Subject{
@Override
public void SayHello(String name) {
System.out.println(name+"嘿嘿");
}
}
继承invocationHandler实现自己的调用器处理器
public class InvocationHandlerImpl implements InvocationHandler {
/**
* 要代理的真实对象
*/
private Object subject;
public InvocationHandlerImpl(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理之前
System.out.println("调用之前先说个jdk动态代理牛逼");
System.out.println("Method: "+method);
//代理中
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用,
Object returnValue = method.invoke(subject, args);
//代理之后
System.out.println("调用结束那就说声拜拜动态代理");
return returnValue;
}
}
测试,通过proxy类生成动态代理对象
public class Main {
public static void main(String[] args) {
//要被代理的对象
Subject realSubject=new SubjetcImpl();
//要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
ClassLoader classLoader = realSubject.getClass().getClassLoader();
Class[] interfaces = realSubject.getClass().getInterfaces();
/**
* 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
*/
Subject subject =(Subject)Proxy.newProxyInstance(classLoader, interfaces, handler);
System.out.println("动态代理类的类型"+subject.getClass().getName());
subject.SayHello("aciu");
}
}
2、CGLIB
- 如果代理类没有实现接口,那么AOP就会选择使用CGLIB,用继承方式做动态代理。若该类为final类,则没法用CGLIB
- CGLIB(Code Generation Library)是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象。并覆盖其中特定方法并添加增强代码,从而实现AOP。
SpringAOP里的名词概念
- 连接点(Join Point):指程序运行过程中执行的方法。SpringAOP中一个连接点总代表一个方法的执行
- 切面(Aspect):被抽取出来的公共模块,可以用来横切多个对象。Aspect切面可以看成Poincut切点和Advice通知的结合,一个切面可以由多个切点和通知组成(SpringAOP中用@Aspect)
- 通知(Advice):指要在连接点(Join Point)上执行的操作,即增强的逻辑,比如权限的校验和日志的记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。
- 目标对象(Target):包含连接点的对象,也称作被通知的对象
- 织入(Weaving):通过动态代理在目标对象的方法中执行增强逻辑的过程,即在Target的Join point中执行Advice
- 引入(Introduction):添加额外的方法或字段到被通知的类中
Spring通知有那些类型
- 前置通知(Before Advice):在连接点之前执行通知
- 后置通知(After Advice):在连接点退出的时候执行的通知
- 环绕通知(Around Advice):包围一个连接点的通知,可以在方法调用前后完成自定义行为
- 返回后通知(Afterreturning Advice):在连接点正常完成后执行的通知
- 抛出异常(AfterThrowing Advice):在方法抛出异常退出时执行的通知
Advice的通知顺序
没有异常的执行顺序
- around before advice
- before advice
- target method执行
- after advice
- around after advice
- afterReturning advice
出现异常的执行顺序
- around before advice
- before advice
- target method执行
- after advice
- around after advice
- afterThrowing advice
- 异常发生
BeanFactory和ApplicationContext有什么区别
- BeanFactory采用的是延时加载形式注入bean的,只有在被调用的时候bean才去实例化,难以发现bean的配置问题
- ApplicationContext是容器启动时就实例化了所有Bean,有利于检查bean的配置,而且因为都实例化好了所以运行速度更快,但占用空间
Spring容器的启动流程
- 初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中。这一步是把BeanFactory、BeanDefinitionReader、路径扫描器加载到容器中
- 将配置类的BeanDefinition注册到容器中
- 调用refresh()刷新容器,refresh()中的obtainBeanFactory方法,完成BeanFactory的初始化。prepareBeanFactory(beanFactory)是BeanFactory的预处理,完成对Bean的填充
Spring的Bean周期
大概周期是 实例化->属性赋值->初始化->销毁
-
实例化Bean,通过反射生成对象
对于BeanFactory容器,只有当用户请求时才会调用createBean去实例化
对于ApplicationContext,容器启动完毕后就会调用BeanDefinition去实例化所有的Bean
-
设置对象属性,也就是填充bean。
-
调用aware接口相关方法,如invokeAwareMethod,可以完成BeanName,BeanFactory,BeanClassLoader对象的属性设置
-
调用BeanPostProcessor前置处理
-
初始化bean,对实例化好后的Bean进行依赖注入的过程,调用initmethod方法,如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
-
调用BeanPostProcessor进行后置处理
-
获得完整对象,可以通过getBean的方式来进行对象的获取
-
销毁
bean的初始化和实例化
http://t.csdn.cn/hKIbl
Spring中Bean的作用域
- singleton:默认作用域,单例bean,每个容器只有一个bean的实例
- prototype:每次从容器中调用Bean时,都返回一个新的实例
- request:为每一个request请求创建一个实例,请求完成后,bean会被回收
- session:与request类似,同一个session会话共享一个实例,不同的会话使用不同的实例
- global-session:全局作用域,所有的会话共享一个实例
Spring中的bean是线程安全的吗
- 对于prototype的Bean,因为每次请求都会创建新的bean,所以不存在共享bean,不会有线程安全问题
- 对于Singleton的Bean,假若是无状态的Bean,像Controller类、Service类等,不会对bean成员进行修改的类,就是线程安全的。假若是有实例变量的对象,可以保存数据的,像Model,就是非线程安全的。
如何保证Bean线程安全
- 将作用域从Singleton改为Prototype
- 采用ThreadLocal,为每个线程提供一个独立的副本
Spring基于XML注入Bean的方式
- set()方法注入
- 构造器注入:1、通过index设置参数的位置 2、通过type设置参数类型
- 静态工厂注入
- 实例工厂注入
循环依赖
什么是循环依赖
https://www.cnblogs.com/xujq/p/16283608.html
总的来说就是A类依赖于B类,B类依赖于A类
- 在创建Bean的过程中,先实例化A对象,然后填充属性,发现A中的b为空
- 在容器中查找B对象,如果找到了则直接赋值不存在循环依赖问题,找不到就创建对象b
- 实例化B对象,此时B对象的a为空,要填充a,然后又得创建a对象,就形成了闭环
- 也就是循环依赖问题
Spring如何解决循环依赖问题
通过三级缓存解决
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kWPLvsUo-1665923656690)(Spring%E7%9F%A5%E8%AF%86%E7%82%B9.assets/2844624-20220519082133692-3017351-16578068282362.png)]
其实此时a对象是存在的,只是完成了实例化未完成初始化。
- spring的三级缓存是三个map
- spring中一级缓存存放的是初始化完成的完整对象,二级缓存存放的是非完整对象,而三级缓存缓存的是一个单例的bean工厂
- 如果A类和B类发生了循环依赖。那么创建过程就是A实例化完放在二级缓存中,B实例化完放在二级缓存中,然后B调用二级缓存中的A完成初始化,然后将B放到一级缓存中,接着初始化A,就完成了。
- 三级缓存是给AOP代理准备的,他的作用是可以通过ObjectFactory来存储单例模式下提前暴露的bean实例。
- 因为创建的Bean实现AOP时,注入的应该是代理类而不是本类。在没有循环依赖的情况下spring会选择先创建bean,再生成相应的代理类。
- 但是Spring是无法知道是否有循环依赖的,所以spring选择了出现循环依赖对象注入时,再生成代理类。所以就设计了三级缓存,选择在对象外面套一层ObjectFactory,提前曝光ObjectFactory,等到ObjectFactory.getObject时再生成代理对象,并将对象放到二级缓存当中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gsYGKsG4-1665923656691)(Spring%E7%9F%A5%E8%AF%86%E7%82%B9.assets/2844624-20220518225418696-434678922-16617381473972.png)]
哪些情况的循环依赖Spring没法解决
- 通过构造方法进行依赖注入的循环依赖问题,因为A的构造方法有B,B的构造方法有A这种在创建对象的时候就被堵塞住了,类似于是“先有鸡还是先有蛋”
- 通过setter方法进行依赖注入且是prototype(多例)情况下的循环依赖问题。每次请求都是创建新的bean,反复下去会有无穷无尽的bean产生
哪些情况的循环依赖Spring能解决
通过setter方法依赖注入且是单例模式下的循环依赖问题
Spring的自动装配
在spring中,使用autowire来配置自动装载模式,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象。
(1)在Spring框架xml配置中共有5种自动装配:
no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="people" class="com.kuang.pojo.Peopel">
<property name="name" value="张三"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
</beans>
byName:设置autowire属性为byName,那么Spring会根据class属性找到实体类,然后查询实体类中所有setter方法的名字,根据setter方法后面的名字(例如SetDog,则setter方法后面的名字为dog)再到配置文件中寻找一个与该名字相同id的Bean,注入进来。如图:
byType:设置autowire属性为byType,那么Spring会自动寻找一个与该属性类型相同的Bean,注入进来。
注意:使用byType这种方式,必须保证配置文件中所有bean的class属性的值是唯一的,否则就会报错
例如:下边这种方式是错误的,因为两个bean中的class属性的值重复了,会报错
constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
(2)基于注解的自动装配方式:
使用@Autowired、@Resource注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
如果查询的结果不止一个,那么@Autowired会根据名称来查找;
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
@Autowired可用于:构造函数、成员变量、Setter方法
注:@Autowired和@Resource之间的区别:
(1) @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
(2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
Sring事务的原理
Spring事务的本质是数据库对事务的支持
Spring事务的种类
spring支持编程式事务和声明式事务
-
编程式事务是通过TransactionTemplate来管理事务
-
声明式事务管理建立在AOP之上,其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。可以通过注解@Transaction或者xml配置来声明某方法的事务特征
-
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。
-
唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
Spring的事务传播机制
传播机制解决的是spring事务的交叉问题,当多个事务同时存在的时候,spring如何处理这些事务的行为。比如事务A里调用了事务B,AB都用了@Transaction,那B到底用的是自己设置的还是A的
- REQUIRE:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。A调B,B支持A的事务,若A没有事务则创建新事务
- REQUIRES_NEW:无论有没有事务,都会创建新的事务
- NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。B能独立
Spring中的隔离级别
默认、读未提交、读已提交、可重复读、串行化
Spring框架中用到了哪些设计模式
- 工厂模式:Spring使用工厂模式、通过BeanFactory和ApplicationContext来创建对象
- 单例模式:bean默认为单例
- 策略模式:Resource的实现类,对于不同资源的获取,采取了不同的策略
- 代理模式:SpringAOP
注解
@Target 表示注解可以修饰在哪些地方,
- ElementType.TYPE 表示可以修饰接口、类、枚举、注解
- ElementType.FIELE 表示可以修饰字段、枚举常量
- ElementType.METHOD 表示可以修饰方法
- ElementType.PARAMENTER 方法参数
@Retention 表示什么时候用注解
- RetentionPolicy.SOURCE 编译阶段就丢弃注解
- RetentionPolicy.CLASS 默认保留策略,在类加载时就丢弃注解
- RetentionPolicy.RUNTIME 始终不丢弃注解
@Document表明该注解将包含在javadoc中
@Inherited表明子类可以继承父类中的注解
@Target(FIELD)
@Retention(RUNTIME)
@Documented
@Inherited
public @interface CarName {
String value() default "";
}