Spring IOC和AOP

1、IOC

概念:所谓控制反转,就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转给容器来帮忙实现。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。

Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。

image.png

1.1 底层实现:

image.png

如果是xml文件,那么需要解析xml文件;如果是注解,需要通过反射获取注解,然后根据获取到的bean信息通过反射实例化bean,实例化之后将bean放到spring容器的bean缓存池中(hashMap),当要使用bean时,可以通过applicationContext获取bean(getBean)。

1.2 spring ioc autowired如何实现

@Autowired表示被修饰的类需要注入对象,spring会扫描所有被@Autowired标注的类,然后根据 类型type 在ioc容器中找到匹配的类注入

@Autowired VS @Resource
  • 提供方:@Autowired是由Spring提供;
    @Resource是由javax.annotation.Resource提供,即J2EE提供,需要JDK1.6及以上;
  • 注入方式:@Autowired只按照byType 注入;
    @Resource默认按byName自动注入,也提供按照byType 注入;

1.3 @component

普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>
虽然有了@Autowired,但是我们还是要写一堆bean的配置文件,相当麻烦,而@Component就是告诉spring,我是pojo类,把我注册到容器中吧,spring会自动提取相关信息。那么我们就不用写麻烦的xml配置文件了。

https://zhuanlan.zhihu.com/p/29344811

1.4 bean生命周期

image.png
  • 1.4.1.当调用者通过 getBean(beanName)向容器请求某一个 Bean 时,如果容器注册了org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor 接口,在实例化 Bean 之前,将调用接口的 postProcessBeforeInstantiation()方法;
  • 1.4.2.根据配置情况调用 Bean 构造函数或工厂方法实例化 Bean;
  • 1.4.3.如果容器注册了 InstantiationAwareBeanPostProcessor 接口,在实例化 Bean 之后,调用该接口的 postProcessAfterInstantiation()方法,可在这里对已经实例化的对象进行一些“梳妆打扮”;
  • 1.4.4.如果 Bean 配置了属性信息,容器在这一步着手将配置值设置到 Bean 对应的属性中,不过在设置每个属性之前将先调用InstantiationAwareBeanPostProcessor 接口的postProcessPropertyValues()方法;
  • 1.4.5.调用 Bean 的属性设置方法设置属性值;
  • 1.4.6.如果 Bean 实现了 org.springframework.beans.factory.BeanNameAware 接口,将调用setBeanName()接口方法,将配置文件中该 Bean 对应的名称设置到 Bean 中;
  • 1.4.7.如果 Bean 实现了 org.springframework.beans.factory.BeanFactoryAware 接口,将调用 setBeanFactory()接口方法,将 BeanFactory 容器实例设置到 Bean 中;
  • 1.4.8.如果 BeanFactory 装配了 org.springframework.beans.factory.config.BeanPostProcessor后处理器,将调用 BeanPostProcessor 的 Object postProcessBeforeInitialization(Object bean, String beanName)接口方法对 Bean 进行加工操作。其中入参 bean 是当前正在处理的 Bean,而 beanName 是当前 Bean 的配置名,返回的对象为加工处理后的 Bean。用户可以使用该方法对某些 Bean 进行特殊的处理,甚至改变 Bean 的行为, BeanPostProcessor 在 Spring 框架中占有重要的地位,为容器提供对 Bean 进行后续加工处理的切入点, Spring 容器所提供的各种“神奇功能”(如 AOP,动态代理等)都通过 BeanPostProcessor 实施;
  • 1.4.9.如果 Bean 实现了 InitializingBean 的接口,将调用接口的 afterPropertiesSet()方法;
  • 1.4.10.如果在<bean>通过 init-method 属性定义了初始化方法,将执行这个方法;
  • 1.4.11.BeanPostProcessor 后处理器定义了两个方法:其一是 postProcessBeforeInitialization() 在第 8 步调用;其二是 Object postProcessAfterInitialization(Object bean, String beanName)方法,这个方法在此时调用,容器再次获得对 Bean 进行加工处理的机会;
  • 1.4.12.如果在<bean>中指定 Bean 的作用范围为 scope=“prototype”,将 Bean 返回给调用者,调用者负责 Bean 后续生命的管理, Spring 不再管理这个 Bean 的生命周期。如果作用范围设置为 scope=“singleton”,则将 Bean 放入到 Spring IoC 容器的缓存池中,并将 Bean引用返回给调用者, Spring 继续对这些 Bean 进行后续的生命管理;
  • 1.4.13.对于 scope=“singleton”的 Bean,当容器关闭时,将触发 Spring 对 Bean 的后续生命周期的管理工作,首先如果 Bean 实现了 DisposableBean 接口,则将调用接口的afterPropertiesSet()方法,可以在此编写释放资源、记录日志等操作;
  • 1.4.14.对于 scope=“singleton”的 Bean,如果通过<bean>的 destroy-method 属性指定了 Bean 的销毁方法, Spring 将执行 Bean 的这个方法,完成 Bean 资源的释放等操作。

