IoC(Inversion of Control) 控制反转与DI(Dependency Injection)依赖注入
- IoC(Inversion of Control) 控制反转
- 使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转(降低程序的耦合度)
-
Spring技术对IoC思想进行了实现
-
Spring提供了一个容器,称为IoC容器,用来充当思想中的“外部”
-
IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中被称为Bean
-
- DI(Dependency Injection) 依赖注入
- 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
以上操作的目标:充分解耦
bean相关
bean配置
-
bean基础配置
- id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一
-
class:bean的类型,即配置的bean的全路径类名
- 例子
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
- bean的别名
- name:为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔
- 例子
<bean id="bookService" name="service service4 bookEbi" class="com.itheima.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
- bean的作用范围
- scope:为bean设置作用范围,可选值为单例singleton,非单例prototype,若不写scope,则默认是单例对象
- 例子
<bean id="bookDao" name="dao" class="com.itheima.dao.impl.BookDaoImpl" scope="prototype"/>
bean实例化
使用FactoryBean的方式实例化bean对象
- 实现FactoryBean接口,指定创建的泛型对象
//FactoryBean创建对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
public Class<?> getObjectType() {
return UserDao.class;
}
// 控制创建的bean是单例的还是非单例的,通常都是单例的
public boolean isSingleton() {
return true;
}
}
- 配置
<!--方式四:使用FactoryBean实例化bean-->
<!--注意,这种构造方法创建的不是FactoryBean对象,而是FactoryBean对象中的getObject对象-->
<bean id="userDao" class="com.itheima.factory.UserDaoFactoryBean"/>
bean的生命周期
-
初始化容器
- 1.创建对象(内存分配)
- 2.执行构造方法
- 3.执行属性注入(set操作)
- 4.执行bean初始化方法
-
使用bean
- 执行业务操作
-
关闭/销毁容器
- 执行bean销毁方法
-
bean的销毁时机
-
容器关闭前触发bean的销毁
-
关闭容器方式:
- 手动关闭容器
ConfigurableApplicationContext接口close()操作
- 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
ConfigurableApplicationContext接口registerShutdownHook()操作
-
public class AppForLifeCycle {
public static void main( String[] args ) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
//注册关闭钩子函数,在虚拟机退出之前回调此函数,关闭容器
//ctx.registerShutdownHook();
//关闭容器
ctx.close();
}
}
依赖注入相关
依赖注入方式
-
向一个类中传递数据的方式有几种?
- 普通方法(set方法)
- 构造方法
-
依赖注入描述了在容器中建立bean和bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
- 引用类型
- 简单类型(基本数据类型与String)
-
依赖注入方式
-
setter注入
-
简单类型
- 在bean中定义引用类型属性并提供可访问的set方法
public class BookDaoImpl implements BookDao { private String databaseName; // 数据库名称 private int connectionNum; // 数据库连接数量 //setter注入需要提供要注入对象的set方法 public void setConnectionNum(int connectionNum) { this.connectionNum = connectionNum; } //setter注入需要提供要注入对象的set方法 public void setDatabaseName(String databaseName) { this.databaseName = databaseName; } }
- 配置中使用property标签value属性注入简单类型数据
<!--注入简单类型--> <!--对bookDao进行配置--> <bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"> <!--property标签:设置注入属性--> <!--name属性:设置注入的属性名,实际是set方法对应的名称--> <!--value属性:设置注入简单类型数据值--> <property name="connectionNum" value="100"/> <property name="databaseName" value="mysql"/> </bean>
-
引用类型
- 在bean中定义引用类型属性并提供可访问的set方法
public class BookServiceImpl implements BookService{ private BookDao bookDao; private UserDao userDao; //setter注入需要提供要注入对象的set方法 public void setUserDao(UserDao userDao) { this.userDao = userDao; } //setter注入需要提供要注入对象的set方法 public void setBookDao(BookDao bookDao) { this.bookDao = bookDao; } }
- 配置中使用property标签ref属性注入引用类型对象
<!--注入引用类型--> <!--对bookService进行配置--> <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl"> <!--property标签:设置注入属性--> <!--name属性:设置注入的属性名,实际是set方法对应的名称--> <!--ref属性:设置注入引用类型bean的id或name--> <property name="bookDao" ref="bookDao"/> <property name="userDao" ref="userDao"/> </bean>
-
-
构造器注入
- 简单类型
- 引用类型
-
依赖自动装配
- IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
- 自动装配方式
- 按类型(常用)
- 按名称
- 按构造方法
- 配置中使用bean标签autowire属性设置自动装配的类型
<!--id在自动装配时可以省略不写-->
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.itheima.service.impl.BookServiceImpl" autowire="byType"/>
一些细节需要注意
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因为变量名与配置耦合,不推荐使用
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
容器
创建容器
- 类路径加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- 文件路径加载配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\Code\\java\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
- 加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");
获取bean
- 使用bean名称获取
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
- 使用bean名称获取并指定类型
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
- 使用bean类型获取(容器中这个类型的bean只能有一个)
BookDao bookDao = ctx.getBean(BookDao.class);
注解开发
bean相关
注解开发定义bean
- 使用@Component定义bean
@Component("bookDao")
public class BookDaoImpl implements BookDao {
}
@Component
public class BookServiceImpl implements BookService {
}
- 核心配置文件中通过组件扫描加载bean
<context:component-scan base-package="com.itheima"/>
- Spring提供@Component注解的三个衍生注解
- @Controller:用于表现层bean定义
- @Service:用于业务层bean定义
- @Repository:用于数据层bean定义
@Repository("bookDao") // 数据层的bean
public class BookDaoImpl implements BookDao {
}
@Service // 业务层的bean
public class BookServiceImpl implements BookService {
}
纯注解开发
-
在纯注解开发中,使用java类代替spring核心配置文件
- 核心配置文件如下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 核心配置文件中通过组件扫描加载bean--> <context:component-scan base-package="com.itheima"/> </beans>
- java类如下
package com.itheima.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; //声明当前类为Spring配置类 @Configuration //设置bean扫描路径,多个路径书写为字符串数组格式 @ComponentScan({"com.itheima.service","com.itheima.dao"}) public class SpringConfig { }
其中,java类中的@Configuration完全代替了原来核心配置文件的结构
- @Configuration注解用于设定当前类为配置类
- @ConponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据用数组格式
//设置bean扫描路径,多个路径书写为字符串数组格式
@ComponentScan({"com.itheima.service","com.itheima.dao"})
- 读取Spring核心配置文件初始化容器对象切换为读取java配置类初始化容器对象
// 加载配置文件初始化容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
纯注解开发中bean的管理
-
bean的作用范围
- 使用@Scope定义bean作用范围
//@Scope设置bean的作用范围 @Scope("singleton") public class BookDaoImpl implements BookDao { }
-
bean的生命周期
- 使用@PostConstruct、@PreDestroy定义bean生命周期
@Repository //@Scope设置bean的作用范围 @Scope("singleton") public class BookDaoImpl implements BookDao { public void save() { System.out.println("book dao save ..."); } //@PostConstruct设置bean的初始化方法 @PostConstruct public void init() { System.out.println("init ..."); } //@PreDestroy设置bean的销毁方法 @PreDestroy public void destroy() { System.out.println("destroy ..."); } }
依赖注入相关
依赖注入
- 使用@Autowired注解开启自动装配模式(按类型装配)
@Service
public class BookServiceImpl implements BookService {
//@Autowired:注入引用类型,自动装配模式,默认按类型装配
@Autowired
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
注意:自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法
- 使用@Qualifier注解开启指定名称装配bean
@Service
public class BookServiceImpl implements BookService {
//@Autowired:注入引用类型,自动装配模式,默认按类型装配
@Autowired
//@Qualifier:自动装配bean时按bean名称装配
@Qualifier("bookDao")
private BookDao bookDao;
}
注意:@Qualifier注解无法单独使用,必须配合@Autowired注解使用
- 对于简单类型注入,使用@Value进行装配
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
//@Value:注入简单类型(无需提供set方法)
@Value("${name}") // name来自于配置文件property
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
- 使用@PropertySource注解加载properties文件
@Configuration
@ComponentScan("com.itheima")
//@PropertySource加载properties配置文件
@PropertySource({"jdbc.properties"})
public class SpringConfig {
}
注意:路径仅支持单一文件配置,多文件要使用数组格式配置,不允许使用通配符*
第三方bean管理
- 使用@Bean配置第三方bean
@Configuration
public class SpringConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 使用独立的配置类管理第三方bean
@Configuration
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
-
将独立的配置类加入核心配置
- 方式一:导入式(推荐使用)
public class JdbcConfig { @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); // 相关配置 return ds; } }
使用@Import注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式
@Configuration @Import({JdbcConfig.class}) public class SpringConfig { }
- 方式二:扫描式(不推荐使用,隐藏性强,不知道在config中导入过哪些配置类)
@Configuration public class JdbcConfig { @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); // 相关配置 return ds; } }
使用@ComponentScan注解扫描配置类所在的包,加载对应的配置类信息
@Configuration @ComponentScan({"com.itheima.config", "com.itheima.service", "com.itheima.dao"}) public class SpringConfig { }
第三方bean依赖注入
- 简单类型依赖注入
public class JdbcConfig {
//1.定义一个方法获得要管理的对象
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String password;
//2.添加@Bean,表示当前方法的返回值是一个bean
//@Bean修饰的方法,形参根据类型自动装配
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 引用类型依赖注入
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
// 属性设置
return ds;
}
引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象
AOP(Aspect Oriented Programming)面向切面编程
AOP简介
- AOP:面向切面编程,一种编程范式,指导开发者如何组织程序结构
-
作用:在不改变原始设计的基础上为其进行功能增强
- spring理念:无入侵式编程
AOP核心概念
- 连接点:程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
- 在SpringAOP中,理解为方法的执行
- 切入点:匹配连接点的式子
- 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 通知:在切入点处执行的操作,也就是共性功能
- 在SpringAOP中,功能最终以方法的形式呈现
- 通知类:定义通知的类
- 切面:描述通知与切入点的对应关系
AOP工作流程
1.Spring容器启动
2.读取所有切面配置中的切入点
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
// 设置切入点,要求配置在方法上方
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void ptx(){}
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
// 设置在切入点pt()的前面运行当前操作(前置通知)
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
3.初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,则创建对象
- 匹配成功,则创建原始对象(目标对象)的代理对象
注:只要有一个切入点匹配成功,就算匹配上了
4.获取bean执行方法
- 获取bean,调用方法并执行,完成操作
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
AOP切入点表达式
切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述方式
- 方式一:执行接口中的无参数方法
- 方式二:执行实现类中的无参数方法
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
execution(public User com.itheima.service.UserService.findById(int))
- 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
- 访问修饰符:public、private等,可省略
- 返回值
- 包名
- 类/接口名
- 方法名
- 参数
- 异常名:方法定义中抛出指定异常,可以省略
可以使用通配符描述切入点,实现快速描述
- *****:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.itheima.*.UserService.find*(*))
匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
- ..:多个连续地任意符号,可以对出现,常用于化简包名与参数的书写
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
- +:专用于匹配子类类型
execution(* *..*Service+.*(..))
书写技巧
- 描述切入点通常描述接口,而不描述实现类,可以降低耦合度
- 访问控制修饰符针对接口开发均采用public描述(可省略public)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
- 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配
- 接口名/类名书写名称与模块相关的采用****匹配,例如UserService书写成:Service,绑定业务层接口名
- 方法名书写以动词进行精准匹配,名词采用****匹配,例如getById书写成getBy,selectAll书写成selectAll
- 通常不使用异常作为匹配规则
通知类型
-
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
-
AOP通知共分为5种类型
-
前置通知
-
后置通知
-
环绕通知(常用)
- 名称:@Around
- 类型:方法注解
- 位置:通知方法定义上方
- 作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
- 范例
@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("around before advice ..."); Object ret = pjp.proceed(); // 表示对原始操作的调用 System.out.println("around after advice ..."); return ret; }
-
返回后通知(不常用)
-
抛出异常后通知(不常用)
-
@Around注意事项
- 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
- 通知中如果未使用ProceedingJoinPoint对原始方法进行调用,将跳过原始方法的执行
- 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,通知方法必须设定为Object类型
- 原始方法的返回值如果是void类型,通知方法的返回值可以设置成void,也可以设置成Object
- 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
AOP通知获取数据
-
获取切入点方法的参数
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
@Before("pt()") public void before(JoinPoint jp) { Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); System.out.println("before advice ..." ); }
- ProceedingJointPoint:适用于环绕通知
@Around("pt()") public Object around(ProceedingJoinPoint pjp) { Object[] args = pjp.getArgs(); System.out.println(Arrays.toString(args)); args[0] = 666; Object ret = null; try { ret = pjp.proceed(args); } catch (Throwable t) { t.printStackTrace(); } return ret; }
注:ProceedingJointPoint是JointPoint的子类
-
获取切入点方法返回值
- 返回后通知
- 环绕通知
-
获取切入点方法运行异常信息
- 抛出异常后通知
- 环绕通知
Spring事务
Spring事务简介
- 事务作用:在数据层保障一系列的数据库操作同成功同失败
- Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功或同失败
Spring事务角色
- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务调解员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
Spring事务属性
事务相关配置
事务传播行为
- 事务传播行为:事务协调员对事务管理员所携带事务的处理态度