概念:所谓控制反转,就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转给容器来帮忙实现。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。
Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境。
如果是xml文件,那么需要解析xml文件;如果是注解,需要通过反射获取注解,然后根据获取到的bean信息通过反射实例化bean,实例化之后将bean放到spring容器的bean缓存池中(hashMap),当要使用bean时,可以通过applicationContext获取bean(getBean)。
@Autowired表示被修饰的类需要注入对象,spring会扫描所有被@Autowired标注的类,然后根据 类型type 在ioc容器中找到匹配的类注入
@Autowired VS @Resource
普通pojo实例化到spring容器中,相当于配置文件中的
虽然有了@Autowired,但是我们还是要写一堆bean的配置文件,相当麻烦,而@Component就是告诉spring,我是pojo类,把我注册到容器中吧,spring会自动提取相关信息。那么我们就不用写麻烦的xml配置文件了。
可以将这些方法大致划分为三类:
Bean 自身的方法:如调用 Bean 构造函数实例化 Bean,调用 Setter 设置 Bean 的属性值以及通过
Bean 级生命周期接口方法:如 BeanNameAware、 BeanFactoryAware、 InitializingBean 和 DisposableBean,这些接口方法由 Bean 类直接实现;
容器级生命周期接口方法:在上图中带“★” 的步骤是由 InstantiationAwareBean PostProcessor 和 BeanPostProcessor 这两个接口实现,一般称它们的实现类为“ 后处理器” 。 后处理器接口一般不由 Bean 本身实现,它们独立于 Bean,实现类以容器附加装置的形式注册到 Spring 容器中并通过接口反射为 Spring 容器预先识别。当Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用,所以这些后处理器的影响是全局性的。当然,用户可以通过合理地编写后处理器,让其仅对感兴趣Bean 进行加工处理。
BeanFactory:是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;
ApplicationContext:应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;
同时ApplicationContext会利用 Java 反射机制自动识别出配置文件中定义的 BeanPostProcessor、 InstantiationAwareBeanPostProcessor 和 BeanFactoryPostProcessor,并自动将它们注册到应用上下文中;
而BeanFactory 需要在代码中通过手工调用 addBeanPostProcessor()方法进行注册。
这也是为什么在应用开发时,我们普遍使用 ApplicationContext 而很少使用 BeanFactory 的原因之一。
1、读取config.xml文件的bean标签放入数组,读取内容包含的id和class。
2、循环数组并根据class路径利用反射机制实例化Bean(实例化bean的过程就是上面),并放入Map(spring容器中的Bean缓存池)。
3、根据传入的BeanId获取Map中对应的bean实例。
从上面我们知道bean是存储在hashMap中的,其中key是beanId,vlaue是bean实例;
bean获取方式其实就是从应用上下文ApplicationContext的HashMap中根据key获取value的过程。
方式一:Object getBean(String name)
方式二:
方式一和方式二的区别只是方式二自动做了类型转换。
方式一:读取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了
方式三是最推荐使用的
Spring默认会在容器初始化的过程中,解析xml,并将单例的bean创建并保存到map中,这样的机制在bean比较少时问题不大,但一旦bean非常多时,spring需要在启动的过程中花费大量的时间来创建bean 花费大量的空间存储bean,但这些bean可能很久都用不上,这种在启动时在时间和空间上的浪费显得非常的不值得。
所以Spring提供了懒加载机制。所谓的懒加载机制就是可以规定指定的bean不在启动时立即创建,而是在后续第一次用到时才创建,从而减轻在启动过程中对时间和内存的消耗。
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中对象变化如下
解决办法:如果项目中出现了循环依赖,则使用setter注入替代构造器注入。
AOP的全称是Aspect Orient Programming,即面向切面编程,扩展功能不通过修改源代码实现。
设计模式中,有一种
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;
}
}
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);
}
如果加入容器的目标对象有实现接口,就使用JDK代理
如果目标对象没有实现接口,就使用Cglib代理。
反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行的时候才动态加载类,并且对于任意一个类,都能够知道这个类的所有属性和方法,调用方法/访问属性,不需要提前在编译期知道运行的对象是谁,他允许运行中的Java程序获取类的信息,并且可以操作类或对象内部属性。
程序中对象的类型一般都是在编译期就确定下来的,而当我们的程序在运行时,可能需要动态的加载一些类,这些类因为之前用不到,所以没有加载到jvm,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载。
new:静态编译,在编译期就将模块编译进来,执行该字节码文件,所有的模块都被加载;
反射:动态编译,编译期没有加载,等到模块被调用时才加载;
要使用一个类,就要先把它加载到虚拟机中,生成一个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(实例对象,参数值);