0.SpringBoot启动流程
1.准备环境,根据不同的环境创建不同的Environment;
2.准备、加载上下文,为不同的环境选择不同的Spring Context,然后加载资源、配置Bean;
3.初始化,这个阶段刷新SpringContext,启动应用;
4.结束。
1.使用Spring框架的好处
1.控制反转
Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或者查找依赖的对象们。
2.面向切面编程
把应用业务逻辑和系统服务分开。
3.容器
Spring包含并管理应用中对象的生命周期和配置。
4.MVC框架
Spring的Web框架是精心设计的框架,是Web框架的很好替代。
5.事务管理
Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务。
2.什么是IoC?
- 1.控制反转,就是通过Spring来管理对象的创建、配置和生命周期,这就相当于把控制权交给了Spring,不需要人工来管理对象间复杂的依赖关系,这样做的好处是解耦。
- 2.Spring里,主要提供了BeanFactory、ApplicationContext两种IoC容器,通过他们来实现对Bean的管理。
IOC的应用:什么是Bean?怎么注册、使用?生命周期?作用域?
3.面向切面编程AoP
0.什么是面向对象编程OOP?
万物皆对象,所有事物都可以抽象成一个类,这个类包含了属性、行为;每个对象都是某个类的一个实例。
面向对象有3大特征:
- 1.封装
封装就是把数据和操作数据的方法封装起来,对外只公开对数据操作的接口。 - 2.继承
从已有的类得到继承信息,创建新类的过程。提供继承信息的是父类,得到继承信息的是子类。 - 3.多态
编译时静态绑定父类的方法,运行时动态绑定子类的方法。
实现多态的两个条件:- 1.子类继承父类,并重写父类中的方法;
- 2.父类型引用指向子类型对象。
1.什么是AoP?
- 1.面向切面编程,可以说是OOP面向对象编程的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次的结构,用来模拟公共行为的一个集合。
- 2.AOP技术,是一种横切的技术,把封装的对象剖开,把一些影响了多个类的公共行为封装到1个可重用的模块,并将其命名为"Aspect",既:切面。横切关注点是那些与业务无关,却被业务模块所共同调用的部分,把他们封装起来,可以减少重复代码,降低模块间的耦合度。
- 3.AOP把软件系统分为2种关注点:
- 核心关注点:它是业务处理的主要流程;
- 横切关注点:他们经常发生在核心关注点的多处,且多处类似,如:权限认证、日志、事务。
2.AOP的基本概念
- 1.切面(Aspect)——@Aspect注解
- 一个**关注点(切入点)**的模块化,这个关注点可能会横切多个对象;
- 如下程序的关注点(切入点):名字为:say 的方法。
@Component("annotationTest")
@Aspect
public class AnnotationTest {
1. * 定义切点
* sayings() 方法名,那只是个代理对象,不需要写具体方法
@Pointcut("execution(* *.say(..))")
public void sayings(){}
2.前置通知
@Before("sayings()")
public void sayHello(){
System.out.println("注解类型前置通知");
}
3.后置通知
@After("sayings()")
public void sayGoodbey(){
System.out.println("注解类型后置通知");
}
4.环绕通知。注意要有ProceedingJoinPoint参数传入。
@Around("sayings()")
public void sayAround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("注解类型环绕通知..环绕前");
pjp.proceed(); 执行方法
System.out.println("注解类型环绕通知..环绕后");
}
}
- 2.连接点(Joinpoint)
- 程序执行过程中的某一行为;
- 它包括切入点,只有符合@Pointcut切入条件的连接点,才是切入点。
- 3.通知(Advice)
- “切面”对于某个“连接点”所产生的动作;
- 如下程序:由@Pointcut(“execution(* *.say(…))”)所修饰的方法名sayings(),那只是个代理对象,不需要写具体方法;sayings() 只是为了让后续的各种通知 方便使用这个切入点。
- @Before(“sayings()”)、@After(“sayings()”)、@Around(“sayings()”) 这3个注解只根据代理对象sayings()进行拦截。
*****
*****
@Pointcut("execution(* *.say(..))")
public void sayings(){}
2.前置通知
@Before("sayings()")
3.后置通知
@After("sayings()")
4.环绕通知。注意要有ProceedingJoinPoint参数传入。
@Around("sayings()")
- 4.切入点(Pointcut)
- 匹配连接点的断言,在AOP中通知和一个切入点表达式关联;
- @Pointcut(“execution(* *.say(…))”),表示所有的say()方法,就是切入点。
- 5.目标对象(Target Object)
- 被一个或多个切面所通知的对象;
- 在这里就是BraveKnight类的对象,他的say()方法就是切入点。
- 6.AOP代理(AOP Proxy)
- 两种代理方式:JDK动态代理、CGLIB代理。
- Spring 5.x 中 AOP 默认使用 JDK 动态代理。
- SpringBoot 2.x 开始默认使用 CGLIB,为了解决使用 JDK 动态代理可能导致的类型转化异常。
3.通知(Advice)的类型有哪些?
- 1.前置通知(Before advice):
- 2.后置通知(After advice):
- 3.返回后通知 (After return advice):
- 4.环绕通知(Around advice):
- 可以决定是否调用目标方法;
- 可以返回一个与目标对象完全不同的返回值,虽然这很危险。
- 5.抛出异常后通知(After throwing advice):
4.怎么实现JDK动态代理?
1.定义接口
RealSubject类实现了Subject接口;
生成的代理类Proxy也实现了Subject接口,代理类和被代理类是兄弟关系。
public interface Subject {
public String sayHello(String name);
public String sayGoodBye(String name);
}
2.实现接口
public class RealSubject implements Subject{
@Override
public String sayHello(String name) {
return "hello:" + name;
}
@Override
public String sayGoodBye(String name) {
return "goodBye:" + name;
}
}
3.实现InvocationHandler接口——最核心的1个接口
java.lang.reflect 包中的 InvocationHandler 接⼝
对于被代理类的操作,都会由该接口中的invoke方法实现,其中参数的含义是:
- 1.Object proxy :被代理类的实例;
- 2.Method method:调用被代理类的方法;
- 3.Object[] args:该方法需要的参数。
使用invoke方法,首先就是要实现该接口。
- 1.并且我们可以在invoke方法中调用被代理类的方法,并获得返回值;
- 2.自然也可以在调用被代理类方法的前后去做一些其他的事,从而实现动态代理。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class InvocationHandlerImpl implements InvocationHandler {
private Subject subject;
public InvocationHandlerImpl(Subject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
1.代理真实对象前可以添加一些操作
System.out.println("在调用之前,我要干点什么呢?");
System.out.println("Method:"+method);
2.当代理对象调用真实对象的方法时,
其会自动跳转到代理对象关联的handler对象的invoke方法来进行调用
Object returnVal = method.invoke(subject,args);
3.在代理真实对象之后,自定义一些操作
System.out.println("调用之后,自定义操作");
return returnVal;
}
}
4.测试——最核心的一个方法
java.lang.reflect 包中的 Proxy 类中的 newProxyInstance ⽅法:
- 1.参数ClassLoader loader:被代理类的类加载器;
- 2.参数Class<?>[] interfaces:被代理类实现的所有接口;
- 3.参数InvocationHandler h:调用处理器类的对象实例。
该方法会返回一个被修改过的类的实例,从而可以自由的调用实例的方法。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
- InvocationHandlerImpl 实现了 InvocationHandler 接口,
- 并能实现方法调用 从代理类—到—被代理类的分派转发;
- 其内部通常包含指向被代理类实例的引用,用于真正执行分派转发过来的方法调用.
- 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class DynamicDemoDisplay {
public static void main(String[] args) {
Subject realSubject = new RealSubject();
1.传入要代理的对象
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
2.获取类加载器
ClassLoader loader = realSubject.getClass().getClassLoader();
3.获取被代理类实现的所有接口
Class[] interfaces = realSubject.getClass().getInterfaces();
4.获取反射机制生成的,动态代理对象
Subject subject = (Subject) Proxy.newProxyInstance(loader,interfaces,handler);
System.out.println("动态代理对象的类型:"+subject.getClass().getName());
String resVal = subject.sayGoodBye("Xiao Ming");
System.out.println(resVal);
}
}
5.怎么实现静态代理
- 1.被代理类RealMovie实现了Movie接口;
- 2.代理类Cinema也实现了Movie接口;
- 3.RealMovie与Cinema是兄弟关系,Cinema调用play方法,实际上调用的是RealMovie的play方法。而且在play方法执行前后,自定义加入了打广告的逻辑
1.定义接口
public interface Movie {
public void play();
}
2.实现接口
public class RealMovie implements Movie{
@Override
public void play() {
System.out.println("在看电影---《肖申克的救赎》");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("电影结束....");
}
}
3.定义代理对象——Cinema
public class Cinema implements Movie{
RealMovie realMovie;
public Cinema(RealMovie realMovie) {
this.realMovie = realMovie;
}
@Override
public void play() {
advertisement(true);
realMovie.play();
advertisement(false);
}
public void advertisement(boolean isStart){
if (isStart){
System.out.println("电影开始,爆米花、可乐 88折");
}
else {
System.out.println("电影结束:爆米花、可乐 66折带回家");
}
}
}
4.测试
public class ProxyTest {
public static void main(String[] args) {
RealMovie realMovie = new RealMovie();
Movie movie = new Cinema(realMovie);
movie.play();
}
}
6.动态代理、静态代理的区别
1.代理类是否自动生成?
- 动态代理的代理类Proxy由Java反射机制,自动生成;
- 静态代理的代理类 Cinema 由程序员 手工编写。
7.Spring中的设计模式
1.单例模式:
Spring的Bean默认就是单例的;
2.工厂模式:
工厂模式主要是通过BeanFactory、ApplicationContext来生产Bean对象;
3.代理模式:
最常见的AoP的实现方式就是通过代理来实现的,Spring主要是JDK动态代理,CGLIB代理。
8.动态代理JDK、CGLIB有什么区别?
jdk动态代理
主要是针对类实现了接口,AOP则会使用JDK动态代理。基于反射机制实现,生成一个同样实现该接口的代理类,然后通过重写方法,实现代码增强。
CGLIB代理
如果被代理类没有实现接口,则用CGLIB,他的底层原理是基于 asm 第三方框架,通过修改字节码(.class文件)生成一个子类,再重写父类方法,实现代码增强。
9.Spring怎么解决循环依赖的?
0.什么是Bean的实例化、初始化?
-
实例化:是对象创建的过程。比如使用构造方法new对象,为对象在内存中分配空间。
-
初始化:是为对象中的属性赋值的过程。
1.解决循环依赖的前提
- 1.不全是构造器方式的循环依赖
- 2.必须是单例
2.本质上解决循环依赖的问题,是三级缓存
一级缓存:保存实例化、初始化都完成的对象;
二级缓存:保存实例化完成、还没初始化完成的对象;
三级缓存:保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象。
3.举例分析
假设A、B互相依赖。
A对象的创建过程
-
1.创建对象A,实例化的时候把A对象工厂放入三级缓存;
-
2.A在属性注入时,发现依赖B,转头去实例化B
-
3.同样创建B对象,注入属性时发现依赖A,依次从一级~到三级缓存查询A;
- 在三级缓存通过对象工厂拿到A,把A放入二级缓存,同时删除三级缓存中的A;
- 此时,B对象实例化、初始化完成,放入一级缓存;
-
4.接着继续创建A,从一级缓存中拿到B,此时A也创建完成,删除二级缓存中的A,把A放入一级缓存;
-
5.最后,一级缓存保存着完全创建的A、B对象。
因为,把实例化、初始化的流程分开了;如果都是构造器方式,没法分开,就无法解决循环依赖了。
10.为什么要三级缓存?二级缓存不行吗?
* 1级缓存 Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
* 2级缓存 Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
* 3级缓存 Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
-
1处,在最上层的缓存singletonObjects中,获取单例bean,这里面拿到的bean,直接可以使用;如果没取到,则进入2处
-
2处,在2级缓存earlySingletonObjects中,查找bean;
-
3处,如果在2级缓存中,还是没找到,则在3级缓存中查找对应的工厂对象,利用拿到的工厂对象(工厂对象中,有3个field,一个是beanName,一个是RootBeanDefinition,一个是已经创建好的,但还没有注入属性的bean),去获取包装后的bean,或者说,代理后的bean。
什么是已经创建好的,但没有注入属性的bean?
- 比如一个bean,有10个字段,你new了之后,对象已经有了,内存空间已经开辟了,堆里已经分配了该对象的空间了,只是此时的10个field还是null。