可以将这些方法大致划分为三类:

  • Bean 自身的方法:如调用 Bean 构造函数实例化 Bean,调用 Setter 设置 Bean 的属性值以及通过<bean>的 init-method 和 destroy-method 所指定的方法;

  • Bean 级生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,这些接口方法由 Bean 类直接实现;

  • 容器级生命周期接口方法:在上图中带“★” 的步骤是由 InstantiationAwareBean PostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“ 后处理器” 。 后处理器接口一般不由 Bean 本身实现,它们独立于 Bean,实现类以容器附加装置的形式注册到 Spring 容器中并通过接口反射为 Spring 容器预先识别。当Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用,所以这些后处理器的影响是全局性的。当然,用户可以通过合理地编写后处理器,让其仅对感兴趣Bean 进行加工处理。

1.5 ApplicationContext 与 BeanFactory 区别

  • BeanFactory:是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;

  • ApplicationContext:应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;

    1. 国际化(MessageSource)
    2. 访问资源,如URL和文件(ResourceLoader)
    3. 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
    4. 消息发送、响应机制(ApplicationEventPublisher)
    5. AOP(拦截器)

同时ApplicationContext会利用 Java 反射机制自动识别出配置文件中定义的 BeanPostProcessor、 InstantiationAwareBeanPostProcessor 和 BeanFactoryPostProcessor,并自动将它们注册到应用上下文中;
而BeanFactory 需要在代码中通过手工调用 addBeanPostProcessor()方法进行注册。
这也是为什么在应用开发时,我们普遍使用 ApplicationContext 而很少使用 BeanFactory 的原因之一。

1.6 Spring的bean的存储和管理机制

1、读取config.xml文件的bean标签放入数组,读取内容包含的id和class。
2、循环数组并根据class路径利用反射机制实例化Bean(实例化bean的过程就是上面),并放入Map(spring容器中的Bean缓存池)。
3、根据传入的BeanId获取Map中对应的bean实例。

1.7 Spring bean获取方式

从上面我们知道bean是存储在hashMap中的,其中key是beanId,vlaue是bean实例;

bean获取方式其实就是从应用上下文ApplicationContext的HashMap中根据key获取value的过程。
方式一:Object getBean(String name)
方式二:<T> T getBean(String name, Class<T> requiredType)
方式一和方式二的区别只是方式二自动做了类型转换。

1.8 ApplicationContext 获取方式

方式一:读取xml文件获取ApplicationContext对象
ApplicationContext ac = new FileSystemXmlApplicationContext("applicationContext.xml");

方式二:通过Spring提供的工具类获取ApplicationContext对象
ApplicationContext ac2 = WebApplicationContextUtils.getWebApplicationContext(ServletContext sc);

方式三:新建一个springUtils工具类,且实现ApplicationContextAware接口
上面bean实例化时的第五步,如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;
通过这种方式可以把ApplicationContext传入到springUtils中,然后再springUtils中就可以使用applicationContext.getBean()来获取bean了

方式三是最推荐使用的

1.9 spring懒加载:

Spring默认会在容器初始化的过程中,解析xml,并将单例的bean创建并保存到map中,这样的机制在bean比较少时问题不大,但一旦bean非常多时,spring需要在启动的过程中花费大量的时间来创建bean 花费大量的空间存储bean,但这些bean可能很久都用不上,这种在启动时在时间和空间上的浪费显得非常的不值得。
所以Spring提供了懒加载机制。所谓的懒加载机制就是可以规定指定的bean不在启动时立即创建,而是在后续第一次用到时才创建,从而减轻在启动过程中对时间和内存的消耗。

1.10 spring bean 注册到 IOC容器的过程

  • 1、读取bean配置信息(通过xml中<bean> ,或者通过注解@Autowrite @Configuration),根据配置信息,在容器内部建立Bean定义注册表;
  • 2、根据bean注册表实例化bean;
  • 3、将bean实例化并将其放入hashMap;
  • 4、获取并使用bean

1.11 spring 循环依赖

spring 常用的注入方式有三种:构造方法注入,setter注入,基于注解的注入。

