Java Spring
Spring
Spring notebook,代码见lijinhao’s github
- ioc
将对象交由ioc容器统一管理,对象和对象之间实现解耦
通过修改配置文件修改对象关系 - DI 依赖注入
由java反转实现
目录
- reflection 反射 reflection
- i18n 工厂模式实例 i18n
- S01 使用Spring
- S02 配置Bean
- 利用setter方法实现对象依赖注入 利用setter方法实现对象依赖注入
- book-shop ioc在实际项目中的重要用途(ref应用)
- s04 注入集合对象list、set、properties
- 高效函数 ApplicationContext对象的函数
- bean scope bean scope属性
- s05 bean单例模式和多例模式,生命周期
- s06手动实现极简的IoC容器
- s07基于注解配置IoC容器
- s07_1@Autowired自动装配注解
- s07-2@Resource按名称自动装配注解
- s07-3@Primary、@PostConstruct、@PreDestroy、@Scope、@Value
- s08Java Config配置IoC容器、Java Config对象依赖注入
- s09 Spring Test测试模块的用途
前置知识
工厂模式
学习要点
- 反射及其作用
- 反射的四个核心类
- 反射在项目中的作用
什么是反射
反射(reflection.reflect)是代码在运行时
动态访问类与对象的技术
- 来自于
java.lang.reflection.reflect
初学java时,要实例化一个对象,需要:这个步骤在程序编译时运行。//Object obj = new Object<>();
MathOperation mathOperation = null;
// 因为newInstance()返回的是一个object对象,因此可以强转为MathOperation对象
mathOperation = (MathOperation) Class.forName("reflection.reflect." + op).newInstance();
int result = mathOperation.operate(a, b);
反射的作用
大多数java框架都基于反射实现参数配置、动态注入等特性
反射核心类
方法 | 用途 |
---|---|
Class.forName() | 静态方法,用于获取指定Class对象 |
ClassObj.newInstance() | 通过默认构造方法创建新的对象 |
ClassObj.getConstructor() | 获得指定的public修饰构造方法Constructor对象 |
ClassObj.getMethod() | 获得指定的public修饰Method对象 |
ClassObj.getField() | 获得指定的public修饰成员变量Field对象 |
使用Spring
pom.xml文件
|
applicationContext.xml文件
- 添加bean
<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">
<!-- 在IOC容器启动时,自动由Spring实例化Apple对象,取名sweetApple放入到容器中 -->
<bean id="sweetApple" class="spring.ioc.Apple">
<property name="title" value="红富士"></property>
<property name="origin" value="European"></property>
<property name="color" value="red"></property>
</bean>
</beans>使用
public class SpringApplication {
public static void main(String[] args) {
// ApplicationContext接口唯一职责是初始化ioc容器并且实例化对象
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Child lily = context.getBean("lily", Child.class);
lily.eat();
Child andy = context.getBean("andy", Child.class);
andy.eat();
Child xiaomi = context.getBean("xiaomi", Child.class);
xiaomi.eat();
}
}
配置Bean
不同的配置方法
- 基于XML文件配置Bean
- 基于注解配置Bean
三种XML实例化Bean的配置方法
- 基于构造方法实例化对象
- 基于静态工厂实例化对象
- 基于工厂实例方法实例化对象
基于默认构造方法实例化对象
修改配置文件
<!-- bean标签默认通过默认构造方法来创建对象 -->
<bean id="apple1" class="com.spring.ioc.entity.Apple">
</bean>新建启动入口
public class SpringApplication {
public static void main(String[] args) {
// ApplicationContext接口的唯一职责就是初始化ioc容器并且实例化对象
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
}在Apple类的默认构造方法中输出
public class Apple {
public Apple() {
System.out.println("Apple对象已创建" + this);
}
}输出
Apple对象已创建com.spring.ioc.entity.Apple@35fb3008
总结
- 可见bean标签默认通过默认构造方法来创建对象
- 但是这样构造的对象没有参数传入,功能简陋
- 需要一个更好的创建对象方法
基于带参构造方法实例化对象
新增bean, 主要是
constructor-arg
参数<!-- bean标签通过带参构造方法来创建对象 -->
<bean id="apple2" class="com.spring.ioc.entity.Apple">
<constructor-arg name="title" value="红富士"/>
<constructor-arg name="color" value="red"/>
<constructor-arg name="origin" value="European"/>
</bean>其余同默认构造方法
输出
Apple对象通过带参构造方法已创建com.spring.ioc.entity.Apple@7cd62f43
利用setter方法实现对象依赖注入
public void setApple(Apple apple) { |
applicationContext.xml文件
<bean id="sweetApple" class="spring.ioc.Apple"> |
ref代表reference
<bean id="lily" class="spring.ioc.Child"> |
构造方法实现对象依赖注入
<bean id="apple2" class="com.spring.ioc.entity.Apple"> |
ioc项目代码中解耦合
ref应用案例: 书店后端的业务层和数据层解耦合
注入集合对象
基于java的list和set的特性,数据允许重复时使用list,不允许重复时使用set
- 实现功能类,实现注入需要用到setter方法
public class Company { |
修改applicationContext.xml配置文件
<bean id="company" class="com.spring.ioc.entity.Company">
<property name="rooms">
<list>
<value>2001-总裁办</value>
<value>2002-总经理办公室</value>
<value>2003-研发部会议室</value>
</list>
</property>
</bean>修改程序入口,实现注入
public class SpringApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Company company = context.getBean("company", Company.class);
System.out.println(company);
}
}输出
Company{rooms=[2001-总裁办, 2002-总经理办公室, 2003-研发部会议室], computers=null, info=null}
其他
- set方法
<bean id="company" class="com.spring.ioc.entity.Company">
<property name="rooms">
<set>
<value>2001-总裁办</value>
<value>2002-总经理办公室</value>
<value>2003-研发部会议室</value>
</set>
</property>
</bean>
public class Company { |
- map
<property name="computers"> |
高效函数
- getBeanDefinitionNames()获取bean的id名字,内部bean无法获取
String[] beanName = context.getBeanDefinitionNames();
for(String name: beanName){
System.out.println(name);
}
beanScope属性
详解
- bean scope属性用来决定对象何时被创建和作用范围
- bean scope配置将影响容器内对象的数量
- 默认情况下bean会在IoC容器创建后自动实例化,全局唯一
属性详解
单例模式 singleton
IOC容器初始化时,就会将对象创建
如果不写scope属性就代表每一个容器有且只有唯一实例,可以被全局共享,也就是在全局只要是同一个bean id都指向同一个
存在线程安全问题,setNumber、getNumber
多例模式 prototype
在获取bean(getBean函数调用)时创建对象
prototype代表允许存在多个实例,每次使用对象,IOC容器会自动创建一个新的实例
<bean id="bookDao" class="com.spring.ioc.bookshop.dao.BookDaoOracleImpl" scope="prototype"/> |
应知
【笔试题】
- 在IoC容器初始化的过程中实例化了多少个对象?
原理:
- IOC容器初始化时,就会将
单例模式 singleton
对象创建 - 在获取bean(getBean函数调用)时创建
多例模式 prototype
对象 - 如果单例模式对象
ref
引用了多例模式对象,会先创建多例模式对象,再创建单例模式对象
- 如何判断一个函数应该设置为单例还是多例?
如果该函数对象属性恒定不变,那么就用单例,如果经常变化,就是多例
<bean id="userDao" class="com.spring.ioc.dao.UserDao" scope="prototype"/> |
其他
属性 | 说明 |
---|---|
request | web环境下,每一次独立请求存在唯一实例 |
session | web环境下,每一个session存在唯一实例 |
application | web环境下,ServletContext存在唯一实例 |
websocket | 每一次WebSocket连接存在唯一实例(网络在线客服) |
bean生命周期
- 步骤:
- 创建对象
- 设置属性
- 调用init方法
- 通过调用对应的业务方法提供服务
- 释放资源
public class Order { |
调用init方法
<bean id="order1" class="com.spring.ioc.entity.Order" init-method="init" destroy-method="destroy">
<!-- 2. 设置属性-->
<property name="price" value="19.8"/>
<property name="quantity" value="1000"/>
</bean>释放资源
((ClassPathXmlApplicationContext) context).registerShutdownHook();
output:
创建Order对象,com.spring.ioc.entity.Order@754ba872 |
手动实现极简的IoC容器
可以将每一个ClassPathXmlApplicationContext理解为一个Ioc容器,用来存储对象的就是其中的Map
详情请看s06代码模块
基于注解配置IoC容器
在写代码的过程中完成配置
组件类型注解-声明当前类的功能与职责
自动装配注解-根据属性特征自动注入对象
元数据注解-更细化的辅助IoC容器管理对象的注解
注解 | 说明 |
---|---|
@Component | 组件注解/通用注解,被@Component注解的类将被IoC容器管理并且实例化 |
@Controller | 说明当前类是MVC应用中的控制类 |
@Service | 说明当前类是Service业务服务类 |
@Repository | 说明当前类用于业务持久层,通常描述Dao类 |
开启组件扫描
<!-- 只有XML配置开启组件扫描,才能使用注解 --> |
基于注解的IoC容器的扫描器跟基于XML的不一样,不需要用到<bean></bean>
,只需要添加<context:component-scan base-package="com.spring"/>
|
组件类型注解(例如@Repository)默认beanId为类首字母小写
beanid = userDao,也可以自己更改,如使用@Repository(“uDao”)
|
public class SpringApplication { |
output:
userController:com.spring.ioc.controlloer.UserController@73d4cc9e |
【问题】这几个实例时单例还是多例模式的?
答:必然是单例模式,因为只有单例模式的实例才会在IoC容器初始化的时候将对象创建
自动装配注解
分类 | 注解 | 说明 |
按类型装配 | @Autowired | 按容器内对象类型动态注入属性 |
@Inject | 同@Autowired,但不支持required属性 | |
按名称装配 | @Named | 与@Inject搭配使用,按属性名自动装配属性 |
@Resource | 有限按名称,再按类型智能匹配 |
- 什么是按类型装配/按名称装配?如上,在一个bean中使用另一个bean的名称来注入属性被称作按名称注入
<bean id="bookDao" class="com.spring.ioc.bookshop.dao.BookDaoImpl">
</bean>
<bean id="bookService" class="com.spring.ioc.bookshop.service.BookService">
<property name="bookDao" ref="bookDao"/>
</bean>
按类型注入则是不关心bean的名称,而是在为属性注入时,将属性类型相同的对象也完成注入
Autowired自动装配注解
public class UserService { |
public class UserService { |
装配注解写在set方法上和写在定义属性的位置都可以完成对象注入,但是过程机制是完全不一样的。
前者执行了set方法实现对象注入,后者不执行set方法,Spring IoC容器会自动通过反射技术将属性private修饰符改成public,直接进行赋值
问题
- 当存在两个类型相同的类时,注入失败代码会报错
- 出现原因
因为IUserDao类型的对象由两个:userDao和userOracleDao,IoC容器在初始化的过程中要将属性注入,但是不知道注入哪一个的对象的属性,因此报错。 - 解决办法
- 方法一:去除@Reposity,UserDao就不会被IoC容器管理
- 方法二:其中一个加上@Primary注解(Primary主要的)
public class UserOracleDao implements IUserDao{
public UserOracleDao() {
System.out.println("正在创建UserOracleDao:" + this);
}
}
更好的办法,是按名称装配
Resource按名称自动装配注解
具体测试见模块s07-2
|
元数据注解
代码见s07-3
| 注解 | 说明 |
| ————– | ———————————————————- |
| @Primary | 按类型装配时,出现多个相同类型对象,有@Primary的优先被注入 |
| @PostConstruct | 相当于XML里面的init-method |
| @PreDestroy | 相当于XML里面的destroy-method |
| @Scope | 设置对象的scope属性 |
| @Value | 为属性注入静态数据(配置文件常用) |
pom.xml添加:
<context:property-placeholder location="classpath:config.properties"/> |
config.properties:
metaData=spring.com |
UserService.java:
|
基于JavaConfig配置IoC容器
核心:使用Java代码替代XML完成IoC容器配置
- 完全摆脱XML的束缚,使用独立的Java类管理对象与依赖
- 注解配置相对分散,利用Java Config可对配置集中管理
- 可以在编译时进行依赖检查,不容易出错(XML运行后才会报错)
注解 | 说明 |
---|---|
@Configuration | 作用于类,说明当前类时Java Config配置类,完成替代XML文件 |
@Bean | 作用于方法,方法返回对象将被IoC容器管理,beanId默认为方法名 |
@ImportResource | 作用于类,加载静态文件,可食用@Value注解获取 |
@ComponentScan | 作用于类,同XML的context:compoment-scan标签 |
userController依赖于userService,userService依赖于userDao,能够完成依赖注入,
都仰仗于set方法,那么在基于Java Config中如何声明这些依赖呢?
|
UserService userService(UserDao userDao
)与UserDao userDao
= new UserDao()相对应
Spring发现UserService userService()中需要注入一个参数UserDao userDao,因为存在对应关系,自动完成注入
完整代码:
//当前类是一个配置类,用于替代applicationContext.xml |
output:
已创建ioc.dao.UserDao@1176dcec |
观察可以发现@1176dcec位置的UserDao,在创建UserService的对象userService时被调用,
@71248c21位置的UserService,在创建UserController时被调用
说明注入和创建的是同一个对象
以上创建是先按name尝试注入,name不存在时按类型注入
Java Config在敏捷开发如SpringBoot中常用,可以快速迭代
有利有弊
- Java Config的优点是配置集中在了一份代码里,还可以在编写java代码的过程中完成
- 缺点是重新配置需要重新编译
- XML则适合大型项目开发中
SpringTest测试模块
Spring Test对JUnit单元测试框架有良好的整合
通过Spring Test可以在JUnit在单元测试时自动初始化IoC容器
适用于需要多个测试用例的情景
Spring与JUnit4整合过程
Maven工程依赖spring-test
利用
@RunWith
与@ContextConfiguration
描述测试用例类- @RunWith表示将JUnit4交给Spring Test运行,由后者接管
- @ContextConfiguration表示使用什么配置文件,传入数据是一个数组
测试用例类从容器获取对象完成测试用例的执行
添加依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>SpringTestor代码:
// 将JUnit4的执行权交给Spring Test模块,在测试用例执行之前自动初始化IoC容器
public class SpringTestor {
/**
* 使用方法:在使用之前假设IoC容器已经初始化好,只不过是在写一个测试用的Spring Application
*/
private UserService userService;
public void testUserService(){
userService.createUser();
}
}
output: