随笔分类
操作 Bean管理
Bean管理涉及两个操作:
- Spring 创建对象
- Spring 注入属性
对于 Bean的管理有两个实现方式
基于 xml
基于 xml创建对象
<!-- 配置User对象的创建 id:起名 class:全类路径 -->
<bean id="user" class="demo.User"></bean>
bean标签中常用属性
- id:bean唯一标识(id中不可以有符号)
- class:类的全路径
- name:可以来标识 bean,可以和 id同时存在,此时 name作为别名(name中可以有符号)
创建对象时,默认去执行的是类的无参构造方法
基于xml实现属性注入
DI:Dependency Injection,依赖注入,基于创建好的对象
spring支持两个方式的依赖注入:有参构造、set方法
set注入
创建类,定义属性和对应的 set方法
<!-- 配置User对象的创建 id:起名 class:全类路径 -->
<bean id="user" class="demo.User">
<!-- 注入属性 -->
<property name="userName" value="liangye"/>
</bean>
private String userName;
public void setUserName(String userName) {
this.userName = userName;
}
可见,当属性特别多时,这么写实际上是繁杂的
这是可以使用表增加 表空间 p的方式进行相关优化
xmlns:p="http://www.springframework.org/schema/p"
...
<bean id = "user" class="demo.User" p:userName="liangye" p:age="18"/>
- 属性注入空值的情况
<property name="age">
<null></null>
</property>
-
属性注入包含特殊符号
可以使用 html转义表来进行特殊符号的替代
使用 xml语法 CDATA来避免过滤字符数据
>]]>
有参构造
创建类(基于有参构造),注入属性
<bean id="user" class="demo.User">
<!-- 使用有参构造 -->
<constructor-arg name="userName" value="luoye"/>
</bean>
private String userName;
// 有参构造
public User(String userName) {
this.userName = userName;
}
自动装配
xml 自动装配
大多数情况下,使用的都是基于注解的自动装配,但基于 xml的自动装配还是值得学习的.
之前讲的实际上都是手动装配的一个过程
使用 bean标签的 autowire属性来实现自动装配
- 根据指定装配规则(属性名称或属性类型),Spring自动将匹配到的属性值进行注入
- byName:根据 属性名称进行注入,注入值 bean的 id值和类属性名称一样
- byType:根据 属性类型进行注入,存在 bean创建对象的类型和属性类型一致即可
- 对于 byType而言,相同类型的 bean不能创建多个
注入场景
(1)、外部 bean
存在 service层和 dao层,在 service层的类中调用 dao层中类的方法
/**
* 在此类中调用 UserDao实现类中的方法
*/
public class UserService {
// 创建UserDao类型属性,生成set方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("userService add...");
// 创建 UserDao的实现类
// // 原始方式
// UserDao userDao = new UserDaoImpl();
// userDao.update();
// 基于 Spring的xml方式 --> 标签中创建的是接口实现类的对象
userDao.update();
}
}
public interface UserDao {
void update();
}
public class UserDaoImpl implements UserDao{
@Override
public void update() {
System.out.println("UserDao的实现类中的update方法被调用了");
}
}
UserBean.xml
<bean id="userService" class="demo.service.UserService">
<!-- 注入属性 属性是对象的话需要使用ref,表示引用对象创建,指向的是属性对应对象的创建的bean的标识 -->
<property name="userDao" ref="userDao"></property>
</bean>
<!-- 注意要创建的是接口的实现类的对象 -->
<bean id="userDao" class="demo.dao.UserDaoImpl"></bean>
测试:
/**
* 测试外部bean的注入
* 运行结果:
* userService add...
* UserDao的实现类中的update方法被调用了
*/
@Test
public void testOutClass() {
ApplicationContext context = new ClassPathXmlApplicationContext("UserBean.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
(2)、内部 bean以及级联赋值
此时 先不去使用 ref的写法,有专门的内部 bean的写法 -> property里嵌套着 bean对象的创建
类与类中不仅仅存在一对一关系
典型的一对多关系:部门和员工
- 一个部分有多个员工,一个员工仅属于某个部门
- 即在实体类中表示的是一对多的关系
/**
* 部门
*/
public class Dept {
@Override
public String toString() {
return "Dept{" +
"dName='" + dName + '\'' +
'}';
}
private String dName;
public void setdName(String dName) {
this.dName = dName;
}
}
/**
* 员工
*/
public class Employee {
private String Ename;
public void setEname(String ename) {
Ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Employee{" +
"Ename='" + Ename + '\'' +
", gender='" + gender + '\'' +
", dept=" + dept +
'}';
}
public void setDept(Dept dept) {
this.dept = dept;
}
private String gender;
// 关联
private Dept dept;
}
UserBean.xml
<!-- 内部bean:员工和部门 不使用ref-->
<bean id="employee" class="demo.bean.Employee">
<property name="ename" value="liangye"/>
<property name="gender" value="male"/>
<!-- 内部bean -->
<property name="dept">
<bean id="dept" class="demo.bean.Dept">
<property name="dName" value="Java后端开发"/>
</bean>
</property>
</bean>
测试:
/**
* 测试内部bean
* 运行结果:
* Employee{Ename='liangye', gender='male', dept=Dept{dName='Java后端开发'}}
*/
@Test
public void testInnerBean() {
ApplicationContext context = new ClassPathXmlApplicationContext("UserBean.xml");
Employee employee = context.getBean("employee", Employee.class);
System.out.println(employee);
}
其实级联赋值也仅仅是内部 bean的另外几种写法
此时的级联赋值使用 ref
仅仅修改下配置文件即可
<!-- 使用 ref -->
<bean id="employee" class="demo.bean.Employee">
<property name="ename" value="liangye"/>
<property name="gender" value="male"/>
<property name="dept" ref="dept"/>
</bean>
<bean id="dept" class="demo.bean.Dept">
<property name="dName" value="Java后端开发"/>
</bean>
此时还可以使用表达式的方法去赋值内部对象的属性值
前置条件:要在 Employee中添加 dept的 get(),基于此进行的修改
<!-- 使用 ref -->
<bean id="employee" class="demo.bean.Employee">
<property name="ename" value="liangye"/>
<property name="gender" value="male"/>
<property name="dept" ref="dept"/>
<property name="dept.dName" value="Java开发部门"></property>
</bean>
<bean id="dept" class="demo.bean.Dept">
<property name="dName" value="Java后端开发"/>
</bean>
</beans>
其它操作
注入数组、集合等其它类型属性
package demo.collectionsType;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Student {
// 数组类型属性
private String[] courseStr;
// List集合类型属性
private List<String> list;
// Map集合类型属性
private Map<String, String> map;
// Set集合类型属性
private Set<String> sets;
// 一个学生可以选修多门课
private List<Course> courses;
public void setCourses(List<Course> courses) {
this.courses = courses;
}
@Override
public String toString() {
return "Student{" +
"courseStr=" + Arrays.toString(courseStr) +
", list=" + list +
", map=" + map +
", sets=" + sets +
", courses=" + courses +
'}';
}
public void setCourseStr(String[] courseStr) {
this.courseStr = courseStr;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public void setSets(Set<String> sets) {
this.sets = sets;
}
}
package demo.collectionsType;
public class Course {
private String cName;
@Override
public String toString() {
return "Course{" +
"cName='" + cName + '\'' +
'}';
}
public void setcName(String cName) {
this.cName = cName;
}
}
bean1.xml
<bean id="student" class="demo.collectionsType.Student">
<!-- 注入数组类型属性 -->
<property name="courseStr">
<array>
<value>"软件工程"</value>
<value>"操作系统"</value>
</array>
</property>
<!-- 注入List类型元素 -->
<property name="list">
<list>
<value>"list1"</value>
<value>"list2"</value>
</list>
</property>
<!-- 注入map.. -->
<property name="map">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="MAP" value="map"></entry>
</map>
</property>
<!-- 注入set -->
<property name="sets">
<set>
<value>"java"</value>
<value>"golang"</value>
</set>
</property>
<!-- 注入list集合属性 值是对象 -->
<property name="courses">
<list>
<ref bean="course"></ref>
<ref bean="course2"></ref>
<ref bean="course3"></ref>
</list>
</property>
</bean>
<!-- 创建多个Course的对象 -->
<bean id="course" class="demo.collectionsType.Course">
<property name="cName" value="计算机"></property>
</bean>
<bean id="course2" class="demo.collectionsType.Course">
<property name="cName" value="世界"></property>
</bean>
<bean id="course3" class="demo.collectionsType.Course">
<property name="cName" value="望远镜"></property>
</bean>
测试:
/**
* 运行结果:
* Student{courseStr=["软件工程", "操作系统"], list=["list1", "list2"], map={JAVA=java, MAP=map}, sets=["java", "golang"], courses=[Course{cName='计算机'}, Course{cName='世界'}, Course{cName='望远镜'}]}
*/
@Test
public void testForStudent() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student);
}
抽取注入类型属性
使用名称空间 util --> 首先需要引入 util
public class Book {
private List<String> list;
@Override
public String toString() {
return "Book{" +
"list=" + list +
'}';
}
public void setList(List<String> list) {
this.list = list;
}
}
bean2.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: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 -->
<util:list id="bookList">
<value>并发编程实战</value>
<value>Head of Java</value>
</util:list>
<bean id="book" class="demo.collectionsType.Book">
<!--使用ref来关联抽取出来的部分 -->
<property name="list" ref="bookList"/>
</bean>
</beans>
测试
/**
* 运行结果:
* Book{list=[并发编程实战, Head of Java]}
*/
@Test
public void testForPublicBean() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
Book book = context.getBean("book", Book.class);
System.out.println(book);
}
bean分类
在 Spring中存在两种 bean,一种是普通 bean(自定义的 bean),还有一种是 FactoryBean
普通 bean:配置文件里定义的类型和返回来的类型是 一样的
工厂 bean:配置文件里定义的类型和返回来的类型 可以不一致
- 创建一个实现接口 FactoryBean的类:此类将作为 FactoryBean
- 实现接口里面的方法,在实现的方法中定义返回的 bean类型
bean作用域
在 spring中,可以设置创建的 bean实例是单实例还是多实例
默认情况下,bean是被设置成 单例模式的
如何来设置成别的模式呢?
在 Spring配置文件 bean标签里面可以设置单例还是多例:属性 scope
- singleton:默认值,表示单实例模式,使用的是 饿汉式创建对象(加载配置文件时)
- prototype:多实例,使用的延迟创建思想,实际调用时才会去创建对象
bean生命周期
从对象创建到销毁的过程
- 无参构造 bean实例
- 为 bean的属性设置值和对其它 bean的引用
- 调用 bean的初始化方法(需要进行配置初始化)
- bean实例可以被使用了(对象获取到了)
- 当容器关闭时,调用 bean的销毁的方法(需要进行配置销毁的方法)
如果有设置 bean的 后置处理器的话,实际上 bean的生命周期是有 7个 state的,在执行 bean初始化方法之前( postProcessorBeforeInitialization)和之后(postProccessorAfterInitialiation),都需要将 bean传递给后置处理器
/**
* 代码演示 bean的生命周期
* 再有配置后置处理器的情况下
*/
public class Order {
private String oName;
public Order() {
System.out.println("① bean的无参构造被调用了");
}
public void setoName(String oName) {
System.out.println("② 设置 bean的属性");
this.oName = oName;
}
// 设置bean的初始化方法
public void initMenthod() {
System.out.println("③ bean的初始化方法被执行了");
}
// 设置bean的销毁方法
public void destoryMethod() {
System.out.println("⑤ bean的销毁方法被执行了");
}
}
/**
* bean的后置处理器
*/
public class MyProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之前");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之后");
return null;
}
}
bean3.xml
<bean id="order" class="demo.bean.Order" init-method="initMenthod" destroy-method="destoryMethod">
<property name="oName" value="Mac Book Pro"></property>
</bean>
<!-- 后置处理器会对当前所有的bean都起到作用 -->
<bean id="myBeanProcessor" class="demo.processor.MyProcessor"></bean>
测试:
/**
* ① bean的无参构造被调用了
* ② 设置 bean的属性
* 初始化之前
* ③ bean的初始化方法被执行了
* 初始化之后
* ④ 对象可以被使用了
* ⑤ bean的销毁方法被执行了
*
*/
@Test
public void testLifeForBean() {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
Order order = context.getBean("order", Order.class);
if (order != null) {
System.out.println("④ 对象可以被使用了");
}
// 手动去销毁bean --> 必须是 AbstractAppicationContext或其子类
context.close();
}
基于注解
注解是代码特殊标记
使得 xml配置更加简洁、更加优雅
Spring针对 bean管理中创建对象提供的注解
- @Component
- @Service
- @Controller
- @Repository
需要在 xml文件中 开启组件扫描(指明扫描的是哪个包下的内容,如果没有开启组件扫描,Spring无法知道去哪里扫描注解):增加名称空间 context,和 util的使用类似
创建对象
创建类,在类上面添加创建对象的注解
<?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: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">
<!-- 开启组件扫描
如果要扫描多个包:1.多个包使用逗号相隔
2.使用多个包的上层目录
默认filter会对包中所有类的所有内容进行扫描,这往往并不是我们想要的
use-default-filters -> false
context:include-filter:设置要扫描的内容
context:exclude-filter:设置不去扫描的内容
-->
<context:component-scan base-package="./"></context:component-scan>
<!-- 自定义 filter -->
<context:component-scan base-package="./" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>
</beans>
// 注解里的value可以不写,默认是类名称,首字母小写
@Component(value = "userService") // 等价于 <bean id="userService class="..">
public class UserService {
public void add() {
System.out.println("userService add ...");
}
}
测试
/**
* 运行结果:
* userService add ...
*/
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
注入属性
Spring提供了 4个常用注解来进行依赖注入
基于注解的属性注入便无需再去添加 set方法了
- @Autowired:根据属性类型进行注入(byType)
- 当一个接口有多个实现类时,便不能根据属性类型来进行自动注入了
- 此时需要根据属性名称(bean标识,需要在相关注解中指定 value的值)来进行区分不同的实现类
- @Qualifier:根据属性名称进行注入(byName)
- @Qualifier需要和 @Autowire配合使用
- @Resource:可以根据属性注入,也可以根据名称注入
- @Resource是 Javax(Java Extension)扩展包中的注解,Spring官方更建议我们去使用的 @Autowire 以及 @Qualifier
- @Value:注入普通类型属性
// @Autowired // 根据类型注入
// @Autowired
// @Qualifier("userDaoImpl") // 根据名称进行注入
@Resource // 兼容,但不建议使用
private UserDao userDao;
@Override
public String toString() {
return "UserService{" +
"userDao=" + userDao +
", serviceName='" + serviceName + '\'' +
'}';
}
// 注入普通属性
@Value(value = "Java")
private String serviceName;
完全注解开发
注解目的:简化 xml配置
怎么做到无 xml呢? 配置类即可
这是原来的 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: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">
<!-- 开启组件扫描
如果要扫描多个包:1.多个包使用逗号相隔
2.使用多个包的上层目录
默认filter会对包中所有类的所有内容进行扫描,这往往并不是我们想要的
use-default-filters -> false
context:include-filter:设置要扫描的内容
context:exclude-filter:设置不去扫描的内容
-->
<context:component-scan base-package="./"></context:component-scan>
<!-- <!– 自定义 filter –>-->
<!-- <context:component-scan base-package="./" use-default-filters="false">-->
<!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>-->
<!-- </context:component-scan>-->
</beans>
对应的配置类
@Configuration用于定义配置类,可 替换 xml配置文件,被注解的类内部可以包含多个 @Bean注解的方法或者自定义了 bean扫描路径,这些都会被 AnnotationConfigApplicationContext或者 AnnotationWebApplicationContext类进行加载扫描,用于构建 bean定义,初始化 Spring容器
@Configuration // 作为配置类,来替代 xml配置文件 其实就是相当于 <beans ...
@ComponentScan(basePackages = "./")
public class XmlReplaceConfig {
}
测试
/**
* 完全注解开发
* UserService{userDao=dao.UserDaoImpl@2d0399f4, serviceName='Java'}
* userService add ...
* UserDaoImpl upgrade ...
*/
@Test
public void test2() {
// 加载配置类
ApplicationContext context = new AnnotationConfigApplicationContext(XmlReplaceConfig.class);
UserService userService = context.getBean("userService", UserService.class);
System.out.println(userService);
userService.add();
}
对于完全注解开发,在实际开发中使用最多的便是 SpringBoot了,简化版的 Spring,这个配置类其实就相当于 SpringBoot的启动配置类,SpringBoot项目启动时默认便会去加载这个配置类 可见 SpringApplication.run...,但需要明白一点:SpringBoot 的启动配置类中包含的内容不仅仅是加载配置类这么简单!