目录
四、Spring对IoC的实现
4.1IoC控制反转
4.2依赖注入
4.2.1set注入
4.2.2构造注入
4.3set注入专题
4.3.1注入外部Bean
4.3.2注入内部Bean
4.3.3注入简单类型
4.3.4级联属性赋值
4.3.5注入数组
4.3.6注入List、Set、Map集合
4.3.7注入Properties
4.3.8注入null和空字符串
4.3.9注入的值中含有特殊符号
4.4p命名空间注入
4.5c命名空间注入
4.6util命名空间
4.7基于XML的自动装配
4.7.1根据名称自动装配
4.7.2根据类型自动装配
4.8Spring引入外部属性配置文件
控制反转是一种思想。
控制反转是为了降低程序耦合度,提高程序扩展力,达到OCP原则、DIP原则。
控制反转,反转的是什么?
将对象的创建权交出去,交给第三方容器负责。
将对象和对象之间的关系维护权交出去,交给第三方容器负责。
控制反转这种思想如何实现?
DI(Dependency Injection):依赖注入
依赖注入实现了控制反转的思想。
Spring通过依赖注入的方式来完成Bean的管理。
Bean的管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。
依赖注入:
依赖指的是对象和对象之间的关联关系。
注入指的是一种数据传递行为,通过注入行为来让对象和对象产生关系。
依赖注入常见的实现方式包括两种:
第一种:set注入。
第二种:构造注入。
set注入,基于set方法实现的,底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种机制要求属性必须对外提供set方法。
UserDao
package com.hhb.dao;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert() {
//使用log4j2日志框架
logger.info("数据库正在保存用户信息");
}
}
UserService
package com.hhb.service;
import com.hhb.dao.UserDao;
import com.hhb.dao.VipDao;
public class UserService {
private UserDao userDao;
//set注入的话,必须提供一个set方法
//Spring容器会调用这个set方法来给userDao属性赋值。
//set方法必须以set单词开头
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void saveUser() {
//保存用户信息到数据库
userDao.insert();
vipDao.insert();
}
}
spring.xml
测试
@Test
public void testSetDI(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userServiceBean = applicationContext.getBean("userServiceBean", UserService.class);
userServiceBean.saveUser();
}
实现原理:
通过property标签获取到属性名:userDao
通过属性名推断出set方法名:setUserDao
通过反射机制调用setUserDao()方法给属性赋值
property标签的name是属性名
property标签的ref是要注入的bean对象的id。(通过ref属性来完成bean的装配,这是bean最简单的一种装配方式。装配指的是:创建系统组件之间关联的动作)
set注入的核心实现原理:通过反射机制调用set方法来给属性赋值,让两个对象之间产生关系。
核心原理:通过调用构造方法来给属性赋值。
UserDao
package com.hhb.dao;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert() {
//使用log4j2日志框架
logger.info("数据库正在保存用户信息");
}
}
VipDao
package com.hhb.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VipDao {
private static final Logger logger=LoggerFactory.getLogger(VipDao.class);
public void insert(){
logger.info("正在保存VIP信息");
}
}
CustomerService
package com.hhb.service;
import com.hhb.dao.UserDao;
import com.hhb.dao.VipDao;
public class CustomerService {
private UserDao userDao;
private VipDao vipDao;
public CustomerService(UserDao userDao, VipDao vipDao) {
this.userDao = userDao;
this.vipDao = vipDao;
}
public void save() {
userDao.insert();
vipDao.insert();
}
}
beans.xml
测试
@Test
public void testConstructDI() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
CustomerService csBean = applicationContext.getBean("csBean", CustomerService.class);
csBean.save();
CustomerService csBean2 = applicationContext.getBean("csBean2", CustomerService.class);
csBean2.save();
CustomerService csBean3 = applicationContext.getBean("csBean3", CustomerService.class);
csBean3.save();
}
通过测试得知,通过构造方法注入的时候:
可以通过下标
可以通过参数名
也可以不指定下标和参数名,可以类型自动推断
set-di.xml
外部Bean的特点:bean定义到外面,在property标签中使用ref属性进行注入,通常这种方式是常用的。
set-di.xml
这种方式作为了解。
第一步:定义User类,提供setter方法
package com.hhb.bean;
public class User {
private String username; // String是简单类型
private String password;
private int age; // int是简单类型
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
第二步:set-di.xml
第三步:测试
@Test
public void testSimpleTypeSet() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
User user = applicationContext.getBean("userBean", User.class);
System.out.println(user);
}
简单类型包括:
基本数据类型
基本数据类型对应的包装类
String或其他的CharSequence子类
Number子类
Date子类
Enum子类
URL
URI
Temporal子类
Locale
Class
以上简单值类型对应的数组类型
SimpleValueType
package com.powernode.spring6.beans;
import java.net.URI;
import java.net.URL;
import java.time.LocalDate;
import java.util.Date;
import java.util.Locale;
public class A {
private byte b;
private short s;
private int i;
private long l;
private float f;
private double d;
private boolean flag;
private char c;
private Byte b1;
private Short s1;
private Integer i1;
private Long l1;
private Float f1;
private Double d1;
private Boolean flag1;
private Character c1;
private String str;
private Date date;
private Season season;
private URI uri;
private URL url;
private LocalDate localDate;
private Locale locale;
private Class clazz;
// ⽣成setter⽅法
// ⽣成toString⽅法
}
enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
如果把Date当做简单类型的话,日期字符串格式不能随便写。必须按照这种格式写:Wed Oct 19 16:28:13 CST 2022
经典案例:给数据源的属性注入值
MyDataSource
package com.hhb.jdbc;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class MyDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password;
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public T unwrap(Class iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return false;
}
}
set-di.xml
Student
package com.hhb.bean;
public class Student {
private String name;
//学生属于哪个班级
private Clazz clazz;
public void setName(String name) {
this.name = name;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
//使用级联属性赋值,需要get方法
public Clazz getClazz() {
return clazz;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", clazz=" + clazz +
'}';
}
}
Clazz
package com.hhb.bean;
public class Clazz {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Clazz{" +
"name='" + name + '\'' +
'}';
}
}
cascade.xml
Array1
package com.hhb.bean;
import java.util.Arrays;
public class Array1 {
private String[] hobbies;
private Array2[] array2;
public void setArray2(Array2[] array2) {
this.array2 = array2;
}
public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "Array1{" +
"hobbies=" + Arrays.toString(hobbies) +
", array2=" + Arrays.toString(array2) +
'}';
}
}
Array2
package com.hhb.bean;
public class Array2 {
private String name;
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Array2{" +
"name='" + name + '\'' +
'}';
}
}
spring-array.xml
抽烟
喝酒
烫头
Person
package com.hhb.bean;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Person {
private List names;
private Set phones;
private Map adrrs;
public void setNames(List names) {
this.names = names;
}
public void setPhones(Set phones) {
this.phones = phones;
}
public void setAdrrs(Map adrrs) {
this.adrrs = adrrs;
}
@Override
public String toString() {
return "Person{" +
"names=" + names +
", phones=" + phones +
", adrrs=" + adrrs +
'}';
}
}
spring-collection.xml
张三
李四
王五
张三
110
120
110
Property
package com.hhb.bean;
import java.util.Properties;
public class Property {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "Property{" +
"properties=" + properties +
'}';
}
}
Properties本质上也是一个Map集合。
Properties的父类Hashtable实现了Map接口。
Properties的key和value只能是String类型。
spring-properties.xml
com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/spring6
root
123456
XML中有5个特殊字符,分别是:<、>、'、"、&
以上5个特殊符号在XML中会被特殊对待,会被当做XML语法的一部分进行解析,如果这些特殊符号直接出现在注入的字符串当中,会报错。
特殊字符 | 转义字符 |
---|---|
> | > |
< | < |
' | ' |
" | " |
& | & |
目的:简化配置
Dog
package com.hhb.bean;
import java.util.Date;
public class Dog {
// 简单类型
private String name;
private int age;
// 非简单类型
private Date birth;
// p命名空间注入底层还是set注入,只不过p命名空间注入可以让spring配置变的更加简单。
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", birth=" + birth +
'}';
}
}
spring-p.xml
c命名空间是简化构造方法注入的。
People
package com.hhb.bean;
public class People {
private String name;
private String age;
private Boolean flag;
//c命名空间是简化构造注入的
//c命名空间注入办法是基于构造方法的
public People(String name, String age, Boolean flag) {
this.name = name;
this.age = age;
this.flag = flag;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
", flag=" + flag +
'}';
}
}
spring-c.xml
使用util命名空间可以让配置复用。
MyDataSource1
package com.hhb.jdbc;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;
public class MyDataSource1 implements DataSource {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "MyDataSource1{" +
"properties=" + properties +
'}';
}
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public T unwrap(Class iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return false;
}
}
spring-util.xml
com.mysql.cj.jdbc.driver
jdbc:mysql://localhost:3306/spring
root
123
都是基于set方法的。
MyDataSource
package com.hhb.jdbc;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class MyDataSource implements DataSource {
private String driver;
private String url;
private String username;
private String password;
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public T unwrap(Class iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return false;
}
}
jdbc.properties
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring6
jdbc.username=root
jdbc.password=123
spring-properties.xml