IOC简介
概念、优点网上一大堆,我只说下自己的见解。所谓控制反转IoC就是在开发过程中,不需要手动的new对象,而是把对象都交给Spring的IOC容器,由Spring容器来创建和注入到需要的相应对象的位置。
不使用SpringIoC的情况下举个例子:在传统的MVC模型中,用户Controller类会调用用户Service类,用户Service类调用用户Dao类,如果实现一个注册功能,需要有以下流程:
Controller需要调用Service中的方法,所以Controller类中必须创建Service对象
public class UserController {
UserService userService = new UserService();
@Request("/register")
public void register(User user){
userService.register(user);
}
}
Service需要调用Dao层中的方法,所以Service中必须创建Dao对象
public class UserService{
UserDao userDao = new UserDao();
public void register(User user){
userDao.insertUser(user);
}
}
Dao层需要创建数据源,获取数据库连接
public class UserDao{
DataSource ds = new DataSource(); // 实际上创建数据源还要配置,没这么简单
public void insertUser(User user){
ds.getConnection.executeUpdate("INSERT INTO t_user (user_name, password) values (?,?)");
}
}
上面写了一个MVC开发逻辑层次,有些地方是伪代码,理解即可,可以看到每一层都要new下一层的对象,使用IOC可以避免这种一直new来new去的情况,尤其是在项目中组件很多的时候优势会很明显
使用xml配置IOC
由SpringIoC容器管理对象,那得先告诉容器哪些对象需要放到容器里,哪些对象在哪里会被用到,这些信息都可以保存到配置文件中。(虽然这个方式现在很少用,都是用注解,但是理解配置文件可以深入理解注解方式)
要使用spring ioc首先要引入依赖,maven中引入:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
配置文件
对于上面的代码,既然不想直接new对象,那么就要靠spring ioc,把这些类的信息告诉spring是通过配置文件实现的,配置文件是一个xml,有固定的格式,我们只要关注其中的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="spring.service.UserService">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="spring.dao.UserDao">
<property name="mockDataSource" ref="mockDataSource"></property>
</bean>
<bean id="mockDataSource" class="spring.dao.MockDataSource"></bean>
</beans>
每个bean就是一个对应的类,bean的id是指当前类在配置文件中的唯一标识,class是类对应的路径,只要一个类就和一个id绑定。
每个bean内的property标签是该类需要被注入的属性,name是该属性的变量名,ref是说明该属性要注入哪个bean。
依赖注入
通过set方法注入
其实只有配置文件还是不够的,配置文件只告诉spring这些类以及类对象要去的地方,但是spring还没有把创建的对象赋值给类属性,赋值过程叫注入。实际上,spring是通过set方法注入的,因此在类中必须写要被注入属性的set方法。比如
public class UserService {
UserDao userDao;
public void register(UserEntity user) {
userDao.insertUser(user);
System.out.println("Service层执行成功...");
}
public void setUserDao(UserDao userDao) { // spring就是通过这个方法注入的,不写不行!
this.userDao = userDao;
}
}
通过构造方法注入
实际上除了通过set方法注入,spring还支持通过构造方法注入!构造方法注入配置文件就要改一下,不能用property了,要用constructor-arg标签,以UserService为例,配置文件改为:
<bean id="userService" class="spring.service.UserService">
<!--<property name="userDao" ref="userDao"></property>-->
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
UserService改为:
public class UserService {
UserDao userDao;
public UserService(UserDao userDao) { // 通过构造方法注入
this.userDao = userDao;
}
public void register(UserEntity user) {
userDao.insertUser(user);
System.out.println("Service层执行成功...");
}
}
bean property 和 constructor-arg其他属性说明
bean标签常用属性
id:bean的唯一标识
name:废弃不建议使用,作用和id差不多
class:bean对应的类的全路径
scope:singleton或prototype,决定了bean的创建时机。
property标签常用属性
name:被注入的属性的变量名
ref:注入bean的id
value:注入基本类型时的值
constructor-arg标签中的属性
name:被注入属性的变量名
ref: 注入的bean的id
value:如果注入的基本数据类型,就直接value而不是ref,注入基本类型时使用
index:指定当前注入对象对应构造方法中第几个参数
type:指定注入的当前值的类型,注入基本类型时使用
第三方包注入
上面的类都是我们自己写的,那么第三方包如何注入呢,比如数据库连接池HikariCP,仔细想一下其实第三方包里面的类也只是个类而已,只要找到它的类路径即可。配置文件如下:
<bean id="userDao" class="spring.dao.UserDao">
<property name="mockDataSource" ref="mockDataSource"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="mockDataSource" class="spring.dao.MockDataSource"/>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/online_mall"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</bean>
UserDao中通过set方法即可注入DataSource
使用注解配置IOC
xml配置bean的方式在类比较少的时候还好,当类多起来的时候,要不断的扩大配置文件,会比较麻烦,后来就引入了更好的更方便的IOC配置方法,直接使用注解,摒弃冗长复杂的配置文件。
告别配置文件
组件注解和自动装配注解
在类上加上@component 表示这个类是一个spring ioc组件,在spring容器中创建该类的对象
在要被注入的属性上加@AutoWired注解,表示自动注入
@Component
public class UserService {
@AutoWired
UserDao userDao;
public void register(UserEntity user) {
userDao.insertUser(user);
System.out.println("Service层执行成功...");
}
}
@Component
public class UserDao {
....
}
上述代码就相当于配置文件
<bean id="userService" class="spring.service.UserService">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="spring.dao.UserDao"/>
引入第三方包
注解虽好,但是如果一个类在第三方包里怎么办,还是以数据库连接池HikariDP为例,按照注解的套路,我们要在HikariDataSource这个类上加一个@Component,但是这显然是不可能的,因为我们根本无法修改其源码,方法是有的,使用@Configuration和@Bean注解
@Configuration用在类上,注明一个类是Spring配置类
@Bean注解用在方法上,注明该方法返回一个对象,这个对象加入到Spring ioc容器中
@Configuration
public class DataSourceUtils {
@Bean
public static DataSource getDataSource() {
......
return new HikariDataSource(config);
}
}
@Component
public class UserDao {
@Autowired
DataSource ds;
public void insertUser(UserEntity user) {
ds.executeUpdate(user);
System.out.println("Dao层执行成功..." + user);
}
}
其他常用注解
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)配合@Component使用,注明类的作用域SCOPE_PROTOTYPE是多例, SCOPE_SINGLETON单例
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class UserService {
......
}
@PropertySource(“xxx.properties”)用在类上,注明读取配置文件xxx,并在类中使用@Value取值注入配置文件中的内容
配置文件jdbc.properties
# 数据库url
jdbc.url=jdbc:mysql://localhost:3306/online_mall
# 数据库用户名与密码
jdbc.username=root
jdbc.password=12345678
@PropertySource("jdbc.properties")
public class DataSourceUtils {
@Value("${jdbc.url}")
private String db_url;
@Value("${jdbc.username}")
private String user;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource getDataSource() throws SQLException {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(db_url);
config.setUsername(user);
config.setPassword(password);
return new HikariDataSource(config);
}
}
@PostConstruct:定义该方法在构造方法之前执行
@PreDestroy:定义该方法在方法结束时执行一次
@Qualifier:配合bean标签定义别名,在注入时配合AutoWired指定注入哪个对象