1、IOC
概念:所谓控制反转,就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转给容器来帮忙实现。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。
1.1 底层实现:
如果是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生命周期
- 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的一各更高级的容器,提供了更多的有用的功能;
- 国际化(MessageSource)
- 访问资源,如URL和文件(ResourceLoader)
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
- 消息发送、响应机制(ApplicationEventPublisher)
- 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的构造方法,这个对象就在内存中存在了(对象里面的依赖还没有被注入),然后把这个对象保存下来,当循环依赖产生时,直接拿到之前保存的对象,于是循环依赖就被终止了,依赖注入也就顺利完成了。
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中对象变化如下
- 开始初始化对象A
singletonFactories:
earlySingletonObjects:
singletonObjects:
- 开始初始化对象A
- 调用A的构造器实例化A,并把A放入singletonFactories
singletonFactories:A
earlySingletonObjects:
singletonObjects:
- 调用A的构造器实例化A,并把A放入singletonFactories
- 开始注入A的依赖,发现A依赖对象B之后,初始化对象B(同样是调用B的构造器实例化B,并把B放入singletonFactories)
singletonFactories:A,B
earlySingletonObjects:
singletonObjects:
- 开始注入A的依赖,发现A依赖对象B之后,初始化对象B(同样是调用B的构造器实例化B,并把B放入singletonFactories)
- 开始注入B的依赖,发现B依赖对象A,开始初始化对象A,发现A在singletonFactories里有,则直接获取A,把A放入earlySingletonObjects(提前曝光),把A从singletonFactories删除
singletonFactories:B
earlySingletonObjects:A
singletonObjects:
- 开始注入B的依赖,发现B依赖对象A,开始初始化对象A,发现A在singletonFactories里有,则直接获取A,把A放入earlySingletonObjects(提前曝光),把A从singletonFactories删除
- 对象B的依赖注入完成之后,对象B创建完成,把B放入singletonObjects,并且把B从earlySingletonObjects和singletonFactories中删除
singletonFactories:
earlySingletonObjects:A
singletonObjects:B
- 对象B的依赖注入完成之后,对象B创建完成,把B放入singletonObjects,并且把B从earlySingletonObjects和singletonFactories中删除
- 继续A的初始化,把对象B注入给A,继续注入A的其他依赖,直到A注入完成
singletonFactories:
earlySingletonObjects:A
singletonObjects:B
- 继续A的初始化,把对象B注入给A,继续注入A的其他依赖,直到A注入完成
- 对象A创建完成,把A放入singletonObjects,并把A从earlySingletonObjects和singletonFactories中删除
singletonFactories:
earlySingletonObjects:
singletonObjects:A,B
- 对象A创建完成,把A放入singletonObjects,并把A从earlySingletonObjects和singletonFactories中删除
- 循环依赖处理结束,A和B都初始化和注入完成
singletonFactories:
earlySingletonObjects:
singletonObjects:A,B
- 循环依赖处理结束,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;
}
}
- 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使用场景:
- Logging 日志
- Authentication 权限
- Transactions 事务
- Context passing 内容传递
- Error handling 错误处理
- Lazy loading 懒加载
- Debugging 调试
- logging,tracing,profiling and monitoring 记录跟踪 优化 校准
- Performance optimization 性能优化
- Persistence 持久化
- Resource pooling 资源池
- Synchronization 同步
- 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(实例对象,参数值);