所谓循环依赖,当我们注入一个对象A时,需要注入对象A中标记了某些注解的属性,这些属性也就是对象A的依赖,把对象A中的依赖都初始化完成,对象A才算是创建成功。那么,如果对象A中有个属性是对象B,而且对象B中有个属性是对象A,那么对象A和对象B就算是循环依赖,如果不加处理,就会出现:创建对象A-->处理A的依赖B-->创建对象B-->处理B的对象A-->创建对象A,这样无限的循环下去,我们开发过程中有时也会遇到这种问题,那么spring框架本身是怎么解决这个问题的呢?

从上面bean的生命周期中,我们发现bean实例化(构造器)和setter设置属性并不是同时发生的,这个其实就是解决循环依赖的依据。

当我们初始化一个Bean时,先调用Bean的构造方法,这个对象就在内存中存在了(对象里面的依赖还没有被注入),然后把这个对象保存下来,当循环依赖产生时,直接拿到之前保存的对象,于是循环依赖就被终止了,依赖注入也就顺利完成了。

image.png
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先存singletonObjects中获取bean,
        Object singletonObject = this.singletonObjects.get(beanName);
//如果bean不存在,并且bean正在创建中
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
//从earlySingletonObjects中获取
                singletonObject = this.earlySingletonObjects.get(beanName);
//如果earlySingletonObjects不存在(allowEarlyReference默认为true)
                if (singletonObject == null && allowEarlyReference) {
//获取singletonFactories
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
//从singletonFactories中获取bean
                        singletonObject = singletonFactory.getObject();
//添加到earlySingletonObjects
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
public boolean isSingletonCurrentlyInCreation(String beanName) {
        return this.singletonsCurrentlyInCreation.contains(beanName);
    }
  • singletonObjects:缓存key = beanName, value = bean;这里的bean是已经创建完成的,该bean经历过实例化->属性填充->初始化以及各类的后置处理。因此,一旦需要获取bean时,我们第一时间就会寻找一级缓存

  • earlySingletonObjects:缓存key = beanName, value = bean;这里跟一级缓存的区别在于,该缓存所获取到的bean是提前曝光出来的,是还没创建完成的。也就是说获取到的bean只能确保已经进行了实例化,但是属性填充跟初始化还没有做完(AOP情况后续分析),因此该bean还没创建完成,仅仅能作为指针提前曝光,被其他bean所引用

  • singletonFactories:该缓存key = beanName, value = beanFactory;在bean实例化完之后,属性填充以及初始化之前,如果允许提前曝光,spring会将实例化后的bean提前曝光,也就是把该bean转换成beanFactory并加入到三级缓存。在需要引用提前曝光对象时再通过singletonFactory.getObject()获取。

在整个getbean过程中,singletonObjects、earlySingletonObjects、singletonFactories中对象变化如下

    1. 开始初始化对象A
      singletonFactories:
      earlySingletonObjects:
      singletonObjects:
    1. 调用A的构造器实例化A,并把A放入singletonFactories
      singletonFactories:A
      earlySingletonObjects:
      singletonObjects:
    1. 开始注入A的依赖,发现A依赖对象B之后,初始化对象B(同样是调用B的构造器实例化B,并把B放入singletonFactories)
      singletonFactories:A,B
      earlySingletonObjects:
      singletonObjects:
    1. 开始注入B的依赖,发现B依赖对象A,开始初始化对象A,发现A在singletonFactories里有,则直接获取A,把A放入earlySingletonObjects(提前曝光),把A从singletonFactories删除
      singletonFactories:B
      earlySingletonObjects:A
      singletonObjects:
    1. 对象B的依赖注入完成之后,对象B创建完成,把B放入singletonObjects,并且把B从earlySingletonObjects和singletonFactories中删除
      singletonFactories:
      earlySingletonObjects:A
      singletonObjects:B
    1. 继续A的初始化,把对象B注入给A,继续注入A的其他依赖,直到A注入完成
      singletonFactories:
      earlySingletonObjects:A
      singletonObjects:B
    1. 对象A创建完成,把A放入singletonObjects,并把A从earlySingletonObjects和singletonFactories中删除
      singletonFactories:
      earlySingletonObjects:
      singletonObjects:A,B
    1. 循环依赖处理结束,A和B都初始化和注入完成
      singletonFactories:
      earlySingletonObjects:
      singletonObjects:A,B

解决办法:如果项目中出现了循环依赖,则使用setter注入替代构造器注入。

2、AOP

2.1 概念

AOP的全称是Aspect Orient Programming,即面向切面编程,扩展功能不通过修改源代码实现。

2.2实现方式

  • 2.2.1 JDK 动态代理(必须有接口)
    通过java.lang.reflect.Proxy类实现。
    动态代理就是为了解决静态代理不灵活的缺陷而产生的。静态代理是固定的,一旦确定了代码,如果委托类新增一个方法,而这个方法又需要增强,那么就必须在代理类里重写一个带增强的方法。而动态代理可以灵活替换代理方法,动态就是体现在这里。同时,静态代理每个方法都需要单独写一个代理类,而动态代理一个接口只实现一个动态代理类即可。

设计模式中,有一种

  • 2.2.2 实现
Moveable move = (Moveable) Proxy.newProxyInstance(Car.class.getClassLoader(), Car.class.getInterfaces(), new LogHandler(new Car()));

使用JDK的动态代理去生成代理只需要一行代码,传入的参数中其实就俩,一是被代理类的类对象,二是自定义的增强处理代码。
从上面的例子中可以看出,动态代理除了接受Car类型的目标对象,还可以接受任何其他类型的对象;也不管目标对象实现的接口有多少方法,都可以被代理。


public class LogHandler implements InvocationHandler{ 
 
 private Object target; 
 
 public LogHandler(Object object){
   super();
   this.target = object;
 }
 
 //增强处理
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
   Object o = method.invoke(target,args);
   return o;
 }
}

