问题一:层与层之间紧密耦合在了一起,接口与具体实现紧密耦合在了一起
解决思路:程序代码中不要手动new对象,第三方根据要求为程序提供需要的Bean对象
问题二:通用的事务功能耦合在业务代码中,通用的日志功能耦合在业务代码中
解决思路:程序代码中不要手动new对象,第三方根据要求为程序提供需要的Bean对象的代理对象
IoC思想
DI思想
AOP思想
Java中常用的框架:
不同语言,不同领域都有属于自己的框架,使用框架开发是作为程序员的最基础的底线。Java语言中的框架,可以分为基础框架和服务框架:
spring是一个开源的轻量级Java开发应用框架,可以简化企业级应用开发。Spring解决了开发者在JavaEE开发中遇到的许多常见的问题,提供了功能强大IOC
、AOP
及Web MVC
等功能。是当前企业中Java开发几乎不能缺少的框架之一。Spring的生态及其完善,不管是Spring哪个领域的解决方案都是依附于在Spring
Framework基础框架的。
Spring框架的历史
Spring Framework技术栈图示
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.7version>
dependency>
public interface UserService {
}
public class UserServiceImpl implements UserService {
}
<bean id="userService" class="com. itheima.service.impl.UserServiceImpl">bean>
//创建工厂对象
DefaultListableBeanFactory BeanFactory = new DefaultListableBeanFactory();
//创建一个读取器(XML文件)
XmlBeanDefinitionReader Reader = new XmlBeanDefinitionReader(BeanFactory);
//读取配置文件给工厂
Reader.loadBeanDefinitions("beans.xml");
//根据id获取Bean实例对象
UserService userService = (UserService)BeanFactory.getBean("UserService");
setUserDao(UserDao userDao)
用于接收注入的对象;public class UserServiceImpl implements UserService {
public void setUserDao(UserDao userDao){
System.out.println("BeanFactory调用方法获取userDao设置到此处"+userDao);
}
}
中嵌入
配置注入;<bean id="UserService" class="com.Smulll.service.Impl.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
bean>
<bean id="UserDao" class="com.Smulll.Dao.Impl.UserDaoImpl">bean>
//创建工厂对象
DefaultListableBeanFactory BeanFactory = new DefaultListableBeanFactory();
//创建一个读取器(XML文件)
XmlBeanDefinitionReader Reader = new XmlBeanDefinitionReader(BeanFactory);
//读取配置文件给工厂
Reader.loadBeanDefinitions("beans.xml");
//根据id获取Bean实例对象
UserService userService = (UserService)BeanFactory.getBean("UserService");
ApplicationContext 称为Spring容器,内部封装了BeanFactory,比BeanFactory功能更丰富更强大,使用ApplicationContext进行开发时,xml配置文件的名称习惯写成applicationContext.xml
//创建ApplicationContext,加载配置文件,实例化容器
ApplicationContext applicationContext = new ClassPathxmlApplicationContext("applicationContext.xml");
//根据beanName获得容器中的Bean实例
UserService userService = (UserService) applicationContext.getBean("UserService");
System.out.println(userService) ;
getBean
时才进行Bean的创建,ApplicationContext除了继承了BeanFactory外,还继承了ApplicationEventPublisher(事件发布器)、ResouresPatternResolver(资源解析器)、MessageSource(消息资源)等。但是ApplicationContext的核心功能还是BeanFactory。
BeanFactory是核心接口,项目运行过程中肯定有具体实现参与,这个具体实现就是DefaultListableBeanFactory
,而ApplicationContext内部维护的Beanfactory的实现类也是它
只在Spring基础环境下,即只导入spring-context坐标时,此时ApplicationContext的继承体系
只在Spring基础环境下,常用的三个ApplicationContext作用如下:
实现类 | 功能描述 |
---|---|
ClassPathXmlApplicationContext | 加载类路径下的xml配置的ApplicationContext |
FileSystemXmlApplicationContext | 加载磁盘路径下的xml配置的ApplicationContext |
AnnotationConfigApplicationContext | 加载注解配置类的ApplicationContext |
如果Spring基础环境中加入了其他组件解决方案,如web层解决方案,即导入spring-web坐标,此时ApplicationContext的继承体系
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.3.7version>
dependency>
只在Spring的Web环境下,常用的两个ApplicationContext作用如下:
实现类 | 功能描述 |
---|---|
XmlWebApplicationContext | web环境下,加载类路径下的xml配置的ApplicationContext |
AnnotationConfigWebApplicationContext | web环境下,加载磁盘路径下的xml配置的ApplicationContext |
Spring开发中主要是对Bean的配置,Bean的常用配置:
Xml配置方式 | 功能描述 |
---|---|
Bean的id和全限定名配置 | |
通过name设置Bean的别名,通过别名也能直接获取到Bean实例 | |
Bean的作用范围, BeanFactory作为容器时取值singleton和prototype | |
Bean的实例化时机,是否延迟加载。BeanFactory作为容器时无效 | |
Bean实例化后自动执行的初始化方法,method指定方法名 | |
Bean实例销毁前的方法,method指定方法名 | |
设置自动注入模式,常用的有按照类型byType,按照名字byName | |
指定哪个工厂Bean的哪个方法完成Bean的创建 |
Bean的基础配置
例如:配置UserDaolmpl由Spring容器负责管理
<bean id="userDao" class="com.itheima.dao .impl.UserDaoImp! " />
此时存储到Spring容器(singleObjects单例池)中的Bean的beanName是userDao,值是UserDaolmpl对象,可以根据beanName获取Bean实例
applicationContext.getBean("userDao");
如果不配置id,则Spring会把当前Bean实例的全限定名作为beanName
applicationContext.getBean("com.itheima.dao.impl.UserDaoImpl");
Bean的别名配置
可以为当前Bean指定多个别名,根据别名也可以获得Bean对象
<bean id="userDao" name="aaa,bbb" class="com.itheima.dao.impl.UserDaoImp1" />
此时多个名称都可以获得UserDaolmpl实例对象
applicationContext.getBean("userDao");
applicationContext.getBean("aaa");
applicationContext.getBean("bbb");
默认情况下,单纯的Spring环境Bean的作用范围有两个: Singleton和Prototype
当lazy-init设置为true时为延迟加载,也就是当Spring容器创建的时候,不会立即创建Bean实例,等待用到时在创建Bean实例并存储到单例池中去,后续在使用该Bean直接从单例池获取即可,本质上该Bean还是单例的
<bean id="userDao" class=" com.itheima.dao.impl.UserDaoImpl" lazy-init="true"/>
Bean在被实例化后,可以执行指定的初始化方法完成一些初始化的操作,Bean在销毁之前也可以执行指定的销毁方法完成一些操作,初始化方法名称和销毁方法名称通过
<bean id="userDao" class=" com.itheima.dao .impl.UserDaoImpl" init-method="init" destroy-method="destroy" />
public class UserDaoImpl implements UserDao {
public UserDaoImpl() { System.out.println("UserDaoImpl创建了...");}
public void init(){ System.out.println("初始化方法...");}
public void destroy(){ system.out.println("销毁方法...");}
}
扩展:除此之外,我们还可以通过实现InitializingBean
接口,完成一些Bean的初始化操作,如下:
public class UserDaoImpl implements UserDao,InitializingBean {
public UserDaoImpl(){System.out.println("UserDaoImpl创建了...");}
public void init(){System.out.println("初始化方法...");}
public void destroy(){System.out.println("销毁方法...");}
//执行时机早于init-method配置的方法
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean...");
}
}
Bean的实例化配置
Spring的实例化方式主要如下两种:
构造方式实例化Bean又分为无参构造方法实例化和有参构造方法实例化,Spring中配置的
几乎都是无参构造该方式,此处不在赘述。下面讲解有参构造方法实例化Bean
//有参构造方法
public UserDaoImpl (String name) {}
有参构造在实例化Bean时,需要参数的注入,通过
标签,嵌入在
标签内部提供构造参数,如下:
<bean id="userDao" class="com.itheima.dao .impl.UserDaoImpl">
<constructor-arg name="name" value="haohao" />
bean>
工厂方式实例化Bean,又分为如下三种:
使用该静态工厂方法的优点:
public class FactoryDemo1 {
public static UserService CreateUserService(){
return new UserServiceImpl();
}
}
<bean id="FactoryDemo1" class="com.Smulll.Factory.FactoryDemo1" factory-method="CreateUserService">bean>
使用该工厂方法的优点:
package com.Smulll.Factory;
import com.Smulll.service.Impl.UserServiceImpl;
import com.Smulll.service.UserService;
public class FactoryDemo2 {
public UserService CreateUserService(){
return new UserServiceImpl();
}
}
<bean id="factoryDemo2" class="com.Smulll.Factory.FactoryDemo2">bean>
<bean id="factoryUser" factory-bean="factoryDemo2" factory-method="CreateUserService">bean>
<bean id="factoryUser" factory-bean="factoryDemo2" factory-method="CreateUserService">
<constructor-arg name="name" value="zhangsan"/>
bean>
该参数是工厂方法构造Bean方法的参数
使工厂类继承FactoryBean
通过getObject()
方法返回对象
public class FactoryDemo3 implements FactoryBean<UserDao> {
@Override
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
<bean id="factoryDemo3" class="com.Smulll.Factory.FactoryDemo3">bean>
该方法有延迟功能,只有调用类时,才会将getObject()
方法返回的类储存到FactoryBean缓存当中
Bean的依赖注入又两种方式:
注入方式 | 配置方式 |
---|---|
通过Bean的set方法注入 |
|
通过构造Bean的方法进行注入 |
|
其中,ref是reference的缩写形式,翻译为:涉及,参考的意思,用于引用其他Bean的id。value用于注入普通属性值。
依赖注入的数据类型有如下三种:
注:property和constructor-arg的name属性和参数的名字必须要一样
public class UserDaoImpl implements UserDao {
//注入一个List集合
private List<String> list;
public void setList(List<String> list) {
this.list = list;
}
//注入一个字符串类型的Set集合
private Set<String> userSet;
public void setUserSet(Set<String> userSet) {
this.userSet = userSet;
}
//注入一个UserDao类的Set集合
private Set<UserDao> userDaoSet;
public void setUserDaoSet(Set<UserDao> userDaoSet) {
this.userDaoSet = userDaoSet;
}
//注入一个map集合
private Map<String,UserDao> map;
public void setMap(Map<String,UserDao> map) {
this.map = map;
}
//注入Properties
private Properties properties;
public void setProperties(Properties properties){
this.properties = properties;
}
public void show(){
System.out.println(list);
System.out.println(userSet);
System.out.println(userDaoSet);
System.out.println(map);
System.out.println(properties);
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<bean id="userDao" class="com.Smulll.Dao.Impl.UserDaoImpl">
<property name="list">
<list>
<value>aaavalue>
<value>sssvalue>
list>
property>
<property name="userSet">
<set>
<value>asdsavalue>
<value>11111value>
<value>22222value>
<value>33333value>
set>
property>
<property name="userDaoSet">
<set>
<ref bean="userDao1">ref>
<ref bean="userDao2">ref>
<ref bean="userDao3">ref>
<ref bean="userDao4">ref>
set>
property>
<property name="map">
<map>
<entry key="m1" value-ref="userDao1">entry>
<entry key="m2" value-ref="userDao2">entry>
<entry key="m3" value-ref="userDao3">entry>
<entry key="m4" value-ref="userDao4">entry>
map>
property>
<property name="properties">
<props>
<prop key="p1">p111prop>
<prop key="p2">p222prop>
<prop key="p3">p333prop>
<prop key="p4">p444prop>
props>
property>
bean>
<bean id="userDao1" class="com.Smulll.Dao.Impl.UserDaoImpl">bean>
<bean id="userDao2" class="com.Smulll.Dao.Impl.UserDaoImpl">bean>
<bean id="userDao3" class="com.Smulll.Dao.Impl.UserDaoImpl">bean>
<bean id="userDao4" class="com.Smulll.Dao.Impl.UserDaoImpl">bean>
beans>
@Test
public void demo5(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
UserDao userDao = (UserDao)classPathXmlApplicationContext.getBean("userDao");
userDao.show();
}
扩展:自动装配方式
如果被注入的属性类型是Bean引用的话,那么可以在
标签中使用autowire
属性去配置自动注入方式,属
性值有两个:
setXxx
与id="xxx" (name="xxx")
是否一致;private UserService userService;
public void setUserService(UserService userService){
this.userService = userService;
}
<bean id="userDao" class="com.Smulll.Dao.Impl.UserDaoImpl" autowire="byName">bean>
<bean id="userService" class="com.Smulll.service.Impl.UserServiceImpl">bean>
Spring的xml标签大体上分为两类,一种是默认标签,一种是自定义标签
标签
标签Spring的默认标签用到的是Spring的默认命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http: //www.w3.org/2001/xMschema-instance"
xsi:schemalocation="http: //www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
beans>
该命名空间约束下的默认标签如下:
标签 | 作用 |
---|---|
一般作为xml配置根标签,其他标签都是该标签的子标 | |
Bean的配置标签,上面已经详解了,此处不再阐述 | |
外部资源导入标签 | |
指定Bean的别名标签,使用较少 |
标签,除了经常用的做为根标签外,还可以嵌套在根标签内,使用profile
属性切换开发环境
<beans profile="test">
beans>
<beans profile="dev">
beans>
可以使用以下两种方式指定被激活的环境:
-Dspring.profiles.active=test
System.setProperty("spring.profiles.active","test")
标签,用于导入其他配置文件,项目变大后,就会导致一个配置文件内容过多,可以将一个配置文件根据业务某块进行拆分,拆分后,最终通过
标签导入到一个主配置文件中,项目加载主配置文件就连同
导入的文件一并加载了
<import resource="classpath:UserModuleApplicationcontext.xml"/>
<import resource="classpath:ProductModuleApplicationcontext.xml"/>
标签是为某个Bean
添加别名,与在
标签上使用name
属性添加别名的方式一样,我们为UserServicelmpl
指定四个别名: aaa、bbb、xxx、yyy
<bean id="userService" name="aaa,bbb" class="com.itheima.service.imp1.UserserviceImp1">
<property name="userDao" ref="userDao"/>
bean>
<alias name="userservice" alias="xxx"/>
<alias name="userservice" alias="yyy"/>
Spring的自定义标签需要引入外部的命名空间,并为外部的命名空间指定前缀,使用<前缀:标签>
形式的标签,称之为自定义标签,自定义标签的解析流程也是Spring xml扩展点方式之一
<bean id="userDao" class="com.itheima. dao .imp1.userDaoImpl" />
<context:property-placeholder/>
<mvc:annotation-driven/>
<dubbo:application name="application"/>
步骤:
方法定义 | 返回值和参数 |
---|---|
Object getBean (String beanName) | 根据beanName从容器中获取Bean实例,要求容器中Bean唯一,返回值为Object,需要强转 |
T getBean (Class type) | 根据Class类型从容器中获取Bean实例,要求容器中Bean类型唯一,返回值为Class类型实例,无需强转 |
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) ;
以上在xml中配置的Bean都是自己定义的,例如: UserDaolmpl,UserServicelmpl。但是,在实际开发中有些功能类并不是我们自己定义的,而是使用的第三方jar包中的,那么,这些Bean要想让Spring进行管理,也需要对其进行配置
配置非自定义的Bean需要考虑如下两个问题:
配置Druid数据源交由Spring管理
导入Druid坐标
<dependency>
<groupId>mysqlgroupid>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.49version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.23version>
dependency>
配置文件
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver">property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis">property>
<property name="username" value="xxxxxx">property>
<property name="password" value="xxxxxx">property>
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>
产生一个指定日期格式的对象,原始代码如下
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>
配置SqlSessionFactory交由Spring管理
导入Mybatis相关坐标
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
<bean id="inputStream" class="org.apache.ibatis.io.Resources" factory-method="getResourceAsStream">
<constructor-arg name="resource" value="mybatis-config.xml">constructor-arg>
bean>
<bean id="builder" class="org.apache.ibatis.session.SqlSessionFactoryBuilder">bean>
<bean id="factory" factory-bean="builder" factory-method="build">
<constructor-arg name="inputStream" ref="inputStream">constructor-arg>
bean>
的信息封装成一个BeanDefinition
对象;BeanDefinition
存储到一个名为beanDefinitionMap
的Map
集合中去;singletonObjects
的Map集合中;getBean
方法时则最终从该Map集合中取出Bean实例对象返回。
DefaultListableBeanFactory对象内部维护着一个Map用于存储封装好的BeanDefinitionMap
public class DefaultListableBeanFactory extends ... implements ... {
//存储标签对应的BeanDefinition对象
//key:是Bean的beanName,value:是Bean定义对象BeanDefinition
private final Map<String,BeanDefinition> beanDefinitionMap;
}
Spring框架会取出beanDefinitionMap
中的每个BeanDefinition
信息,反射构造方法或调用指定的工厂方法生成Bean实例对象,所以只要将BeanDefinition注册到beanDefinitionMap这个Map中,Spring就会进行对应的Bean的实例化操作
Bean实例及单例池singletonObjects,beanDefinitionMap中的BeanDefinition会被转化成对应的Bean实例对象,存储到单例池singletonObjects中去,在DefaultListableBeanFactory的上四级父类
DefaultSingletonBeanRegistry中,维护着singletonObjects
源码如下:
public class DefaultSingletonBeanRegistry extends ... implements ... {
//存储Bean实例的单例池
key :是Bean的beanName,value:是Bean的实例对象
private final Map<String,object> singleton0bjects = new ConcurrentHashMap(256)
基本流程:
的信息,封装成一个个的BeanDefinition
对象;BeanDefinition
存储在一个名为beanDefinitionMap
的Map
中;beanDefinitionMap
,创建Bean实例对象;singletonObjects
的Map
中;applicationContext.getBean(beanName)
时,从singletonObjects
去匹配Bean实例返回。Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:
BeanFactoryPostProcessor:
Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行;BeanPostProcessor
: Bean后处理器,一般在Bean实例化之后,填充到单例池singletonObjects之前执行。Bean工厂后处理器-BeanFactoryPostProcessor
BeanFactoryPostProcessor是一个接口规范,实现了该接口的类只要交由Spring容器管理的话,那么Spring就会回调该接口的方法,用于对BeanDefinition注册和修改的功能。
BeanFactoryPostProcessor定义如下:
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
}
标签public class MyBeanFactoryProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("beandefinitionMap填充完毕后回调该方法");
//1.注册一个beandefinition 创建一个RootBeanDefinition()对象
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClassName("com.Smulll.Dao.Impl.processorImpl");
//2.将beanFactory强转成DefaultListableBeanFactory类型
DefaultListableBeanFactory beanFactory1 = (DefaultListableBeanFactory) beanFactory;
beanFactory1.registerBeanDefinition("processor",rootBeanDefinition);
}
}
@Test
public void demo2(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
Object processor = classPathXmlApplicationContext.getBean("processor");
System.out.println(processor);
}
<bean class="com.Smulll.processor.MyBeanFactoryProcessor">bean>
Spring提供了一个BeanFactoryPostProcessor的子接口BeanDefinitionRegistryPostProcessor专门用于注册BeanDefinition操作
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
//创建一个RootBeanDefinition()对象
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClassName("com.Smulll.Dao.Impl.processorImpl");
//不需要强转就可以创建一个Bean
beanDefinitionRegistry.registerBeanDefinition("personDao",rootBeanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
BeanFactoryPostProcessor
在SpringBean的实例化过程中的体现
要求:
BaseClassScanUtils
public class BaseClassScanUtils {
//设置资源规则
private static final String RESOURCE_PATTERN = "/**/*.class";
public static Map<String, Class> scanMyComponentAnnotation(String basePackage) {
//创建容器存储使用了指定注解的Bean字节码对象
Map<String, Class> annotationClassMap = new HashMap<String, Class>();
//spring工具类,可以获取指定路径下的全部类
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
try {
String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
ClassUtils.convertClassNameToResourcePath(basePackage) + RESOURCE_PATTERN;
Resource[] resources = resourcePatternResolver.getResources(pattern);
//MetadataReader 的工厂类
MetadataReaderFactory refractory = new CachingMetadataReaderFactory(resourcePatternResolver);
for (Resource resource : resources) {
//用于读取类信息
MetadataReader reader = refractory.getMetadataReader(resource);
//扫描到的class
String classname = reader.getClassMetadata().getClassName();
Class<?> clazz = Class.forName(classname);
//判断是否属于指定的注解类型
if(clazz.isAnnotationPresent(MyComponent.class)){
//获得注解对象
MyComponent annotation = clazz.getAnnotation(MyComponent.class);
//获得属value属性值
String beanName = annotation.value();
//判断是否为""
if(beanName!=null&&!beanName.equals("")){
//存储到Map中去
annotationClassMap.put(beanName,clazz);
continue;
}
//如果没有为"",那就把当前类的类名作为beanName
annotationClassMap.put(clazz.getSimpleName(),clazz);
}
}
} catch (Exception exception) {
}
return annotationClassMap;
}
public static void main(String[] args) {
Map<String, Class> stringClassMap = scanMyComponentAnnotation("com.Smulll");
System.out.println(stringClassMap);
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {
String value();
}
@MyComponent("One")
public class OneBean {
}
Bean被实例化后,到最终缓存到名为singletonObjects
单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor
,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor
,会在流程节点上被Spring自动调用。
public class MyBeanPostProcesser implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName+"postProcessBeforeInitialization");
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName+"postProcessAfterInitialization");
return null;
}
}
postProcessBeforeInitialization
方法和postProcessAfterInitialization
方法需要进行手动创建,接口中实现的是null返回值的方法InitializingBean
接口中的afterPropertiesSet()
方法init-method
方法package com.Smulll.processor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import java.lang.reflect.Proxy;
import java.util.Date;
public class TimelogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//使用动态代理,对目标Bean进行增强,返回proxy对象,进而储存导单例池singletonObjects中
Object beanproxy = Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
(proxy, method, args) -> {
//1.输出开始时间
System.out.println("方法:" + method.getName() + "开始时间:" + new Date());
//2.执行目标方法
Object result = method.invoke(bean, args);
//3.输出结束时间
System.out.println("方法:" + method.getName() + "结束时间:" + new Date());
return result;
});
return beanproxy;
}
}
接口实现类
package com.Smulll.Dao.Impl;
import com.Smulll.Dao.UserDao;
import com.Smulll.service.UserService;
public class UserDaoImpl implements UserDao{
public void show(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("执行了show方法。。。");
}
private UserService userService;
public void setUserService(UserService userService){
this.userService = userService;
}
}
xml文件
<bean id="userDao" class="com.Smulll.Dao.Impl.UserDaoImpl" init-method="init">bean>
<bean class="com.Smulll.processor.TimelogBeanPostProcessor">bean>
测试方法
@Test
public void demo2(){
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao)classPathXmlApplicationContext.getBean("userDao");
userDao.show();
}
BeanPostProcessor在 SpringBean的实例化过程中的体现
Spring Bean的生命周期是从Bean 实例化之后,即通过反射创建出对象之后,导Bean成为一个完整对象,最终储存到单例池中,这个过程被称为SpringBean的生命周期。Spring Bean的生命周期大体上分为三个阶段:
BeanDefinition
的信息进行判断当前Bean的范围是否是singleton
的,是否不是延迟加载的,是否不是FactoryBean
等,最终将一个普通的singleton
的Bean通过反射进行实例化Aware接口
方法、执行BeanPostProcessor
方法、执行InitializingBean
接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等、spring高频面试题Bean的循环引用问题都是在这个阶段体现的singletonObjects
中去了,即完成了Spring Bean的整个生命周期。由于Bean的初始化阶段的步骤比较复杂,所以着重研究Bean的初始化阶段
Spring Bean的初始化过程涉及如下几个过程:
Aware接口
属性注入BeanPostProcessor
的before()
方法回调lnitializingBean接口
的初始化方法回调init回调
BeanPostProcessor
的after()
方法回调BeanDefinition 中有对当前Bean实体的注入信息通过属性propertyValues进行了存储
<bean id="userDao" class="com.Smulll.Dao.Impl.UserDaoImpl">bean>
<bean id="userService" class="com.Smulll.service.Impl.UserServiceImpl">
<property name="dao" ref="userDao">property>
<property name="username" value="AAA">property>
bean>
Spring在进行属性注入时,会分为如下几种情况:
public class UserServiceImpl implements UserService{
private UserDao dao;
private String username;
public UserServiceImpl() {
System.out.println("实例化UserService");
}
public UserServiceImpl(UserDao dao, String username) {
this.dao = dao;
this.username = username;
}
public void setDao(UserDao dao) {
System.out.println("执行setDao方法");
this.dao = dao;
}
public void setUsername(String username) {
this.username = username;
}
}
public class UserDaoImpl implements UserDao{
public UserDaoImpl() {
System.out.println("实例化UserDao");
}
}
user的
在userService的前面
<bean id="userDao" class="com.Smulll.Dao.Impl.UserDaoImpl">bean>
<bean id="userService" class="com.Smulll.service.Impl.UserServiceImpl">
<property name="dao" ref="userDao">property>
<property name="username" value="AAA">property>
bean>
执行结果
<bean id="userService" class="com.Smulll.service.Impl.UserServiceImpl">
<property name="dao" ref="userDao">property>
<property name="username" value="AAA">property>
bean>
<bean id="userDao" class="com.Smulll.Dao.Impl.UserDaoImpl">bean>
多个实体之间相互依赖并形成闭环的情况就叫做 “循环依赖”,也叫做 “循环引用”
<bean id="userService" class="com.Smulll.service.Impl.UserServiceImpl">
<property name="dao" ref="userDao">property>
bean>
<bean id="userDao" class="com.Smulll.Dao.Impl.UserDaoImpl">
<property name="service" ref="userService">property>
bean>
public class UserDaoImpl implements UserDao{
private UserService service;
public void setService(UserService service){
this.service = service;
}
}
public class UserServiceImpl implements UserService{
private UserDao dao;
public void setDao(UserDao dao) {
this.dao = dao;
}
}
Spring提供了三级缓存存储完整Bean实例和半成品Bean实例,用于解决循环引用问题
在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:
public class DefaultsingletonBeanRegistry ... {
//1、最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
Map<String,Object> singletonObjects = new ConcurrentHashMap(256);
//2、早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"
Map<String,Object> earlySingletonObjects = new ConcurrentHashMap(16);
//3、单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存"
Map<String,ObjectFactory<?>> singletonFactories = new HashMap(16);
}
UserService和UserDao循环依赖的过程结合上述三级缓存描述一下
Aware接口是一种框架辅助属性注入的一种思想,其他框架中也可以看到类似的接口。框架具备高度封装性,我们接触到的一般都是业务代码,一个底层功能API不能轻易的获取到,但是这不意味着永远用不到这些对象,如果用到了,就可以使用框架提供的类似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整合第三方框架有两种整合方案:
Spring整合MyBatis,之前已经在Spring中简单的配置了SqlSessionFactory,但是这不是正规的整合方式,MyBatis提供了mybatis-spring.jar专门用于两大框架的整合。
Spring整合MyBatis的步骤如下:
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>3.0.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.2.25.RELEASEversion>
dependency>
SqlSessionFactoryBean
和MapperScannerConfigurer
;<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver">property>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis2">property>
<property name="username" value="root">property>
<property name="password" value="123456">property>
bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.Smulll.mapper">property>
bean>
<bean id="userService" class="com.Smulll.service.Impl.UserServiceImpl">
<property name="carMapper" ref="carMapper">property>
bean>
整合包里提供了一个SqlSessionFactoryBean和一个扫描Mapper的配置对象,SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并通过动态代理产生Mapper的实现类存储到Spring容器中。相关的有如下四个类:
sqlSessionFactoryBean
:需要进行配置,用于提供SqlSessionFactory;MapperScannerConfigurer
:"需要进行配置,用于扫描指定mapper注册BeanDefinition;MapperFactoryBean
: Mapper的FactoryBean,获得指定Mapper时调用getObject方法;ClassPathMapperScanner
: definition.setAutowireMode(2)修改了自动注入状态,所以MapperFactoryBean中的setSqlSessionFactory会自动注入进去。Spring整合其他组件时就不像MyBatis这么简单了,例如Dubbo框架在于Spring进行整合时,要使用Dubbo提供的命名空间的扩展方式,自定义了一些Dubbo的标签
<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="your-application-name" />
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:service interface="com.example.YourServiceInterface" ref="yourServiceBean" />
<dubbo:consumer check="false" timeout="1000" retries="0"/>
beans>
通过配置context文件来加载外部properties文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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="driverClassName" value="${jdbc.driver}">property>
<property name="url" value="${jdbc.url}">property>
<property name="username" value="${jdbc.username}">property>
<property name="password" value="${jdbc.password}">property>
bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.Smulll.mapper">property>
bean>
<bean id="userService" class="com.Smulll.service.Impl.UserServiceImpl">
<property name="carMapper" ref="carMapper">property>
bean>
beans>
spring.schemas
文件里,该文件存储在类加载路径的META-INF里,Spring会自动加载到;spring.handlers
文件里,该文件存储在类加载路径的 META-INF里,Spring会自动加载到;NamespaceHandler
,如果命名空间只有一个标签,那么直接在parse方法中进行解析即可,一般解析结果就是注册该标签对应的BeanDefinition
。如果命名空间里有多个标签,那么可以在init方法中为每个标签都注册一个BeanDefinitionParser
,在执行NamespaceHandler
的parse方法时在分流给不同的BeanDefinitionParser
进行解析(重写doParse方法即可)。