IoC和DI思想主要是解决困惑一,AOP解决困惑二
控制反转,原来在程序中创建Bean的权利反转给第三方。
例如:原来在程序中手动的去 new UserServiceImpl(),手动的去new UserDaoImpl(),而根据IoC思想的指导,寻求一个第三方去创UserServiceImpl对象和UserDaoImpl对象。这样程序与具体对象就失去的直接联系。
谁去充当第三方角色呢?工厂设计模式,BeanFactory
BeanFactory怎么知道产生哪些Bean实例呢?使用配置文件配置Bean的基本信息
依赖注入,某个Bean的完整创建依赖于其他Bean(或普通参数)的注入
例如:将UserDao在BeanFactory内部设置给UserService的过程叫做“注入”,而UserService需要依赖UserDao的注入才能正常工作,这个过程叫做“依赖注入”
面试题:IoC 和 DI 的关系?
首先,先回答IoC和DI的是什么
其次,回答IoC和DI的关系:
第一种观点:IoC强调的是Bean创建权的反转,而DI强调的是Bean的依赖关系,认为不是一回事
第二种观点:IoC强调的是Bean创建权的反转,而DI强调的是通过注入的方式反转Bean的创建权,认为DI是IoC的其中一种实现方式
面向切面编程,用横向抽取方法(属性、对象等)思想,组装成一个功能性切面。(简单理解就是增强对象的)
开源、轻量级Java开发应用框架
生态及其完善,不管是Spring哪个领域的解决方案都是依附于SpringFramework基础框架的
Spring的官网:www.spring.io
Jsp 默默扛下所有;
MVC+三层架构分工明确,但开发成本及其高;
EJB 重量级框架出现,走出一个困境,有进入另一个困境;
Spring 春天来到,随之,SSH风生水起、称霸武林;
Spring 稳住江湖大哥位置,SSM开始上位;
Spring 本着“拿来主义”的思维快速发展,生态不断健全;
SpringBoot 又一里程碑崛起,把“约定大于配置“思想玩儿的炉火纯青;
SpringCloud 打包了微服务众多解决方案,应对互联网项目更加easy!
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.7version>
dependency>
开发时不会接触到,但是是Spring里最核心的对象
只要在配置文件里配置了对象,BeanFactory 就可以帮我们创建
只要在配置文件里对象配置引用子标签,对象中有Set方法,BeanFactory 就可以帮我们创建并注入
只是简化了开发代码,配置文件还是一样
原理: ApplicationContext实现了BeanFactory接口,在调用ApplicationContext时,实际还是在底层调用BeanFactory,ApplicationContext里面维护了一个BeanFactory,BeanFactory里面维护了一个map,里面存放了Bean
ApplicationContext 称为Spring容器,内部封装了BeanFactory,比BeanFactory功能更丰富更强大,使用ApplicationContext 进行开发时,xml配置文件的名称习惯写成applicationContext.xml
面试题: BeanFactory与ApplicationContext的关系
1)BeanFactory是Spring的早期接口,称为Spring的Bean工厂,ApplicationContext是后期更高级接口,称之为Spring
容器;
2)ApplicationContext在BeanFactory基础上对功能进行了扩展,例如:监听功能、国际化功能等。BeanFactory的API更偏向底层,ApplicationContext的API大多数是对这些底层API的封装;
3)Bean创建的主要逻辑和功能都被封装在BeanFactory中ApplicationContext不仅继承了BeanFactory,而且ApplicationContext内部还维护着BeanFactory的引用,所以,ApplicationContext与BeanFactory既有继承关系,又有融合关系。
4)Bean的初始化时机不同,原始BeanFactory是在首次调用getBean时才进行Bean的创建 (延迟加载),而ApplicationContext则是配置文件加载,容器一创建就将Bean都实例化并初始化好 (立即加载)。
ApplicationContext除了继承了BeanFactory外,还继承了ApplicationEventPublisher(事件发布器)、
ResouresPatternResolver(资源解析器)、MessageSource(消息资源)等。但是ApplicationContext的核心功
能还是BeanFactory。
BeanFactory的具体实现类是DefaultListableBeanFactory。
DefaultListableBeanFactory里面有一个Map叫beanDefinitionMap这里面维护的是配置文件里配置的Bean
ApplicationContext具体实现类有三个:
注意:这是只导入了spring-context坐标的结构,如果还导入了spring-web坐标,实现类变成XmlWebApplicationContext,GroovyApplicationContext,AnnotatinConfigWebApplicationContext这些实现类还整合了其他技术。
开发一般用注解,但是xml更能看清spring原理
Xml配置方式 | 功能描述 |
---|---|
< bean id=“” class=“” > | Bean的id和全限定名配置 注:id在容器中会转化成Bean的name,getBean()时传的参数spring只会在配置文件找id或name匹配的类,如果没有赋值id,getBean的参数应该是全限定名 |
< bean name=“”> | 通过name设置Bean的别名(小名),通过别名也能直接获取到Bean实例 注:如果匹配到的是name,spring还会根据name找到id(ApplicationContext的BeanFactory里面维护了别名映射map)将id作为BeanName,如果没有配置id,则用name作为BeanName |
< bean scope=“”> | Bean的作用范围,BeanFactory作为容器时取值singleton和prototype (实际开发中很少配置) |
< bean lazy-init=“”> | Bean的实例化时机,是否延迟加载。BeanFactory作为容器时无效 |
< bean init-method=“”> | Bean实例化后自动执行的初始化方法,method指定方法名 注:构造方法是创建对象用的,初始化方法是对象创建完后用的 |
< bean destroy-method=“”> | Bean实例销毁前的方法,method指定方法名 |
< bean autowire=“byType”> | 设置自动注入模式,常用的有按照类型byType,按照名字byName 注:手动注入需要配置了子类,如果自动注入则不需要配置,spring会根据set方法自己去找想要的Bean,注意名称对应才能找到。 |
< bean constructor-arg=“” > | 有参构造方法实例化Bean 注:此标签不仅仅是为构造方法传递参数,只要是为了实例化对象而传递的参数都可以通过< constructor-arg >标签完成,如果静态工厂方法实例化Bean所传递的参数也是要通过< constructor-arg >进行传递的 |
< bean factory-bean=“” /> | 指定哪个工厂Bean的哪个方法完成Bean的创建 注:原来是创建class里的对象(Spring会自己找无参构造进行创建),命名为id,现在是找到class里的对象中factory-bean规定的方法,用这个方法的返回值创建对象,对象名为id。 |
注:一般在实际开发中配置id,不配置name
面试题:scope的取值:
在单纯的spring环境中:
- singleton:单例,默认值,Spring容器创建的时候,就会进行Bean的实例化,并存储到容器内部的单例池中,每次getBean时都是从单例池中获取相同的Bean实例;
- prototype:原型,Spring容器初始化时不会创建Bean实例,当调用getBean时才会实例化Bean,每次getBean都会创建一个新的Bean实例。
- 在spring-webmvc环境中:
还有request、 session两个取值,可以放在request和session域中
面试题:Spring的实例化方式?
- 构造方式实例化:底层通过构造方法对Bean进行实例化
- 无参构造方法实例化
- 有参构造方法实例化
- 工厂方式实例化:底层通过调用自定义的工厂方法对Bean进行实例化
- 静态工厂方法实例化Bean:定义一个工厂类,提供一个静态方法用于生产Bean实例,在将该工厂类及其静态方法配置给Spring即可。 应用:在创建Bean之前可以写一些其他业务代码;导入的jar中也有别人写好的需要静态构造的Bean,配置factory-method构造。
- 实例工厂方法实例化Bean:也就是非静态工厂方法产生Bean实例,与静态工厂方式比较,该方式需要先有工厂对象,在用工厂对象去调用非静态方法,所以在进行配置时,要先配置工厂Bean,再配置目标Bean。
- 实现FactoryBean规范延迟实例化Bean:和前两种可以达到相同的目的,只不过不用自定义方法了,实现接口规范即可。这种方法在底层的实现上是懒加载的,spring容器创建时不会创建Bean,用到的时候才会创建,最后把Bean放入缓存池中。(正常开发不会用到,但是spring的源码底层经常用)
//静态工厂方法实例化Bean
public class UserDaoFactoryBean {//工厂类
//静态工厂方法
public static UserDao getUserDao(String name){
//可以在此编写一些其他逻辑代码
return new UserDaoImpl();
}
}
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean" factory-method="getUserDao">
//实例工厂方法实例化Bean
public class UserDaoFactoryBean2 {
//非静态工厂方法
public UserDao getUserDao(String name){
//可以在此编写一些其他逻辑代码
return new UserDaoImpl();
}
}
<!-- 配置实例工厂Bean -->
<bean id="userDaoFactoryBean2" class="com.itheima.factory.UserDaoFactoryBean2"/>
<!-- 配置实例工厂Bean的哪个方法作为工厂方法 -->
<bean id="userDao" factory-bean="userDaoFactoryBean2" factory-method="getUserDao">
public class UserDaoFactoryBean3 implements FactoryBean<UserDao> {
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
}
//配置FactoryBean交由Spring管理即可
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean3"/>
注入方式 | 配置方式 |
---|---|
通过Bean的set方法注入 | < property name=“userDao” ref=“userDao”/>< property name=“userDao” value=“haohao”/> |
通过构造Bean的方法进行注入 | < constructor-arg name=“name” ref=“userDao”/>< constructor-arg name=“name” value=“haohao”/> |
注:其中,ref 是 reference 的缩写形式,翻译为:涉及,参考的意思。用于引用其他Bean的id。value 用于注入普通属性值。
依赖注入的数据类型有如下三种:
<!--List<String>普通数据类型-->
<property name="strList">
<list>
<value>haohao</value>
<value>miaomiao</value>
</list>
</property>
<!--List<String>引用数据类型-->
<bean id="userDao3" class="com.itheima.dao.impl.UserDaoImpl"/>
<property name="objList">
<list>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
<ref bean="userDao3"></ref>
</list>
</property>
<!-- 注入泛型为字符串的Set集合 -->
<property name="valueSet">
<set>
<value>muzi</value>
<value>muran</value>
</set>
</property>
<!-- 注入泛型为对象的Set集合 -->
<bean id="userDao3" class="com.itheima.dao.impl.UserDaoImpl"/>
<property name="objSet">
<set>
<bean class="com.itheima.dao.impl.UserDaoImpl"></bean>
<ref bean="userDao3"></ref>
</set>
</property>
注:后面将省略注册UserDao,只写引用方式的注入,另一种就不写了
<!--注入值为字符串的Map集合-->
<property name="valueMap">
<map>
<entry key="aaa" value="AAA" />
<entry key="bbb" value="BBB" />
</map>
</property>
<!--注入值为对象的Map集合-->
<property name="objMap">
<map>
<entry key="ud" value-ref="userDao"/>
<entry key="ud2" value-ref="userDao2"/>
<entry key="ud3" value-ref="userDao3"/>
</map>
</property>
<property name="properties">
<props>
<prop key="xxx">XXX</prop>
<prop key="yyy">YYY</prop>
</props>
</property>
自动装配方式:如果被注入的属性类型是Bean引用的话,那么可以在 标签中使用 autowire 属性去配置自动注入方式,属性值有两个
Spring 的 xml 标签大体上分为两类,一种是默认标签,一种是自定义标签
默认标签 | 作用 |
---|---|
< beans> | 一般作为 xml 配置根标签,其他标签都是该标签的子标签 |
< bean> | Bean的配置标签,上面已经详解了,此处不再阐述 |
< import> | 外部资源导入标签 |
< alias> | 指定Bean的别名标签,使用较少 |
<!-- 配置测试环境下,需要加载的Bean实例 -->
<beans profile="test">
</beans>
<!-- 配置开发环境下,需要加载的Bean实例 -->
<beans profile="dev">
</beans>
可以使用以下两种方式指定被激活的环境:
<!--导入用户模块配置文件-->
<import resource="classpath:UserModuleApplicationContext.xml"/>
<!--导入商品模块配置文件-->
<import resource="classpath:ProductModuleApplicationContext.xml"/>
<!--配置UserService-->
<bean id="userService" name="aaa,bbb" class="com.itheima.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<!--指定别名-->
<alias name="userService" alias="xxx"/>
<alias name="userService" alias="yyy"/>
注:BeanFactory里面维护了一个aliasMap专门映射别名和id的关系
自定义标签:需要引入外部的命名空间,并为外部的命名空间指定前缀,使用 <前缀:标签> 形式的标签,称之为自定义标签,自定义标签的解析流程也是 Spring xml扩展点方式之一,在《Spring整合其他框架》章节进行详细介绍
<!--默认标签-->
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/>
<!--自定义标签-->
<context:property-placeholder/>
<mvc:annotation-driven/>
<dubbo:application name="application"/>
注:1.引用坐标 2.引入命名空间和地址映射 3.使用标签
方法定义 | 返回值和参数 |
---|---|
Object getBean (String beanName) | 根据beanName从容器中获取Bean实例,要求容器中Bean唯一,返回值为Object,需要强转 |
T getBean (Class type) | 根据Class类型从容器中获取Bean实例,要求容器中Bean类型唯一,返回值为Class类型实例,无需强转(一般开发中,一个类型只配置一个Bean) |
T getBean (String beanName,Class type) | 根据beanName从容器中获得Bean实例,返回值为Class类型实例,无需强转 |
//根据beanName获取容器中的Bean实例,需要手动强转
UserService userService = (UserService) applicationContext.getBean("userService");
//根据Bean类型去容器中匹配对应的Bean实例,如存在多个匹配Bean则报错
UserService userService2 = applicationContext.getBean(UserService.class);
//根据beanName获取容器中的Bean实例,指定Bean的Type类型
UserService userService3 = applicationContext.getBean("userService", UserService.class);
在实际开发中有些功能类是使用的第三方jar包中的,那么这些Bean要想让Spring进行管理,也需要对其进行配置
配置非自定义的Bean需要考虑如下两个问题:
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!-- druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<!--配置 DruidDataSource数据源(无参构造)-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
<!--配置必要属性(必须注入四个属性)-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
Connection 的产生是通过DriverManager的静态方法getConnection获取的,所以我们要用静态工厂方式配置。静态工厂方式,三个参数
<bean class="java.lang.Class" factory-method="forName">
<constructor-arg name="className" value="com.mysql.jdbc.Driver"/>
</bean>
<bean id="connection" class="java.sql.DriverManager" factory-method="getConnection" scope="prototype">
<constructor-arg name="url" value="jdbc:mysql:///mybatis"/>
<constructor-arg name="user" value="root"/>
<constructor-arg name="password" value="root"/>
</bean>
实际开发中Connection被DataSource封装了,不这么配,这里只是应用一下
产生一个指定日期格式的对象,原始代码按如下:
String currentTimeStr = "2023-08-27 07:20:00";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(currentTimeStr);
可以看成是实例工厂方式,使用Spring配置方式产生Date实例
<bean id="simpleDateFormat" class="java.text.SimpleDateFormat">
<constructor-arg name="pattern" value="yyyy-MM-dd HH:mm:ss"/>
</bean>
<bean id="date" factory-bean="simpleDateFormat" factory-method="parse">
<constructor-arg name="source" value="2023-08-27 07:20:00"/>
</bean>
实际开发中没有必要用spring帮我们产生日期对象,只是在这演示一下实例工厂方式
<!--mybatis框架-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
//加载mybatis核心配置文件,使用Spring静态工厂方式
InputStream in = Resources.getResourceAsStream(“mybatis-conifg.xml”);
//创建SqlSessionFactoryBuilder对象,使用Spring无参构造方式
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//调用SqlSessionFactoryBuilder的build方法,使用Spring实例工厂方式
SqlSessionFactory sqlSessionFactory = builder.build(in);
<!--静态工厂方式产生Bean实例-->
<bean id=“inputStream” class=“org.apache.ibatis.io.Resources” factory-method=“getResourceAsStream”>
<constructor-arg name=“resource” value=“mybatis-config.xml/>
</bean>
<!--无参构造方式产生Bean实例-->
<bean id="sqlSessionFactoryBuilder" class="org.apache.ibatis.session.SqlSessionFactoryBuilder"/>
<!--实例工厂方式产生Bean实例-->
<bean id="sqlSessionFactory" factory-bean="sqlSessionFactoryBuilder" factory-method="build">
<constructor-arg name="inputStream" ref="inputStream"/>
</bean>
Bean信息定义对象-BeanDefinition:
<bean id="" class="" name="" lazyinit="" scope="" init-method=""
destroy-method="" factory-bean=""
factory-method="" abstract=""
depends-on="" parent="">
<property name="" ref=""/>
<property name="" ref=""/>
<property name="" value=""/>
</bean>
public interface BeanDefinition {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
void setBeanClassName(@Nullable String var1);
String getBeanClassName();
void setScope(@Nullable String var1);
String getScope();
void setLazyInit(boolean var1);
boolean isLazyInit();
void setFactoryBeanName(@Nullable String var1);
String getFactoryBeanName();
void setFactoryMethodName(@Nullable String var1);
String getFactoryMethodName();
void setInitMethodName(@Nullable String var1);
String getInitMethodName();
//..... 省略部分属性和方法
}
Bean 实例化的基本流程:
Spring的后处理器是Spring对外开发的重要扩展点(框架帮我们弄好了,但是我们要实现一些需求要和已经弄好的不一样,要介入框架,就需要通过扩展点实现),允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。开发中使用AOP增强,就是基于这两个扩展点实现的。
Spring主要有两种后处理器:
BeanFactoryPostProcessor 和BeanPostProcessor在SpringBean的实例化过程中的体现:
BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能。
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{
System.out.println("MyBeanFactoryPostProcessor执行了...");
}
}
//配置BeanFactoryPostProcessor
<bean class="com.itheima.processor.MyBeanFactoryPostProcessor"/>
postProcessBeanFactory 参数本质就是 DefaultListableBeanFactory,拿到BeanFactory的引用,自然就可以对beanDefinitionMap中的BeanDefinition进行操作了(但是为了安全,我们是不能拿到整个Map的,我们可以通过BeanName操作) 。
举例:
/*快速入门*/
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition userDaoBD = beanFactory.getBeanDefinition(“userDao”);//获得UserDao定义对象
userDaoBD.setBeanClassName("com.itheima.dao.impl.UserDaoImpl2"); //修改class
//userDaoBD.setInitMethodName(methodName); //修改初始化方法
//userDaoBD.setLazyInit(true); //修改是否懒加载
//... 省略其他的设置方式 ...
}
}
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
//强转成子类DefaultListableBeanFactory
if(configurableListableBeanFactory instanceof DefaultListableBeanFactory){
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
//postProcessBeanFactory 参数本质就是 DefaultListableBeanFactory,DefaultListableBeanFactory里面才有注册方法。
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.itheima.dao.UserDaoImpl2");
//进行注册操作
beanFactory.registerBeanDefinition("userDao2",beanDefinition);
}
}
}
注:关于注册操作,Spring 提供了一个BeanFactoryPostProcessor的子接口 BeanDefinitionRegistryPostProcessor专门用于注册BeanDefinition操作
public class MyBeanFactoryPostProcessor2 implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.itheima.dao.UserDaoImpl2");
beanDefinitionRegistry.registerBeanDefinition("userDao2",beanDefinition);
}
}
执行顺序:
BeanDefinitionRegistryPostProcessor 的postProcessBeanDefinitionRegistry方法
BeanDefinitionRegistryPostProcessor 的postProcessBeanFactory方法
BeanFactoryPostProcessor 的postProcessBeanFactory方法
Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的BeanPostProcessor,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用。
/*快速入门*/
public class MyBeanPostProcessor implements BeanPostProcessor {
/* 参数: bean是当前被实例化的Bean,beanName是当前Bean实例在容器中的名称
返回值:当前Bean实例对象 */
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor的before方法...");
return bean;
}
/* 参数: bean是当前被实例化的Bean,beanName是当前Bean实例在容器中的名称
返回值:当前Bean实例对象 */
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor的after方法...");
return bean;
}
}
//配置MyBeanPostProcessor
<bean class="com.itheima.processors.MyBeanPostProcessor"></bean>
执行顺序:
Bean创建
BeanPostProcessor的before方法…
Bean属性填充…
Bean初始化init方法执行…
BeanPostProcessor的after方法…
举例:
对Bean方法进行执行时间日志增强要求如下:
分析:
/*编写BeanPostProcessor,增强逻辑编写在 after方法中,这里只写被覆盖的方法代码*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//对Bean进行动态代理,返回的是Proxy代理对象,进而存储到单例池中
Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),(Object proxy, Method method, Object[] args) -> {
long start = System.currentTimeMillis();
System.out.println("开始时间:" + new Date(start));
//执行目标方法
Object result = method.invoke(bean, args);
long end = System.currentTimeMillis();
System.out.println("结束时间:" + new Date(end));
return result;
});
//返回代理对象
return proxyBean;
}
Spring Bean的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段:
Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的,是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;
Bean的初始化阶段:该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、spring高频面试题Bean的循环引用问题都是在这个阶段体现的。 Bean创建之后还仅仅是个"半成品",还需要:
Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。
最重要的阶段,后四点前面已经讲过,所以在这讲解前两点:
Spring在进行属性注入时,会分为如下几种情况:
问题:都是半成品谁都没法向下执行。
解决方案:Spring提供了三级缓存存储 完整Bean实例 和 半成品Bean实例 ,用于解决循环引用问题
1、最终存储单例Bean 成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
Map
2、早期Bean单例池,缓存 半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"
Map
3、单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称为"三级缓存"
Map
面试题:如何解决循环引用问题?
UserService和UserDao循环依赖的过程结合上述三级缓存描述一下
- UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
- UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;
- UserDao实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
- UserDao属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;
- UserDao执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
- UserService 注入UserDao;
- UserService执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
常用的Aware接口:
Aware接口 | 回调方法 | 作用 |
---|---|---|
ServletContextAware | setServletContext(ServletContext context) | Spring框架回调方法注入ServletContext对象,web环境下才生效 |
BeanFactoryAware | setBeanFactory(BeanFactory factory) | Spring框架回调方法注入beanFactory对象 |
BeanNameAware | setBeanName(String beanName) | Spring框架回调方法注入当前Bean在容器中的beanName |
ApplicationContextAware | setApplicationContext(ApplicationContext applicationContext) | Spring框架回调方法注入applicationContext对象 |
xml整合第三方框架有两种整合方案:
举例:
1. Spring整合MyBatis(不需要自定义名空间)
之前已经在Spring中简单的配置了SqlSessionFactory(使用静态工厂等方式的内容),但是这不是正规的整合方式,MyBatis提供了mybatis-spring.jar专门用于两大框架的整合。
Spring整合MyBatis的步骤如下:
(1)导入MyBatis整合Spring的相关坐标;(spring-jdbc;mybatis-spring)
(2)编写Mapper和Mapper.xml;
(3)配置SqlSessionFactoryBean和MapperScannerConfigurer;
(4)编写测试代码
整合前后的区别:以前需要先配置UserMapper,再配置UserMapper注入UserService,整合后只需要配置UserMapper注入UserService,UserMapper会被包扫描自动扫描配置
Spring整合MyBatis的原理剖析:
整合包里提供了一个SqlSessionFactoryBean和一个扫描Mapper的配置对象SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并通过动态代理产生Mapper的实现类存储到Spring容器中。相关的有如下四个类:
(1)SqlSessionFactoryBean: 需要进行配置,作用是向容器中提供SqlSessionFactory,SqlSessionFactoryBean实现了FactoryBean和InitializingBean两个接口,所以会自动执行getObject() 和afterPropertiesSet()方法
(2)MapperScannerConfigurer: 需要进行配置,作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor和InitializingBean两个接口,会在postProcessBeanDefinitionRegistry方法中向容器中注册MapperFactoryBean
(3)MapperFactoryBean:Mapper的FactoryBean,获得指定Mapper时调用getObject方法;
(4)ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自动注入状态,所以MapperFactoryBean中的setSqlSessionFactory会自动注入进去。PS:autowireMode取值:1是根据名称自动装配,2是根据类型自动装配
ApplicationContext.xml配置SqlSessionFactoryBean和MapperScannerConfigurer:
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置SqlSessionFactoryBean,作用:把SqlSessionFactory存到spring容器-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Mapper包扫描,作用:扫描指定的包,产生Mapper对象存到spring容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.dao"></property>
</bean>
编写Mapper和Mapper.xml
public interface UserMapper {
List<User> findAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.dao.UserMapper">
<select id="findAll" resultType="com.itheima.pojo.User">
select * from tb_user
</select>
</mapper>
2.spring整合context(需要引入第三方框架命名空间)
Spring 整合其他组件时就不像MyBatis这么简单了,例如Dubbo框架在于Spring进行整合时,要使用Dubbo提供的命名空间的扩展方式,自定义了一些Dubbo的标签
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!--配置应用名称-->
<dubbo:application name="dubbo1-consumer"/>
<!--配置注册中心地址-->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!--扫描dubbo的注解-->
<dubbo:annotation package="com.itheima.controller"/>
<!--消费者配置-->
<dubbo:consumer check="false" timeout="1000" retries="0"/>
</beans>
为了降低我们此处的学习成本,不在引入Dubbo第三方框架了,以Spring的 context 命名空间去进行讲解,该方式也是命名空间扩展方式。
需求:加载外部properties文件,将键值对存储在Spring容器中
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
引入context命名空间,在使用context命名空间的标签,使用SpEL表达式在xml或注解中根据key获得value
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<beans>
外部命名空间标签的执行流程,如下:
3.案例: 设想自己是一名架构师,进行某一个框架与Spring的集成开发,效果是通过一个指示标签,向Spring容器中自动注入一个BeanPostProcessor
步骤分析:
*编写schema约束文件haohao-annotation.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.itheima.com/haohao"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.itheima.com/haohao">
<xsd:element name="annotation-driven"></xsd:element>
</xsd:schema>
*在类加载路径下创建META目录,编写约束映射文件spring.schemas和处理器映射文件spring.handlers
*编写命名空间处理器 HaohaoNamespaceHandler,在init方法中注册HaohaoBeanDefinitionParser
public class HaohaoNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
this.registerBeanDefinitionParser("annotation-driven",new HaohaoBeanDefinitionParser());
}
}
*编写标签的解析器 HaohaoBeanDefinitionParser,在parse方法中注HaohaoBeanPostProcessor
public class HaohaoBeanDefinitionParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
//创建HaohaoBeanPostProcessor的BeanDefinition
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(HaohaoBeanPostProcessor.class);
//注册HaohaoBeanPostProcessor
parserContext.getRegistry().registerBeanDefinition("haohaoBeanPostProcessor",beanDefinition);
return beanDefinition;
}}
*编写HaohaoBeanPostProcessor
public class HaohaoBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("自动注入HaohaoBeanPostProcessor成功");
return bean;
}
}
核心:用注解替代标签,用核心配置类替代xml文件
替代< bean>标签:
xml配置 | 注解 | 描述 |
---|---|---|
< bean id=“” class=“”> | @Component | 被该注解标识的类,会在指定扫描范围内被Spring加载并实例化。注:注解的value属性指定当前Bean实例的beanName,也可以省略不写,不写的情况下为当前类名首字母小写 |
< bean scope=“”> | @Scope | 在类上或使用了@Bean标注的方法上,标注Bean的作用范围,取值为singleton或prototype |
< bean lazy-init=“”> | @Lazy | 在类上或使用了@Bean标注的方法上,标注Bean是否延迟加载,取值为true和false |
< bean init-method=“”> | @PostConstruct | 在方法上使用,标注Bean的实例化后执行的方法 |
< bean destroy-method=“”> | @PreDestroy | 在方法上使用,标注Bean的销毁前执行方法 |
由于JavaEE开发是分层的,为了每层Bean标识的注解语义化更加明确,@Component又衍生出如下三个注解:
@Component衍生注解 | 描述 |
---|---|
@Repository | 在Dao层类上使用 |
@Service | 在Service层类上使用 |
@Controller | 在Web层类上使用 |
如果单单只使用注解是没用的,需要告诉Spring去哪找这些Bean,要配置组件扫描路径:
<!-- 告知Spring框架去itheima包及其子包下去扫描使用了注解的类 -->
<context:component-scan base-package="com.itheima"/>
扫描到了注解,知道了那些Bean需要实例化,那具体将Bean放入容器的事情是谁干的?通过Bean后处理器完成
替代< property> 标签:
属性注入注解 | 描述 |
---|---|
@Value | 使用在字段或方法上,用于注入普通数据 |
@Autowired | 使用在字段或方法上,用于根据类型(byType)注入引用数据 |
@Qualifier | 使用在字段或方法上,结合@Autowired,根据名称注入 |
@Resource | 使用在字段或方法上,既可以根据类型注入,也可以根据名称注入,无参就是根据类型注入,有参数就是根据名称注入 PS:@Resource注解存在与 javax.annotation 包中,Spring对其进行了解析 |
举例:
@Value("haohao")//也可以用于set方法
private String username;
@Value("${jdbc.username}")
private String username;
<context:property-placeholder location="classpath:jdbc.properties"/>
ps:注解和xml不同的是无须提供set方法
@Repository("userDao")//匹配
public class UserDaoImpl implements UserDao{}
@Repository("userDao2")
public class UserDaoImpl2 implements UserDao{}
当容器中同一类型的Bean实例有多个时,会尝试自动根据名字进行匹配,名字与被注入Bean名称不匹配时会报错。 上述代码中的名字是userDao,下面两个相同类型的Bean中第一个的beanName与之匹配,所以选择第一个注入,不会报错,如果没有匹配的则会报错。
@Autowired
private UserDao userDao;//UserDaoImpl
扩展:@Autowired不仅可以用在set方法上,还可以加在一般方法上,根据类型自动匹配参数
@Autowired//先匹配类型,有多个就匹配名称
public void xxx(UserDao userDao){//UserDaoImpl
System.out.println(userDao);
}
@Autowired//匹配类型,因为参数是集合,所以只要类型匹配,都放进去
public void yyy(List<UserDao > userDaoList){//UserDaoImpl ,UserDaoImpl2
System.out.println(userDaoList);
}
@Autowired
@Qualifier("userDao2")
private UserDao userDao;
非自定义Bean不能像自定义Bean一样使用@Component进行管理,非自定义Bean要通过工厂的方式进行实例化,使用@Bean标注方法即可,@Bean的属性为beanName,如不指定beanName,为当前工厂方法名称
PS:工厂方法所在类必须要被Spring管理 (只用@Bean是不管用的),可以在类上加@Component,或者在配置类上用@import引用该类。
将方法返回值Bean实例以@Bean注解指定的名称存储到Spring容器中:
@Bean("dataSource")
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
如果@Bean工厂方法需要参数的话,则有如下几种注入方式:
1.使用@Autowired 根据类型自动进行Bean的匹配,@Autowired可以省略 ;
2.使用@Qualifier 根据名称进行Bean的匹配;(注入属性需要和@Autowired一起使用,注入参数无须加@Autowired)
3.使用@Value 根据名称进行普通数据类型匹配。
@Bean
@Autowired //根据类型匹配参数
public Object objectDemo01(UserDao userDao){
System.out.println(userDao);
return new Object();
}
@Bean
public Object objectDemo02(@Qualifier("userDao") UserDao userDao,@Value("${jdbc.username}") String username){
System.out.println(userDao);
System.out.println(username);
return new Object();
}
上面讲的注解作用都是替代了< bean>标签,但是像< import>、< context:componentScan> 等非< bean> 标签怎样去使用注解替代呢?
定义一个配置类替代原有的xml配置文件,< bean>标签以外的标签,一般都是在配置类上使用注解完成的
配置类的注解 | 作用 |
---|---|
@Configuration | 标识该类是一个配置类,替代xml文件+@Component作用 |
@ComponentScan | 组件扫描配置,替代< context:component-scan base-package=“”/> PS:指定一个或多个包名:扫描指定包及其子包下使用注解的类; 不配置包名:扫描当前@componentScan注解配置类所在包及其子包下的类 |
@PropertySource | 加载外部properties资源配置,替代< context:property-placeholder location=“”/> |
@Import | 加载其他配置类,替代< import resource=“classpath:beans.xml”/> |
@MapperScan | Mapper包扫描,替代< bean class=“org.mybatis.spring.mapper.MapperScannerConfigurer”> |
原来的配置文件:
<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 组件扫描 -->
<context:component-scan base-package="com.itheima"/>
<!-- 引入其他xml文件 -->
<import resource="classpath:beans.xml"/>
配置类代替xml:
@Configuration
@ComponentScan
@PropertySource("classpath:jdbc.properties")
@Import(OtherConfig.class)
public class ApplicationContextConfig {}
注解 | 作用 |
---|---|
@Primary | 相同类型的Bean优先被使用权,@Primary 是Spring3.0引入的,与@Component和@Bean一起使用,标注该Bean的优先级更高,则在通过类型获取Bean或通过@Autowired根据类型进行注入时,会选用优先级更高的 |
@Profile | 替代< beans profile=“test”>@Profile 标注在类或方法上,标注当前产生的Bean从属于哪个环境,只有激活了当前环境,被标注的Bean才能被注册到Spring容器里。不指定环境的Bean,任何环境下都能注册到Spring容器里 |
可以使用以下两种方式指定被激活的环境:(前面学过)
只要将Bean对应的BeanDefinition注册到beanDefinitionMap中,就可以经历整个SpringBean的生命周期,最终实例化进入单例池中xml是直接在spring容器创建时就扫描,将Bean注册到被beanDefinitionMap中去。
这个部分着重讲解整合第三方框架时xml到注解方式的过渡,具体的整合原理在前面xml整合第三方框架中已经讲解。
原有xml方式整合配置如下:
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置SqlSessionFactoryBean-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置Mapper包扫描-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.dao"></property>
</bean>
注解整合:
@Configuration//代替配置文件
@ComponentScan("com.itheima")//代替包扫描
@MapperScan("com.itheima.mapper")//代替Mapper包扫描
public class ApplicationContextConfig {
@Bean//代替配置数据源
public DataSource dataSource(
@Value("${jdbc.driver}") String dirver,
@Value("${jdbc.url}") String url,
@Value("${jdbc.username}") String username,
@Value("${jdbc.password}") String password,
){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(dirver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean//代替SqlSessionFactoryBean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}}
开发者使用第三方框架:
@Import导入实现了ImportSelector接口的类
@Configuration
@ComponentScan("com.itheima")
@Import({MyImportSelector.class})
public class ApplicationContextConfig {
}
框架开发者开发时的代码:
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//参数annotationMetadata叫做注解媒体数组,该对象内部封装的是当前使用了@Import注解的类上的其他注解的元信息,也就是通过annotationMetadata我可以获取到@ComponentScan里面的值是com.itheima
//返回要进行注册的Bean的全限定名数组
return new String[]{User2.class.getName()};
}
}
MyImportSelector 干了什么?将User2注册到容器中,即不通过配置的方式,用代码的方式将类注册到容器中。
有啥用?框架开发者肯定没法操作使用框架人的代码,但是需要把框架里的Bean交给spring容器去管理,就可以在开发框架的时候写好代码,后面使用者只需要加上@Import({MyImportSelector.class})注解,就可以把里面我想要注册的Bean注册到spring容器中去。
开发者使用第三方框架:
@Import导入实现了ImportSelector接口的类
@Configuration
@ComponentScan("com.itheima")
@Import({MyImportBeanDefinitionRegistrar .class})
public class ApplicationContextConfig {
}
框架开发者开发时的代码:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//使用给定的BeanDefinitionRegistry参数,手动注册BeanDefinition
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.itheima.pojo.User2");
registry.registerBeanDefinition("user2",beanDefinition);
}
}
扩展:可以像@Mapper一样再封装一层,隐藏底层代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Import({MyImportBeanDefinitionRegistrar .class})
public @interface MyMapperScan(){
}
开发者在开发时就不需要写@Import({MyImportBeanDefinitionRegistrar .class})
,直接写@MyMapperScan
即可