Spring 基础准备
官网文档
中文翻译
建议学好英语,去看官方文档
所需要的依赖:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.8</version>
</dependency>
</dependencies>
Spring IOC
1 使用 spring 实现 bean 的大致流程
- 准备一个实体类
public class User {
public void add() {
System.out.println("hello Spring5");
}
}
- 配置 bean 文件,用来创建user对象
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 创建配置类对象 -->
<bean id="test" class="pojo.User"></bean>
</beans>
- 创建 Test 方法实现配置
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.User;
public class SpringTest {
@Test
public void TestTest() {
//1 加载spring配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("SpringConfig/TestBean.xml");
//2 获取配置创建的队形
User user = context.getBean(User.class);
user.add();
}
}
最后结果输出
2 IOC 的基本概念
控制反转(IoC)[1]原理的 Spring 框架实现。 IoC 也称为“依赖注入”(DI)。这是一个过程,在此过程中,对象仅通过构造函数参数,工厂方法的参数或在对象实例从工厂方法构造或返回后设置的属性来定义其依赖关系,即与它们一起使用的其他对象。然后,容器在创建 Bean 时“注入”那些依赖项。
此过程从根本上来说是相反的,因此名称为控件反转(IoC),它是通过使用类的直接构造或* Service Locator *模式之类的机制来控制其依赖项的实例化或位置的。
- 控制反转
- 对象创建和对象之间的调用过程,交给 Spring 进行管理。
- 使用 IOC 降低了耦合度
IOC的实现原理
xml 解析、工厂模式、反射
Spring 提供的 IOC 容器的两种实现方法:(两个接口)
- BeanFactory:IOC 容器的基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用
它的主要特点,加载配置时不会创建对象,在获取对象时才会创建,所以它慢 - ApplicationContext:(就是上面使用的方法)BeanFactory 接口的子接口,提供更多更强大的功能。
加载配置文件时就把对象创建好了,所以快。
IOC操作 Bean 管理
- Bean 管理,分为两步
- 创建对象
- 注入属性
实现方式
基于 xml 配置文件方式,工厂模式
<bean id="user" class="com.atguigu.spring5"></bean>
- 在 Spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
- id 属性:唯一标识
- class 属性:类全路径(相对于 src)
创建对象时默认执行无参的构造方法。因此创建类时一定要添加上无参构造器。
基于注解方式,注解调用反射
-
DI:依赖注入,就是注入属性。
- 通过 setter 方法注入
<bean id="book" class="com.atguigu.Book"> <!-- <property name="<属性名>" value="<预注入的值>"></property> --> <property name="bookName" value="易经"></property> <property name="author" value="鬼谷子"></property> </bean>
name
的值要和实体类的属性值对应。
value
就是自己准备输入的值
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); Book book = context.getBean("book", Book.class);
- 有参构造注入
- 创建类,属性,有参构造
<bean id="book" class="com.atguigu.Book">
<constructor-arg name="bookName" value="易经"></constructor-arg>
<constructor-arg name="author" value="鬼谷子"></constructor-arg>
</bean>
可以通过参数索引值赋值<constructor-arg index="索引值(从 1 开始)" value="<参数值>"></constructor-arg>
,但不常用。
name
对应参数名- value 对应准备赋给的值
P 名称空间注入(了解)
-
使用 p 名称空间注入,可以简化基于 xml 配置方式。
-
方法:修改 xml 的根标签的约束条件
第二步 在bean标签中进行属性注入
<bean id="book" class="pojo.Book" p:bname="九阳神功" p:bauthor="匿名">
赋值的特殊情况
- 设置空值
<property name="<属性名>">
<null/>
</property>
- 属性值包含特殊字符(对特殊值进行转译,和 html 一样)
<property name="<属性名>">
<value><!CDATA[<<南京>>]></value>
</property>
-
通过外部 bean 注入属性
- 创建两个类 service 类和 dao类
- 在 service 调用 dao 里面的方法
- 在 spring 配置文件中进行配置
<bean id="userService" class="service.UserService"> <property name="userDao" ref="userDaoImpl"></property> </bean> <!--被引入的外部bean--> <bean id="userDaoImpl" class="dao.UserDaoImpl"></bean>
其他都还是之前的套路。
-
内部 bean 注入属性(注入集合)
- 一对多关系:老师和学生,多对一:学生和老师
- 在实体类之间表示一对多就是,加入一个集合。
不同集合类型,都有一个对应的标签
list 集合
<bean id="emp" class="pojo.Teacher"> <properties name="ename" vlaue="凯老师"></properties> <properties name="Students"> <list> <value>张三</value> <value>李四</value> <!--注入外部bean--> <ref bean="zhansan"/> <ref bean="lisi"/> </list> </properties> </bean>
数组
<bean id="emp" class="pojo.Teacher"> <properties name="ename" vlaue="凯老师"></properties> <properties name="Students"> <array> <value>张三</value> <value>李四</value> </array> </properties> </bean>
map 集合
<bean id="emp" class="pojo.Teacher"> <properties name="ename" vlaue="凯老师"></properties> <properties name="Students"> <map> <entry key="1" value="张三"/> <entry key="2" value="李四"/> </map> </properties> </bean>
set 集合
<bean id="emp" class="pojo.Teacher"> <properties name="ename" vlaue="凯老师"></properties> <properties name="Students"> <array> <value>张三</value> <value>李四</value> </array> </properties> </bean>
-
名称空间引入
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <!--抽出自己需要的部分并修改,这里配置util--> xmlns:p="http://www.springframework.org/schema/p" 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 http://www.springframework.org/schema/util/spring-util.xsd"> <!--使用 util 进行集合的注入--> <util:list id="booklist"> <value>易筋经</value> <value>九阴真经</value> <value>九阳神功</value> </util:list> <bean id="book" class="com.atguigu.spring5.collectiontype.Book"> <property name="list" ref="bookList"></property> </bean> </beans>
方便集合的重复利用
FactoryBean实现 bean 管理
-
Spring 有两种 bean 类型,一种普通的 bean ,另种就是工厂 bean(FactoryBean)。
-
普通bean:在配置文件中定义 bean 类型就是返回值类型
-
FactoryBean :在配置文件中定义 bean 类型可以和返回值类型不一样。
- 创建类,让这个类作为工厂 bean 类,实现接口 FactoryBean
public class Cuser { private String name; private String ID; public Course() { super(); } public Course(String name, String iD) { super(); this.name = name; ID = iD; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getID() { return ID; } public void setID(String iD) { ID = iD; } @Override public String toString() { return "Course [name=" + name + ", ID=" + ID + "]"; } }
- 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
public class MyBean implements FactoryBean<Course>{ @Override public Course getObject() throws Exception { Course course = new Course(); course.setName("小明"); return course; } @Override public Class<?> getObjectType() { return null; } }
- 配置文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"> //这里使用的是MyBean返回值是Cuser,就这点区别 <bean id="myBean" class="bean.MyBean"></bean> </beans>
- 测试代码
@Test public void MyBeanTest() { ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); Cuser course = context.getBean("myBean", Cuser.class); System.out.println(course); }
bean 作用域
- 在Spring中,设置bean是单实例,还是多实例
- 在 Spring 中默认情况下,bean是单实例对象,通过相同的bean创建出来的对象的地址值都是一样的。
@Test
public void TestTest() {
//1 加载spring配置文件
ApplicationContext context =
new ClassPathXmlApplicationContext("SpringConfig/TestBean.xml");
//2 获取配置创建的队形
User user = context.getBean(User.class);
User user2 = context.getBean(User.class);
System.out.println(user);
System.out.println(user2);
}
//两个User对象的地址相同。
pojo.User@12aba8be
pojo.User@12aba8be
- 设置使用单实例还是多实例,可以使用bean标签中的
scope
属性- 默认是
singleton
单实例 - prototype,表示多实例
- 默认是
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 创建配置类对象 -->
<bean id="test" class="pojo.User" scope="prototype"></bean>
</beans>
//结果两个对象的地址不同
pojo.User@66f57048
pojo.User@550dbc7a
两个设置的不同之处,也就是作用域的体现
- 单实例,加载配置文件时就创建单实例对象。
- 多实例,在调用 getBean方法时多实例对象。
bean 生命周期
- 通过无参构造器创建 bean 实例
- 调用 setter 方法为 bean 注入属性值和对其他bean的应用
- 调用 bean 的初始化方法(需要进行配置初始化的方法)
- 获取 bean 对象
- 当容器关闭时,销毁 bean
测试:
package pojo;
public class BeanLive {
private String name;
public BeanLive() {
System.out.println("调用无参构造器");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("调用 setter 方法");
}
public void initMethod() {
System.out.println("调用初始化方法");
}
public void destoryMethod() {
System.out.println("销毁方法");
}
@Override
public String toString() {
return "BeanLive [name=" + name + "]";
}
}
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="live" class="pojo.BeanLive"
init-method="initMethod" destroy-method="destoryMethod">
<property name="name" value="值"></property>
</bean>
</beans>
@Test
public void BeanLiveTest() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("bean2.xml");
BeanLive live = context.getBean("live", BeanLive.class);
System.out.println("bean 实例创建成功");
System.out.println(live);
context.close();
}
结果
调用无参构造器
调用 setter 方法
调用初始化方法
bean 实例创建成功
BeanLive [name=值]
销毁方法
bean后置处理器的生命周期
一共有七步:
- 通过无参构造器创建对象
- 调用 setter 方法为属性复制
- 将 bean 实例通过
postProcessBeforeInitialization
传递给 bean 的后置处理器 - 调用 bean 的初始化方法,也要配置
- 将 bean 实例通过
postProcessAfterInitialization
传递给 bean 的后置处理器 - 获得bean对象,
- 关闭容器时,调用bean的销毁方法
这是继续上面的实验,每个 bean 实例都会有后置处理器,只是之前的后置处理器没有输出功能。
创建后置处理器
package bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPost implements BeanPostProcessor{
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前执行了");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后执行了");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
<!-- 配置后置处理器 -->
<bean id="beanPost" class="bean.MyBeanPost"></bean>
结果:
调用无参构造器
调用 setter 方法
在初始化之前执行了
调用初始化方法
在初始化之后执行了
bean 实例创建成功
BeanLive [name=值]
销毁方法
xml的自动装配
- 概念:通过指定装配规则,以及属性值,Spring自动将匹配的属性值进行注入。
过程演示
根据属性名称自动注入
package pojo;
public class User {
private String name;
//记住这个属性名
private Book book;
public User() {
super();
}
public User(String name, Book book) {
super();
this.name = name;
this.book = book;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
@Override
public String toString() {
return "User [name=" + name + ", book=" + book + "]";
}
}
配置文件进行注入
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 实现自动装配
bean标签属性autowire,配置自动装配
autowire的常用属性值:
byName根据属性名称注入,这里只能时注入其他bean,而且要保证被注入的bean的id值和注入到的bean的属性名相同
byType根据属性的类型注入 -->
<bean id="user" class="pojo.User" autowire="byName"></bean>
<!--这里的id值要和上面的属性值对应-->
<bean id="book" class="pojo.Book">
<property name="name" value="九阳真经"></property>
</bean>
</beans>
测试:
@Test
public void autoEntryTest() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean3.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
}
结果
User [name=null, book=Book [name=九阳真经]]
根据类型注入不用保证 id 和 Field 一样,但要确保xml配置文件中只有一个,该属性的bean,具体过程不再演示
获得数据库连接及引入外部属性文件
- 获得数据库连接池,普通方法
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="*******"></property>
</bean>
- 引入properties配置文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=xxxxxx
添加
xmlns:context="http://www.springframework.org/schema/context"
并在xsi:schemaLocation
中加入
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
使用:context:property-placeholder
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
完整的bean4.xml
<?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:p="http://www.springframework.org/schema/p"
xmlns:util="http://www.springframework.org/schema/util"
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/util http://www.springframework.org/schema/util/spring-util.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="${prop.driverClass}"></property>
<property name="url" value="${prop.url}"></property>
<property name="username" value="${prop.userName}"></property>
<property name="password" value="${prop.password}"></property>
</bean>
</beans>
使用方法
@Test
public void Test() {
ApplicationContext context =
new ClassPathXmlApplicationContext("bean4.xml");
User user = context.getBean("dataSource", DruidDataSource.class);
System.out.println(user);
}
基于注解方式实现对象创建
use-default-filters="false"
默认使用,spring 自带的扫描规则,赋值为 false 表示使用我们自己配置的扫描规则。filter 表示的不是监听,表示的是扫描规则。
扫描规则就是筛选扫描或不扫描的类的注解类型。
针对创建对象使用的注解,四个注解功能相似
@Component
@Service
@Controller
@Repository
基于注解方法实现对象的创建
- 引入依赖:
spring-aop-xxxx.jar
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.9</version>
</dependency>
- 开启组件扫描
<!--开启组件扫描
1 如果扫描多个包,多个包使用逗号隔开
2 扫描包上层目录
-->
<context:component-scan base-package="pojo,dao,bean"></context:component-scan>
- 创建类,并添加响应的注解
//value属性值如果不写,就是默认的类名首字母小写
@Component(value="userService1");
public class UserService{
public void printAdd(){
System.out.println("service add.....");
}
}
- 开启组件扫描的配置
<!--示例1
use-default-filters="false"表示不使用默认的筛选,自己配置filter
context:include-filter,设置扫描哪些内容
-->
<context:component-scan base-package="." use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--示例2
context:exclude-filter,设置扫描哪些内容不进行扫描
-->
<context:component-scan base-package=".">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
实现方法万年不变,(不一定有万年,但不变是真的)
-
注解方式实现属性自动注入
@Autowired
:根据属性类型进行自动创建- 把 service 对象和dao对象创建,在service和dao类添加创建对象的注解。
- 把 service 注入 dao 对象,在 service 类添加到类型属性,在属性上面使用注解
import org.springframework.stereotype.Repository; @Repository(value = "userDaoImpl1") //一个bean对象命名为userDaoImpl1 public class UserDaoImpl implements UserDao { @Override public void add() { System.out.println("dao add....."); } }
@Service //表示这个类是一个 service 类型,没有指定value就默认为 userService public class UserService { //定义 dao 类型属性 //不需要添加 set 方法 //添加注入属性注解 @Autowired private UserDao userDao; public void add() { System.out.println("service add......."); userDao.add(); } }
在加入配置文件
<context:component-scan base-package="." use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
-
其他注入方法
@Qualifier
根据名称进行注入,这里就体现出名称的重要了。
@Autowired //根据类型注入 @Qualifier(value="userDaoImpl1")//根据名称进行注入 private UserDao userDao;
@Resource
:没有 vlaue 属性根据类型进行注入,有根据名称注入。
@Resource //根据类型注入 @Resource(value="userDaoImpl1")//根据名称进行注入 private UserDao userDao;
Value
为普通类型注入值
@Value(value="小明") private String name; //扫描一下就调用了。
-
完全注解开发,就是把配置文件也写成 java 类,配置类
@Configuiation //配置类注解 @ComponentScan(basePackages={"<扫描的包>"}) public class SpringConfig{}
- 进行测试
@Test public void testService{ //加载配置类 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService service = context.getBean("userService", UserService.class); }
**注意:**这里使用的是
AnnotationConfigApplicationContext
使用注解编程和使用xml编程的思想差不多,不同的只是实现方法。