image.png
  • 2.2.2 cglib 动态代理(不需要类继承任何接口,字节码技术)
public class Plane {

    public void fly(long ms) {
        System.out.println("plane is flying!");
        try {
            Thread.sleep(ms);
        } catch (Exception e) {

        }
    }
}

public class CglibProxy implements MethodInterceptor {
    private Object target;

    public CglibProxy(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        //1. 实例化工具类
        Enhancer en = new Enhancer();
        //2. 设置父类对象
        en.setSuperclass(this.target.getClass());
        //3. 设置回调函数
        en.setCallback(this);
        //4. 创建子类,也就是代理对象
        return en.create();
    }

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before invoke ");
        long begin = System.currentTimeMillis();

        //执行目标对象的方法
        Object returnValue = method.invoke(target, objects);

        long end = System.currentTimeMillis();
        System.out.println("after invoke elpased " + (end - begin));

        return returnValue;
    }

}

public static void main() {
    CglibProxy cglibProxy = new CglibProxy(new Plane()); 
    Plane plane = (Plane) cglibProxy.getProxyInstance();             
    plane.fly(150);
}

2.4使用场景:

    1. Logging 日志
    1. Authentication 权限
    1. Transactions 事务
    1. Context passing 内容传递
    1. Error handling 错误处理
    1. Lazy loading 懒加载
    1. Debugging 调试
    1. logging,tracing,profiling and monitoring 记录跟踪 优化 校准
    1. Performance optimization 性能优化
    1. Persistence 持久化
    1. Resource pooling 资源池
    1. Synchronization 同步
    1. Caching 缓存

2.5 在Spring的AOP编程中

如果加入容器的目标对象有实现接口,就使用JDK代理
如果目标对象没有实现接口,就使用Cglib代理。

3、反射:

反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行的时候才动态加载类,并且对于任意一个类,都能够知道这个类的所有属性和方法,调用方法/访问属性,不需要提前在编译期知道运行的对象是谁,他允许运行中的Java程序获取类的信息,并且可以操作类或对象内部属性

程序中对象的类型一般都是在编译期就确定下来的,而当我们的程序在运行时,可能需要动态的加载一些类,这些类因为之前用不到,所以没有加载到jvm,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载。

3.1 new和反射创建有什么区别

new:静态编译,在编译期就将模块编译进来,执行该字节码文件,所有的模块都被加载;
反射:动态编译,编译期没有加载,等到模块被调用时才加载;

3.2 反射的作用

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;

3.3 反射的实现

要使用一个类,就要先把它加载到虚拟机中,生成一个Class对象。这个class对象就保存了这个类的一切信息。

反射机制的实现,就是获取这个Class对象,通过Class对象去访问类、对象的元数据以及运行时的数据。

//通过反射机制创建class对象
class1 = Class.forName(className);
//在运行时,通过创建的class对象,获取自己的父类信息
Class<?> parentClass = class1.getSuperclass();
//通过反射机制创建一个类的对象
Classname 对象=class1.newInstance(参数);
//取得本类已声明的所有字段,包括私有的、保护的
Field[] field = class1.getDeclaredFields();
//返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
Method method = clazz.getMethod(方法名,参数类型);
//调用具体某个实例对象的这个公有方法
method.invoke(实例对象,参数值);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容