Spring框架
在软件开过程中,我们需要使用到实例。我们又期望这些实例以单例的形式存在。现在我们要获得单例没有一个统一的入口,这时候就有了Spring。
我们使用Spring将这些实例统一的管理起来了,如果要获得某个类型的实例,可以通过Spring来获得
使用Spring容器来管理实例,并且从Spring容器中取出实例
组件:Spring容器管理的实例叫组件
注册:注册组件,Spring容器管理实例
引入依赖
Spring的依赖5+1
spring-core\context\aop\expression\beans + jcl(日志包)
junit
Spring依赖的特征:spring-xxx(官方)、 xxx-spring(非官方)
<!--5+1--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.15.RELEASE</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
配置文件
为啥要使用配置文件。使用配置文件将需要管理的实例做统一的管理
Spring配置文件是xml格式的 → 使用xml需要规范,xml中可以使用哪一些标签、可以使用哪一些属性,标签又有什么样的子标签 → 约束,同时也提供了解析的方式
我们需要去引入约束(头)
- 已有的项目复制(×)
- 文件模板(×)
- Spring官方文档 →
→ schema约束
<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd"> <!-- bean definitions here --> <bean id="userService" class="proxy.service.UserServiceImpl"/> </beans>
注册组件
我们要使用Spring容器管理组件
我们需要有对应的类,才能管理它的实例。
<!--注册组件 → UserServiceImpl--> <!--id属性:组件在容器中唯一标识--> <!--class属性:全限定类名 ctrl + alt + shift + c--> <bean id="userService" class="proxy.service.UserServiceImpl"/>
测试
@Test public void test1(){ ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext( "application.xml");//初始化容器 //获得对象,建议用接口接收,方便后期修改维护 UserService userService = (UserService) classPathXmlApplicationContext.getBean("userService"); userService.say(); }
取出组件的方式
getBean
//getBean(String) → 按照组件id取出组件 //getBean(Class) → 按照组件类型取出组件(该类型的组件只有一个) //applicationContext.getBean(UserService.class) //getBean(String,Class) → 按照组件id和类型
@Test public void mytest() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); //按照组件id取出 UserService userService1 = (UserService) applicationContext.getBean("userService"); //按照类型取出 UserService userService2 = applicationContext.getBean(UserService.class); //按照组件id和组件类型 UserService userService3 = applicationContext.getBean("userService", UserService.class); userService1.sayHello("world"); }
最好使用id+组件类型共同配合,这样可以避免类型强转出现的问题
当使用按照类型取出的时候,如果相同类型的组件不止一个,那么就会出现问题
<bean id="userService" class="com.cskaoyan.service.UserServiceImpl"/> <bean id="userService2" class="com.cskaoyan.service.UserServiceImpl2"/>
会出现以下的异常
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.cskaoyan.service.UserService’ available: expected single matching bean but found 2: userService,userService2
维护多个组件之间的关系
即,在一个组件中维护其他组件中的对象
<bean id="userService" class="com.cskaoyan.service.UserServiceImpl"> <!--name属性:给成员赋值,使用的是set方法赋值的--> <!--ref属性:引用了容器中的组件的id--> <property name="userDao" ref="userDao"/> </bean> <bean id="userDao" class="com.cskaoyan.dao.UserDaoImpl"/>
核心接口
容器接口:ApplicationContext、BeanFactory(ApplicationContext的父接口)
ApplicationContext:ClassPathXmlApplicationContext、AnnotationConfigApplicationContext、WebApplicationContext
Spring底层就是维护了一个HashMap
BeanFactory和FactoryBean之间的关系?
联系:都是工厂设计模式,都是向容器中注册组件
区别:BeanFactory注册的全部组件;FactoryBean注册的是特定的组件
- BeanFactory:负责生产和管理Bean的一个工厂接口,提供一个Spring Ioc容器规范,
- FactoryBean: 一种Bean创建的一种方式,对Bean的一种扩展。对于复杂的Bean对象初始化创建使用其可封装对象的创建细节。
Bean的实例化
构造方法
无参构造和有参构造皆可,默认使用的是无参构造,可以通过property来进行赋值
<!--默认使用的是无参构造--> <bean id="noParamConstructorBean" class="com.cskaoyan.bean.NoParamConstructorBean"> <property name="username" value="songge"/> </bean>
如果要使用有参构造,那么需要在子标签中写入构造方法的形参名
<bean id="hasParamConstructorBean" class="com.cskaoyan.bean.HasParamConstructorBean"> <!--name属性:有参构造方法的形参名--> <constructor-arg name="username" value="ligenli"/> </bean>
工厂
静态工厂和实例工厂
静态工厂注册:
<bean id="carFromStaticFactory" class="com.cskaoyan.factory.CarStaticFactory" factory-method="create"/>
指明调用静态工厂中的方法名即可,会自动调用并产生实例,使用factory-method标签
实例工厂注册:首先要用无参构造完成工厂的实例化,然后再调用实例工厂的工厂方法
<!--工厂的实例化 → 无参构造先完成工厂的实例化--> <bean id="instanceFactory" class="com.cskaoyan.factory.CarInstanceFactory"/>
<!--使用工厂实例调用生产方法:唯一一个没有使用class属性的bean标签--> <bean id="carFromInstanceFactory" factory-bean="instanceFactory" factory-method="create"/>
***FactoryBean
将方法的返回值作为容器中的组件
工厂方法:定义工厂接口,来实现工厂接口
public interface FactoryBean<T> { String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType"; //getObject是其核心方法 → 返回值作为容器中的组件 //类定义的泛型体现在返回值类型 @Nullable T getObject() throws Exception; //组件类型 @Nullable Class<?> getObjectType(); default boolean isSingleton() { return true; } }
在注册组件的时候有个命名的规则xxxFactoryBean,注册的组件就是xxx
/** * FactoryBean * XXXFactoryBean → 注册的组件就是XXX * @author stone * @date 2021/12/25 11:31 */ public class CarFactoryBean implements FactoryBean<Car> { @Override public Car getObject() throws Exception { Car car = new Car(); car.setSource("factory bean"); return car; } @Override public Class<?> getObjectType() { return Car.class; } }
组件注册
只需要将FactoryBean注册到容器中即可
<!--FactoryBean只需要注册到容器中--> <bean id="carFromFactoryBean" class="com.cskaoyan.factory.CarFactoryBean"/>
FactoryBean是Spring框架中非常特殊的一个接口,我们注册组件的时候,我们写的是其其实现类,而最终取出的组件是其getObject方法的返回值
Srping容器在取出bean的时候,会先判断这个bean有没有实现FactoryBean,如果有就返回getObject中的返回值,如果没有就返回bean
Scope作用域
singleton 单例:每一次取出来是同一个
prototype 原型:每一次取出来是一个新的 → 每一次取出组件的时候才会开始生命周期
配置作用域
<bean id="singletonBean" class="com.cskaoyan.scope.SingletonBean" scope="singleton"/> <bean id="prototypeBean" class="com.cskaoyan.scope.PrototypeBean" scope="prototype"/> <bean id="defaultBean" class="com.cskaoyan.scope.DefaultBean"/>
默认的作用域是单例,绝大多数情况下我们使用的也是单例
生命周期
组件从实例化开始到组件可用状态这个时间经过哪些方法,在容器关闭的时候也会影响
Scope对生命周期的影响:
1. Prototype组件:每一次取出组件的时候才会开始生命周期 → 懒加载,容器关闭不会执行方法 2. Singleton组件:容器初始化的时候就已经开始生命周期 → 立即加载,容器关闭会执行方法
组件在容器中默认以单例的形式存在,单例组件在容器初始化时开始生命周期,生命周期的起点是Bean的实例化
Aware接口
如果你需要Aware提供的值,才去实现对应的接口,才会执行到对应的set方法
使用的是set方法进行赋值,需要使用一个成员变量接收。
设置BeanName
public interface BeanNameAware extends Aware { void setBeanName(String var1); }
设置BeanFactory
public interface BeanFactoryAware extends Aware { void setBeanFactory(BeanFactory var1) throws BeansException; }
设置上下文
public interface ApplicationContextAware extends Aware { void setApplicationContext(ApplicationContext var1) throws BeansException; }
***BeanPostProcessor接口
BeanPostProcessor接口提供的方法是给容器中的所有的组件使用的,相当于是一个通用性的方法,容器中的所有组件都会执行到BeanPostProcessor的postProcessBeforeInitialization和postProcessAfternitialization方法
对所有的组件进行通用性的处理,也可以根据传入的参数来进行判断,对某一类或者某一个组件进行单独的处理
生效条件:首先定义一个类实现BeanPostProcessor接口,并且将其注册为容器中的组件。
针对于BeanPostProcessor而言,作用范围是:除了BeanPostProcessor这个组件之外,其他的所有组件
public interface BeanPostProcessor { /** * bean: 正在执行生命周期的组件 * beanName: 正在执行生命周期的组件id * 返回值:Object类型,需要注册到容器中的实例 */ @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
针对于返回值,如果返回值为null那么就返回原先的bean,如果不是null,就返回自定义的值并注册到组件中
个性化处理:
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("组件" + beanName +"的BeanPostProcessor的before方法"); //针对于容器中的某一类或某一个组件可以在这里做通用性的处理 // 能否找到所有的DataSource组件做处理 if (bean instanceof DataSource) { //个性化的处理 // 数据库的密码是123456 // 我这里可以获得配置文件中的abcdef // 把abcdef转换为你需要的123456 } // 我能否找到lifeCycleBean这个组件 if ("lifeCycleBean".equals(beanName)) { //原先的username是songge → 组件到达可用状态之前变成ligenli LifeCycleBean temp = (LifeCycleBean) bean; temp.setUsername("ligenli"); return temp; } // 传入的是委托类的实例,返回的是代理对象 return bean; }
主要关注的是通用性,在通用性里也可以自己写代码完成个性化的处理
before和after之间的过程
InitializingBean接口提供的afterPropertiesSet方法
@Override public void afterPropertiesSet() throws Exception { //在processor的before和after之间生效 System.out.println(beanName+":afterPropertiesSet方法"); }
自定义的init方法
public void myinit() { //需要使用init-method的标签告诉容器这是init方法 System.out.println("自定义init"); }
使用自定义的init中的方法的时候需要在配置文件中加上init-method属性,告知容器这是这个实例的init方法
<bean id="lifeCycleBean" class="ioc1.lifeCycle.LifeCycleBean" init-method="myinit" > <property name="username" value="zs"/> </bean>
注意
对于作用域是prototype的组件,每一次取出组件(getBean方法)的时候,才会开始生命周期,因为他是懒加载,这样就不用维护多余的内存空间。同时因为不是单例,那么每次加载的时候都是新的对象,也就会执行新的生命周期
容器关闭
容器在关闭的时候,会执行对应的方法,一般是为了方便资源的释放
一定要注意prototype类型的组件在容器关闭时不会执行对应的方法
DisposableBean提供的destroy方法
public interface DisposableBean { void destroy() throws Exception; }
自定义的destroy方法
public void myDestroy(){ System.out.println("自定义的destroy方法"); }
如果需要调用自定义的destroy方法,那么就需要在标签中指明该方法
<bean id="lifeCycleBean" class="ioc1.lifeCycle.LifeCycleBean" init-method="myinit" destroy-method="myDestroy"> <property name="username" value="zs"/> </bean>
如果自定义的destroy和DisposableBean提供的destroy方法同时都有,那么两个都会执行
这里要注意destroy方法必须要容器关闭才会执行,因此需要调用容器的close方法
applicationContext.close();
完整的生命周期的流程
1.实例化
2.参数设置–使用set方法–关联propert标签
3.如果组件实现了Aware接口,那么就会执行到Aware接口中提供的方法
4.执行BeanPostProcessor中的方法
4.1先执行beforeinit再执行afterinit
4.2如果在这中间有接口提供的或者自定义的init方法,那么就会执行
5.最后到达可用状态
**注解 **
组件注册功能的注解
在注册的时候,使用注解和全限定类名耦合起来,并通过配置文件配置一个包目录,Spring可以自动找到这个包下的所有包含注册功能注解的类
1.限定范围
2.注册功能注解
扫描包的配置文件
<!--扫描包目录:扫描的是当前包以及它的所有的子包--> <context:component-scan base-package="com.cskaoyan"/>
常用的注册组件的注解
@Component, 通用型 @Repository, 通常是dao层的组件 @Service, 通常是service层的组件 @Controller, SpringMVC阶段的 @RestController, SpringMVC阶段的 @ControllerAdvice, SpringMVC阶段的 @Configuration Spring的配置类使用的 → JavaConfig
通过注解注册的组件,组件的id是什么?
1.默认的id值是类名的首字母小写的形式
2.自行指定:使用注解的value属性来指定
//@Component //userServiceImpl → 组件id的默认值是类名的首字母小写的形式 //@Component(value = "userService")//使用注解的value属性指定 @Component("userService")//如果注解中只有value属性,‘value=’这一部分可以省略掉 public class UserServiceImpl implements UserService{ }
作用域
@Scope 直接写在类上,在value属性中直接写作用域即可,同样的,value属性可以省略,直接写值即可
@Scope("prototype") //value属性里直接写作用域就可以了 public class UserServiceImpl implements UserService{ }
生命周期中的init和destroy方法
@PostConstruct 表示的是init方法
@PreDestroy表示的是destroy方法
public class UserServiceImpl implements UserService{ @PostConstruct //init-method public void myinit() { System.out.println("自定义init方法"); } @PreDestroy //destroy-method public void mydestroy() { System.out.println("自定义destroy方法"); } }
注入功能的注解
组件的注入
在组件的成员变量中引入容器中已经注册的组件,维护组件之间的依赖关系
其实就是从容器中取出组件给成员变量赋值。取出组件的方式和getBean方法是类似的
1.按照组件类型取出
2.按照组件id取出
3.按照id和类型取出
@Autowired @Qualifier("userDaoImpl1") //按照类型和id引入 UserDao userDao1; @Resource(name = "userDaoImpl2") // 使用@Resource注解的name属性 UserDao userDao2; @Autowired //按照类型 OrderDao orderDao;
常用的方法是按照类型取出
值的注入
注入组件的成员变量:接收字符串、基本类型这样的一些值
可以使用@Value注解使用SpEL,需要首先加载properties配置文件 如果要加载classpath路径,需要进行指定
<context:property-placeholder location="classpath:param.properties"/>
接下来在使用的时候直接用${}的形式来引入key,就可以自动获取key中的value的值
@Value("${userservice.username}") //${}引入key → SpEL → Spring Expression Language String username;
这里要注意写key的时候一定要增加一个自定义的前缀名,这样可以更方便的分辨属性,并且也可以避免引入系统变量
注意事项
@Autowired、@Autowired+@Qualifier、@Resource、@Value
这些注解,要在容器中的组件才能使用,因为只有组件注册了,才可以取出
单元测试
Spring单元测试:方便从容器中取出实例
直接在单元测试类使用注入功能的注解来取出组件
在单元测试类中需要维护容器 → ApplicationContext → application.xml
单元测试类中会帮我们维护好容器
引入依赖
spring-test
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.15.RELEASE</version> <scope>test</scope> </dependency>
单元测试类上的注解
@Runwith(SpringJUnit4ClassRunner.class) 加载测试类的注解
@ContextConfiguration(“classpath:application.xml”) 加载配置文件的注解,一定要带上classpath
@RunWith(SpringJUnit4ClassRunner.class)//注解是junit提供的,class是spring-test @ContextConfiguration("classpath:application.xml") public class AnnotationTest { @Autowired OrderDao orderDao; @Autowired UserService userService; @Autowired ApplicationContext applicationContext; @Test public void mytest1() { userService.serviceMethod(); } }
ork
spring-test
5.2.15.RELEASE
test
单元测试类上的注解 @Runwith(SpringJUnit4ClassRunner.class) 加载测试类的注解 @ContextConfiguration("classpath:application.xml") 加载配置文件的注解,一定要带上classpath ```java @RunWith(SpringJUnit4ClassRunner.class)//注解是junit提供的,class是spring-test @ContextConfiguration("classpath:application.xml") public class AnnotationTest { @Autowired OrderDao orderDao; @Autowired UserService userService; @Autowired ApplicationContext applicationContext; @Test public void mytest1() { userService.serviceMethod(); } }