Spring—IOC、AOP、JDK\CGLIB、设计模式、循环依赖

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。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值