IOC
IOC概念
控制反转(反转控制),将对象的创建以及相互之间的调用过程交给spring容器处理,以此达到降低代码间的耦合度的目的。
IOC底层原理
一、使用到的技术:xml解析,反射,工厂设计模式。
二、创建对象过程:
1、创建xml配置文件,配置要创建的对象;
2、解析xml配置文件,根据配置的信息创建对象,大致代码如下:
package entity;
public class ObjectFactory {
public static Object getBean() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
//通过xml解析,得到bean标签里面的类的class属性值
String classPath = "xxx";
// 通过反射创建对象
Class<?> clazz = Class.forName(classPath);
//返回实例
return (User) clazz.newInstance();
}
}
IOC接口
一、IOC思想基于IOC容器完成,而IOC容器的底层是对象工厂。
二、spring为实现IOC提供了两种方式(两个接口):
1、BeanFactory:IOC容器的基本实现,是spring内部实现的接口,不提供给开发人员使用。加载配置文件时不会创建对象,只有使用时才会创建对象
2、ApplicationContext:BeanFactory的一个子接口,提供了更多更强大的功能,一般由开发人员使用。加载配置文件时就会创建对象
IOC操作Bean管理
一、Bean管理的概念
bean管理是指两个操作:创建对象、注入属性。
二、Bean管理的两种实现方式
1、基于xml配置方式实现
1.1、基于xml方式创建对象
创建User类,默认拥有无参构造
package entity;
public class User {
private String name;
private int age;
private int sex;
public void print() {
System.out.println("hello world");
}
}
配置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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="entity.User"/>
</beans>
读取配置文件,创建对象
public class MyTest {
@Test
public void testCreate() {
//加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//获取配置创建对象
User user1 = context.getBean("user1", User.class);
user1.print();
}
}
执行结果:
1.2、基于xml方式注入属性
DI:依赖注入(注入属性,IOC的一种具体实现)
set方法注入
set方法注入必须加上属性的set方法和空参构造(默认有空参构造),否则会报 BeanCreationException异常
package entity;
public class User {
private String name;
private int age;
private int sex;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(int sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
配置属性注入
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="entity.User">
<!--使用property标签注入属性值,name:属性名,value:属性值-->
<property name="name" value="dog"/>
<property name="sex" value="1"/>
<property name="age" value="10"/>
</bean>
</beans>
读取配置文件,创建对象(下面几个例子将沿用这部分代码)
public class MyTest {
@Test
public void testCreate() {
//加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//获取配置创建对象
User user1 = context.getBean("user1", User.class);
System.out.println(user1);
}
}
执行结果:
有参构造注入
有参构造注入可以不用set方法,但是要加上有参构造方法,否则会报 BeanCreationException异常
package entity;
public class User {
private String name;
private int age;
private int sex;
public User(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
配置属性注入
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="entity.User">
<!--第一种,根据属性名注入-->
<!--<constructor-arg name="name" value="lion"></constructor-arg>
<constructor-arg name="age" value="5"></constructor-arg>
<constructor-arg name="sex" value="1"></constructor-arg>-->
<!--第二种,根据属性顺序注入,下标从0开始-->
<constructor-arg index="0" value="lion"></constructor-arg>
<constructor-arg index="1" value="5"></constructor-arg>
<constructor-arg index="2" value="1"></constructor-arg>
</bean>
</beans>
执行结果
p命名空间注入
本质上还是set方法注入,所以可以不要有参构造,但是需要无参构造(默认有有参构造)和set方法,否则会报 BeanCreationException异常
package entity;
public class User {
private String name;
private int age;
private int sex;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(int sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
xml配置文件要引入p命名空间约束,否则会报XmlBeanDefinitionStoreException错误
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="entity.User" p:name="tiger" p:age="8" p:sex="1">
</bean>
</beans>
执行结果
c命名空间注入
本质上是有参构造注入,所以可以不需要set方法和空构造,但是要有有参构造,否则会报BeanCreationException异常
package entity;
public class User {
private String name;
private int age;
private int sex;
public User(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
xml配置文件要加上c命名空间约束,否则会报XmlBeanDefinitionStoreException异常
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="entity.User" c:name="cat" c:age="3" c:sex="1">
</bean>
</beans>
执行结果
对象属性注入
如果属性类型是对象类型,如:数组,集合,则不能单纯使用value赋值,否则会报错
Pet类
package entity;
public class Pet {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
'}';
}
}
User类
package entity;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public class User {
private String name;
private int age;
private int sex;
private List<String> hobby;
private String[] books;
private Map<String, String> map;
private Properties properties;
private Pet pet;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public List<String> getHobby() {
return hobby;
}
public void setHobby(List<String> hobby) {
this.hobby = hobby;
}
public String[] getBooks() {
return books;
}
public void setBooks(String[] books) {
this.books = books;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
", hobby=" + hobby + '\n' +
", books=" + Arrays.toString(books) + '\n' +
", map=" + map + '\n' +
", properties=" + properties + '\n' +
", pet=" + pet +
'}';
}
}
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="entity.User">
<property name="sex" value="1"/>
<property name="age" value="20"/>
<property name="name" value="fish"/>
<property name="books">
<array>
<value>java从入门到放弃</value>
<value>mysql从删库到跑路</value>
<value>c语言从看懂到看开</value>
</array>
</property>
<property name="hobby">
<list>
<value>唱</value>
<value>跳</value>
<value>rap</value>
<value>篮球</value>
</list>
</property>
<property name="map">
<map>
<entry key="k1" value="v1"/>
<entry key="k2" value="v2"/>
<entry key="k3" value="v3"/>
</map>
</property>
<property name="properties">
<props>
<prop key="url1">www.baidu.com</prop>
<prop key="utl2">www.bilibili.com</prop>
</props>
</property>
<property name="pet" ref="pet"/>
</bean>
<bean id="pet" class="entity.Pet">
<property name="name" value="旺财"/>
</bean>
</beans>
执行结果
根据外部配置文件注入属性
引入mysql依赖和druid依赖(不是必须,只是这个例子使用了这两个依赖),在resource下创建jdbc.properrties配置文件
jdbc.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/course?serverTimezone=GMT%2B8&useSSL=false&useUnicode=true&characterEncoding=utf-8
user=root
password=123456
bean1.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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--加载配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</bean>
</beans>
MyTest.java
import com.alibaba.druid.pool.DruidDataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void testCreate() {
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
DruidDataSource source = context.getBean("datasource", DruidDataSource.class);
System.out.println(
source.getDriverClassName() + "\n" +
source.getUrl() + "\n" +
source.getUsername() + "\n" +
source.getPassword() + "\n"
);
}
}
执行结果
小tip:
1、如何注入空值属性
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="entity.User">
<!--注入空值-->
<property name="name">
<null/>
</property>
<property name="age" value="6"/>
<property name="sex" value="1"/>
</bean>
</beans>
运行结果
2、如何注入特殊字符,如尖括号
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="entity.User">
<!--转义生成特殊符号-->
<!--<property name="name" value="<活着>"></property>-->
<!--<![CDATA[属性值]]>-->
<property name="name">
<value><![CDATA[<活着>]]></value>
</property>
<property name="age" value="10"></property>
<property name="sex" value="1"></property>
</bean>
</beans>
执行结果
2、基于注解方式实现
2.1、spring对于bean管理对象的创建提供的注解:
(1)@Component
(2)@Repository:一般注解dao(持久)层
(3)@Service:一般注解service(业务)层
(4)@Controller:一般注解controller(web)层
上面四个注解无明确规定要放在哪一层,只是为了开发方便所以约定俗成,实际每一层都可以用随意的一种注解
2.2、基于注解方式创建对象
新建dao包,并且创建一个UserDao类
package dao;
import org.springframework.stereotype.Repository;
// 四个注解任意一个都可以:repository,service,controller,component
// 默认值是类名首字母小写,即userDao
@Repository("userDao")
public class UserDao {
public void print() {
System.out.println("hello world");
}
}
配置xml配置文件,注意要加上context命名空间(引入component-scan标签)并引用模式文档(模式文档路径一定不能有误),否则会报XmlBeanDefinitionStoreException 异常
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描base-package值为包路径,多个路径用“,”隔开或者扫描公共的上层目录-->
<context:component-scan base-package="dao"></context:component-scan>
</beans>
_ 测试代码_
import dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void testCreate() {
//加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//获取配置创建对象
UserDao dao = context.getBean("userDao", UserDao.class);
dao.print();
}
}
执行结果
2.3、基于注解方式注入属性
(1)@Autowired:根据属性类型自动装配
(2)@Qualifier:根据属性名称注入
(3)@Resource:可以根据属性类型注入,也可以根据属性名称注入(java自带的注解)
(4)@Value:注入普通类型属性
使用@Autowired注解和@qualifier注入
在dao包里面创建UserDao接口,并且创建UserDaoImpl类实现UserDao接口,使用@Repository注解,重写print方法
UserDao接口
package dao;
public interface UserDao {
void print();
}
UserDaoImpl类
package dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl implements UserDao{
@Override
public void print() {
System.out.println("hello userDaoImpl");
}
}
创建service包,并且创建Service类,使用@Service注解,并且使用@Autowired注解注入UserDao属性
UserService类
package service;
import dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService{
@Autowired
private UserDao userDao;
public void print() {
System.out.println("hello service");
userDao.print();
}
}
修改bean1.xml配置文件,扫描dao和service包,如果扫描不全会报UnsatisfiedDependencyException(未扫描dao包)或者NoSuchBeanDefinitionException(未扫描service包)异常
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启组件扫描base-package值为包路径,多个路径用“,”隔开或者扫描公共的上层目录-->
<context:component-scan base-package="dao,service"></context:component-scan>
</beans>
测试代码
import dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
public class MyTest {
@Test
public void testCreate() {
//加载配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
//获取配置创建对象
UserService service = context.getBean("userService", UserService.class);
service.print();
}
}
执行结果
注意:在一个接口有多个实现时,可以使用 @Qualifier 确定要注入的类
UserDaoImpl2类
package dao;
import org.springframework.stereotype.Repository;
@Repository
public class UserDaoImpl2 implements UserDao{
@Override
public void print() {
System.out.println("hello userDaoImpl2");
}
}
UserService类
package service;
import dao.UserDao;
import dao.UserDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserService{
@Autowired
@Qualifier("userDaoImpl2")
private UserDao userDao;
public void print() {
System.out.println("hello service");
userDao.print();
}
}
执行结果
使用@Resource注解注入
使用@Resource注解注入如果注入的接口只是单实现则可以根据类型注入
UserService
package service;
import dao.UserDao;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService{
// 根据类型注入
@Resource
private UserDao userDao;
public void print() {
System.out.println("hello service");
userDao.print();
}
}
使用@Resource注解注入如果注入的接口是多实现则可以根据名称注入
UserService
package service;
import dao.UserDao;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService{
// 根据名称注入
@Resource(name = "userDaoImpl")
private UserDao userDao;
public void print() {
System.out.println("hello service");
userDao.print();
}
}
执行结果
使用@Value注入普通属性
UserService
package service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class UserService{
@Value(value = "旺财")
private String name;
public void print() {
System.out.println(name);
}
}
执行结果
3、完全使用注解开发
新建config包,然后创建SpringConfig类
SpringConfig
package config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"dao","service"})
public class SpringConfig{
}
创建测试类
import config.SpringConfig;
import dao.UserDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
public class MyTest {
@Test
public void testConfig() {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService service = context.getBean("userService", UserService.class);
service.print();
}
}
service等类使用根据 @Resource 根据名称注入的代码
执行结果