基于Spring官方文档
采用Spring版本:5.x.x
Bean在计算机英语中有可重用组件的含义。
先实现一个简单的IOC容器(利用反射和单例模式思想):
通过一个配置文件配置所需bean,内容:唯一标识(全限定类名)创建BeanFactory工厂类并定义一个Map用于存放创建的对象,称之为容器BeanFactory工厂类通过读取配置文件中所有key存放至String数组或Enum中遍历String数组或Enum,通过key获取全限定类名,然后反射创建对象存放至容器中(完成容器初始化)通过BeanFactory工厂getBean静态方法获取对象 //1.创建一个UserDao类,里面不写东西 //2.创建beans-config.properties文件,内容如下 userDao=org.lhy.dao.UserDao //3.创建BeanFatory类 public class BeanFatory { private static Properties props; private static Map<String,Object> beans; static { props = new Properties(); try { props.load(BeanFatory.class.getClassLoader() .getResourceAsStream("beans-config.properties")); beans = new HashMap<String, Object>(); Enumeration keys = props.keys(); while (keys.hasMoreElements()){ String key = keys.nextElement().toString(); String beanPath = props.getProperty(key); Object value = Class.forName(beanPath).newInstance(); beans.put(key,value); } } catch (Exception e) { System.out.println("容器初始化失败");; } } public static Object getBean(String beanName){ Object bean = beans.get(beanName); return bean; } //main方法执行 public static void main(String[] args) { int i = 1; while (i++ <= 3){ UserDao userDao = (UserDao) BeanFatory.getBean("userDao"); System.out.println(userDao.toString()); /** 打印: org.lhy.dao.UserDao@4554617c org.lhy.dao.UserDao@4554617c org.lhy.dao.UserDao@4554617c */ } } }含义:IOC(控制反转),通过把创建对象的权力交由框架,是Spring的重要特征。其中还包含DI(依赖注入)和DL(依赖查找)。
作用:降低代码的耦合度(解除依赖关系)。
导入Spring的Maven依赖 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> 创建UserDao类和Spring配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userDao" class="org.lhy.dao.UserDao"/> </beans> 实例化容器对象,获取UserDao对象 public static void main(String[] args) { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = (UserDao) ac.getBean("userDao"); System.out.println(userDao.toString()); }结合之前IOC容器的模拟示例,简单的了解到IOC容器底层的相关原理,也方便学习的深入,但这只是冰山半个角。如果你是在IDEA上,你可以通过Ctrl+H快捷键直观的看到ApplicationContext接口的子类和继承。
可以看到BeanFactory接口是SpringIOC顶层接口,它为Spring IOC提供了底层基础,而ApplicationContext有三个常用的实现类:ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext。
ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径下。(常用)
FileSystemXmlApplicationContext:可以加载本地磁盘下的配置文件(需有访问权限)。
AnnotationConfigApplicationContext:用于读取注解创建容器。
可以在对象的类构造方法打印字符串进行测试。
两种容器创建方式因为对象创建时间点不同而应用在不同的场景下,我们在实际开发中要根据不同情况使用不同的创建容器方式。**在使用立即加载方式创建对象时,单例模式最为适用的。而延迟加载方式,则是多例模式适用的。**但是,这并不是固定的形式,还是要根据对象实例使用情况和加载情况而定的,在后续还会有通过配置来修改创建对象策略和模式。
实际上,除非特殊原因(如需对Bean要完全控制的场景),我们都应该使用ApplicationContext,它包含BeanFactory的所有功能,通过表格一看便知。
功能BeanFactoryApplicationContextBean的实例化部署YesYes集成的生命周期管理NoYesBeanPostProcessor 自动注册NoYesBeanFactoryPostProcessor自动注册NoYes方便的MessageSource 国际化访问NoYes内置 ApplicationEvent 事件发布机制NoYes在扩展的容器特性中都需要注解处理和AOP代理,BeanPostProcessor 是必不可少的,而使用BeanFactory的实现类DefaultListableBeanFactory,默认情况下是不会自动注册BeanPostProcessor的,所以请谨慎使用。
在实际开发中,我们经常要导入一些jar包使用,但jar包中有些类并不提供默认构造方法(无法修改源码),从而导致无法配置Bean对象,所以第二种和第三种方法就是解决jar包中的这些问题的。
在Spring中,默认采用单例模式创建对象,既然是默认,那么显然是可以修改。配置文件中,bean属性scope就为我们提供了修改创建对象模式的权力。
通过其scope属性设置Bean作用域:
作用域描述singleton单例,整个容器共享唯一实例(默认,常用)prototype多例,整个容器可有多个实例(常用)request作用于web应用的请求范围session作用于web应用的会话范围application作用于web应用的全局范围websocket作用于web应用的WebSocket范围global-session作用于集群环境下会话范围(全局),若不是集群环境,则就指session <bean id="userDao" class="org.lhy.dao.UserDao" scope="prototype"/>按住Ctrl点击scope进去查看:
可以看到,默认情况下,bean是一个单例,除非这个bean有一个父bean,这种情况下,它将继承父bean的作用域(scope)。其中还提出,单例是最常用的,是多线程服务对象的理想选择。最后一段说,内部定义的bean是跟随外部bean定义走的,除非它显式指定。
在实际开发中,有时需要你在一个较短作用域的Bean中注入一个较长作用域的Bean。如:在web开发中,单例作用域A类存在一个request作用域B类作为属性,当容器初始化时,按正常流程会实例化A类,但由于B类只会在发送请求访问时才会实例化,所以就会注入失败。这时候我们就可以使用Scope作用域代理来解决这个问题。
通过在需要代理的bean中配置<aop:scoped-proxy/>即可。
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"> <aop:scoped-proxy/> </bean>Scope作用域代理的含义为:当你要注入一个比该Bean作用域更长的Bean时,它会通过AOP动态代理代替这个作用域更长的bean,这个代理Bean与原有Bean具有相同的方法接口。
切记!CGLIB代理只提供公共方法调用,不要试图在代理中调用非公有方法。
AOP动态代理默认采用基于CGLIB的类代理方式,当然,你可以通过<aop:scoped-proxy proxy-target-class="false"/>选择基于JDK代理的接口代理方式。
CGLIB代理:通过实现类进行代理。JDK代理:通过实现该类的接口进行代理,这也意味着此类必须至少有一个接口。