IOC 容器
IOC 底层原理
IOC : 控制反转,对象创建和对象之间的调用过程,交给Spring进行管理,达到降低耦合度的目的。
底层原理 :xml 解析+工厂模式+反射(本质就是对象工厂)
IOC 过程 :
-
配置 xml 文件,配置创建的对象
<bean id = "自定义名称" class = "类路径"></bean>
-
有 A 类和 B 类,现需要在 B 中调用 A 中的方法,需创建工厂类
class XxxFactory{ public static A getA(){ String classValue = 获取-class属性值; //xml解析(获取类路径) Class clazz = Class.forName(className);//通过反射创建对象 return (A)clazz.newInstance(); } }
IOC-BeanFactory 接口
**Spring 提供的 IOC 容器两种实现方式 : **
-
BeanFactory : IOC 容器的基本实现方式,是 Spring 内部使用接口,不提供给开发人员使用。加载配置文件时不会创建对象,在获取或使用对象时才去创建。
-
ApplicationContext : BeanFactory 的子接口,提供更多的功能,面向开发人员使用,加载配置文件时会对配置文件中的对象进行创建。
ApplicationContext 的两个主要实现类
1.ClassPathXmlApplicationContext 2.FileSystemXmlApplicationContext
IOC Bean 的管理
Bean 管理的两个操作 : (1) Spring 创建对象 (2) Spring 注入属性
xml 方式
创建对象
- Spring 配置文件中,使用 bean 标签,然后添加对应属性,就可实现对象的创建
- bean 标签-常用属性
属性 | 值 |
---|---|
id | 唯一标识 |
class | 包类路径 |
- 创建对象时,默认执行无参构造方法完成对象的创建
注入属性
DI : 依赖注入,(就是注入属性)
-
使用 set 方法进行注入
创建类,定义属性,创建对应的 set 方法
//name:属性名,value:注入的值 <bean id="user" class="com.lte.User"> <property name="username" value="admin"></property> <property name="password" value="123"></property> </bean>
-
使用有参构造器进行注入
创建类,定义属性,创建对应有参构造方法
<bean id="user" class="com.lte.User"> <constructor-arg name="username" value="liu"></constructor-arg> <constructor-arg name="password" value="666"></constructor-arg> //或者 <!-- <constructor-arg index="0" value="liu"></constructor-arg>--> </bean>
-
p 名称空间注入(略过…,,只是 set 方式的简化写法)
-
xml 注入其它类型属性
字面量
null 值
<property name="username"> <null/> </property>
属性值包含特殊符号
1.<!--对特殊符号进行转义(<,> 等)--> 2.<!--把特殊符号内容写在 CDATA 中--> <property name="username"> <value><![CDATA[<<特殊符号>>……^!]]></value> </property>
注入属性-外部 Bean
(1) 两个类 service 类和 dao 类 (2)在 service 调用 dao 里面的方法 (3) spring配置文件中进行配置
<!--配置文件 --> <!-- service 和 dao 对象的创建--> <bean id="userService" class="com.lte.Service.impl.UserServiceImpl"> <!-- 注入 UserDAOImpl 对象 ref 属性 : 创建的 UserDAOImpl 对象的 bean 标签的 id 值 (注意 : UserServiceImpl 类中 要有对应的 set 方法) public void setUserDAO(UserDAOImpl userDAO) { this.userDAO = userDAO; } --> <property name="userDAO" ref = "userDAOImpl"></property> </bean> <bean id="userDAOImpl" class="com.lte.DAO.Impl.UserDAOImpl"></bean>
-注入属性-内部 Bean 和级联赋值
创建了 Employee 类 和 Employee 类,Employee 类的其中一个属性为 Department 类型
<!-- 1. 可以使用外部Bean的方式--> <!-- 2. --> <bean id="emp" class="com.lte.domain.Employee"> <property name="name" value="admin"></property> <property name="age" value="123"></property> <!-- 设置对象类型属性 --> <property name="dep"> <bean id="dep" class="com.lte.domain.Department"> <property name="dname" value="财务部"></property> </bean> </property> </bean>
级联赋值
<!-- 1. 外部Bean的方式可以看成级联赋值的一种--> <!-- 2.在外部Bean的基础上--> <bean id="emp" class="com.lte.domain.Employee"> <property name="name" value="admin"></property> <property name="age" value="123"></property> <property name="dep" ref="dep"></property> <!-- (注意 : 需要定义 dep 属性的 get 方法才能使用此种方式)--> <property name="dep.dname" value="技术部"></property> </bean> <bean id="dep" class="com.lte.domain.Department"> <property name="dname" value=""></property> </bean>
数组 ,List ,Map ,Set 集合类型的注入
<!-- 数组 --> <property name="courses"> <array> <value></value> <value></value> </array> </property> <!-- List 集合--> <property name="lists"> <list> <value></value> <value></value> </list> </property> <!-- map集合 --> <property name="maps"> <map> <entry key="" value=""></entry> <entry key="" value=""></entry> </map> </property> <!-- set --> <property name="sets"> <set> <value></value> <value></value> </set> </property>
集合类型为对象
<bean id="" class=""> <property name="ObjectList"> <list> <ref bean="object1"></ref> <ref bean="object2"></ref> </list> </property> </bean> <bean id="object1" class="xxx.xx"> <property name="" value=""></property> </bean> <bean id="object2" class="yyy.yy"> <property name="" value=""></property> </bean>
提取公共的集合注入部分( list 为例)
spring 配置文件中引入名称空间(在 beans 标签里设置) xmlns:util="http://www.springframework.org/schema/util" http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd util标签完成集合的注入提取 <util:list id="lists"> <value>aaaa</value> <value>bbbb</value> </util:list> 注入使用 <property name="list" ref="lists"></property>
FactoryBean
Spring 的两种类型 bean : (1) 普通 bean ,(2)工厂bean(FactoryBean)
普通 bean :在配置文件中定义的 bean 类型,返回值为相同类型
工厂 bean :配置文件中定义的 bean 类型可以和返回类型不一样
//自定义的类实现 FactoryBean 接口 ,重写里面的方法
//配置文件,创建对象
public class FactoryBeanImpl implements FactoryBean
Bean 的作用域
创建的 Bean 实例 : (1)单实例(默认) ,(2)多实例
<!--
singleton : 单实例,默认,加载配置文件时创建
prototype : 多实例,调用 getBean方法时创建
-->
<!-- 设置为多实例 -->
<bean id="user" class="com.lte.User" scope="prototype">
</bean>
Bean 的生命周期
-
创建 bean 实例
-
注入属性(设置属性值)
-
调用 bean 的初始化方法
init-method="初始化需要执行的方法名(不需要括号)"
-
使用 bean (获取对象)
-
销毁 bean (容器关闭时,调用 bean 的销毁方法)
destroy-method="销毁 bean 之后需要执行的方法名(不需要括号)"
在 bean 的后置处理器的情况下,生命周期有七步
在 3.调用 bean 的初始化方法 之前,bean 的实例传递给 bean 后置处理器的 postProcessBeforeInitialization 方法
在 3.调用 bean 的初始化方法 之后,bean 的实例传递给 bean 后置处理器的 postProcessAfterInitialization 方法
//实现 BeanPostProcessor 接口,重写两个方法
public class MyBean 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 bean;
}
}
//配置后置处理器
//会对所在配置文件中的所有 bean 添加后置的处理
<bean id="myBean" class="com.lte.MyBean"></bean>
自动装配
根据指定的装配规则,自动将匹配的属性值进行注入
<!-- 根据属性和名称自动注入 或者其它方式 -->
<!-- 根据属性名称自动注入 -->
<bean id="emp" class="com.lte.domain.Employee" autowire="byName">
<property name="name" value="admin"></property>
<property name="age" value="123"></property>
<property name="list" ref="lists"></property>
</bean>
<!-- 注入值的 bean id 值要和类属性名称一致 -->
<bean id="dep" class="com.lte.domain.Department">
<property name="dname" value="财务部"></property>
</bean>
引入外部属性文件
//引入 druid 数据库连接池的配置文件为例
spring 配置文件中引入名称空间(在 beans 标签里设置)
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
//引入外部属性文件的标签
<context:property-placeholder location="classpath:配置文件全名.后缀"/>
<bean id="" class="com.alibaba.druid.pool.DruidDataSource">
//${} : spring 中的表达式,填写内容为数据库配置文件中等号左边的属性名称
<property name="driverClass" value="${driverClass}"></property>
<property name="url" value="${url}"></property>
......
</bean>
注解方式
spring 中针对 Bean 管理中创建对象提供的四种注解,都可以用来创建 bean 实例
- @Component
- @Service
- @Controller
- @Repository
创建对象
-
引入相关依赖 (aop-jar)
-
开启组件扫描
引入名称空间 xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 开启组件扫描 如果要扫描多个包,包之间用逗号隔开 或 直接扫描上层目录 <context:component-scan base-package="com.lte.domain"/>
关于组件扫描的两个常见示例
示例一
<!-- use-default-filters="false" : 表示不使用默认的 filter,而是自己配置 filter context:include-filter : 扫描的内容 --> <context:component-scan base-package="com.lte.domain" use-default-filters="false"> <!-- 只扫描含有 org.springframework.stereotype.Component 注解所在的类 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/> </context:component-scan>
示例二
<!-- 排除指定的内容 --> <context:component-scan base-package="com.lte.domain"> <!-- org.springframework.stereotype.Component 注解所在的类不扫描 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/> </context:component-scan>
-
在创建的类上添加注解(注解的 value 如果没有赋值,默认为该类的名称并且首字母小写)
属性注入
-
@Autowired : 根据属性类型
//在属性上边使用注解 @Autowired private UserService service;
-
@Qualifier : 根据属性名称
//需要配合 @Autowired 使用 @Autowired //value : 对应类的注解的 value 值 //并且不需要设置 set 方法 @Qualifier(value = "userServiceImpl") private UserService service;
-
@Resource : 根据属性类型 或 名称
@Resource : 根据类型 @Resource(name = "xx") : 根据名称 (Resource 是 import javax.annotation.Resource 下的,不属于 spring)
-
@Value : 注入普通类型(以上三种主要用于对象类型)
@Value(value="admin") private String name;
完全注解开发 —>(springboot)
AOP 面向切面编程
面向切面编程,降低业务逻辑的各个部分之间的耦合度。
AOP 底层原理
AOP 底层使用动态代理,其中包含两种情况的动态代理 :
1. 有接口情况 :使用 JDK 中的动态代理 (创建接口实现类的代理对象)
2. 无接口情况 : 使用 CGLIB 动态代理 (创建子类的代理对象)
JDK 提供的动态代理
java.lang.reflect.Proxy 下提供的 newProxyInstance 方法 可以返回指定接口的代理类的实例
newProxyInstance 方法提供的三个参数 :
1.类加载器
2.被代理类所实现的接口,传入的参数为 Class 类型的数组
3. 实现 InvocationHandler 接口,创建代理对象,添加增强的方法
演示
1.创建 UserService 接口,UserServiceImpl 为该接口实现类(被代理)
2.实现 InvocationHandler 接口
//代理类
class UserServiceProxy implements InvocationHandler{
private Object obj; //被代理的对象
public UserServiceProxy(Object obj) {
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//方法增强
System.out.println(method.getName() + "方法执行前...");
Object invoke = method.invoke(obj, args);
System.out.println(method.getName() + "方法执行后...");
return invoke;
}
}
3.调用 newProxyInstance 方法实现代理
//所在的类为 DynamicProxy
Class[] interfaces = {UserService.class};
UserServiceImpl userService = new UserServiceImpl();
UserService userService1 = (UserService) Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(), interfaces, new UserServiceProxy(userService));
AOP 常见操作术语
-
连接点
类中的哪些方法可以被增强,这些方法称为连接点
-
切入点
实际被增强的方法,称为切入点
-
通知(或增强)
实际增加的逻辑部分称为通知 或者 增强
类型 :前置 ,后置 ,环绕 , 异常 , 最终通知
-
切面
把通知应用到切入点的过程,可以看成一个动作
AOP 操作
spring 框架一般基于 AspectJ 实现 AOP 操作,AspectJ 不是 spring 的组成部分,一般和spring 框架一起使用。
基于 AspectJ 实现 AOP 操作 :(1) xml 配置文件实现 (2) 注解实现
切入点表达式 :明确对某个类中的某个方法进行增强
语法结构 :execution( [权限修饰符 ( * :任意修饰符) ] [返回类型 (无返回类型可以省略) ] [类全路径 (* :包下的所有类) ] [方法名称(* :该类下的所有方法) ] [ 参数列表 (用两个点表示参数) ] )
AspectJ 注解
-
创建 User(被增强类) 类和 UserProxy 类(增强类)
-
使用注解创建 User 和 UserProxy 对象
@Component public class User { ... } @Component public class UserProxy{ ... }
-
在增强类上面添加注解 @Aspect
@Component @Aspect public class UserProxy{ ... }
-
spring 配置文件中开启组件扫描 和 开启生成代理对象
<!-- 引入名称空间 --> xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd <!-- 开启自动扫描--> <context:component-scan base-package="com.lte"></context:component-scan>
<!-- 引入名称空间 --> xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd <!-- 开启Aspectj生成代理对象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
配置不同的通知
@Component @Aspect public class UserProxy { // User 类中的 show() 方法作为切入点 //前置通知(因为在方法前执行,所有无论有没有异常都会执行) @Before(value = "execution(* com.lte.aopanno.User.show(..))") public void before(){ System.out.println("before..."); } //最终通知(无论有没有异常都执行) @After(value = "execution(* com.lte.aopanno.User.show(..))") public void after(){ System.out.println("after..."); } //后置通知(不出现异常,并且在方法返回结果之后执行) @AfterReturning(value = "execution(* com.lte.aopanno.User.show(..))") public void afterReturning(){ System.out.println("afterReturning..."); } //异常通知(出现异常时执行) @AfterThrowing(value = "execution(* com.lte.aopanno.User.show(..))") public void afterThrowing(){ System.out.println("afterThrowing..."); } //环绕通知(出现异常时不执行) @Around(value = "execution(* com.lte.aopanno.User.show(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕之前..."); //表示被增强的方法执行 proceedingJoinPoint.proceed(); System.out.println("环绕之后"); } }
抽取公共切入点
//使用 @Pointcut 对相同切入点进行抽取 @Pointcut(value = "execution(* com.lte.aopanno.User.show(..))") public void publicpiont(){} //将方法名赋给value @Before(value = "publicpiont()") public void before(){System.out.println("before...");}
优先级 :
多个不同的增强类,对同一个方法进行增强,设置增强类优先级
//通过注解 @Order 设置优先级 ,值越小优先级越高 @Order(3) public class UserProxy{ ... }
使用 JdbcTemplate 操作数据库
Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 实现对数据库的操作
操作示例
-
导入相关依赖 和 druid 连接池的配置文件
#druid 部分配置文件 jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/android?rewriteBatchedStatements = true&serverTimezone=UTC jdbc.username=root jdbc.password=123123
-
在 spring 配置文件中 对数据库连接池进行配置
<!-- 引入相关的名称空间--> <!-- 配置数据库连接池 --> <!-- 读取druid配置文件--> <context:property-placeholder location="classpath:druid.properties"/> <!-- mysql8 以下--> <!-- <bean id="druid" class="com.alibaba.druid.pool.DruidDataSource"> --> <!-- mysql8 以上(如果使用8以下的方法,会出现 严重 的警告)--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 开启组件扫描--> <context:component-scan base-package="com.lte"></context:component-scan>
-
配置 JdbcTemplate对象,注入DataSource
<!-- 配置 jdbcTemplate 对象,注入 DataSource--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入 dataSource--> <property name="dataSource" ref="dataSource"></property> </bean>
-
创建 service 类和 dao 类,在 dao 类中注入 JdbcTemplate 对象
//UserDAO 的实现类 @Repository(value = "UserDAOImpl") public class UserDAOImpl implements UserDAO { //注入JDBCTemplate @Autowired private JdbcTemplate jdbcTemplate; }
//UserService 的实现类 @Service(value = "UserServiceImpl") public class UserServiceImpl implements UserService { //注入DAO @Qualifier(value = "UserDAOImpl") private UserDAO userDAO; }
-
操作数据库
插入数据
//传入的两个参数 sql语句 和 可变形参 @Override public void add(User user) { String sql = "insert into users values(?,?,?)"; Object[] obj = {user.getId(),user.getUsername(),user.getPassword()}; int row = jdbcTemplate.update(sql, obj); System.out.println(row); }
删除 和 修改操作也是通过调用 update 方法进行操作
查询数据
@Override public int findCount() { String sql = "select count(*) from users"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class); return count; }
@Override public User findOne(String id) { String sql = "select * from users where id =?"; return jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<User>(User.class),id); }
@Override public List<User> findAll() { String sql = "select * from users"; return jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class)); }
获取连接时出现的异常
异常 :org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLException: Access denied for user ‘86152’@‘localhost’ (using password: YES)
Caused by: java.sql.SQLException: Access denied for user ‘86152’@‘localhost’ (using password: YES)
原因 :properties 文件 username 被系统环境变量替代了,导致无法连接成功(不是 86152’@‘localhost 而应该是 root’@'localhost)
解决方法:
修改 username 为 user 或其他名称 (例如 :username 修改为 jdbc.username )
spring导入properties 文件的标签中添加属性:system-properties-mode=“FALLBACK” 不加时属性值默认为 NVIRONMENT
// 练习时用到的配置标签
<context:property-placeholder location=“classpath:druid.properties” system-properties-mode=“FALLBACK”/>
批量操作
@Override
public int[] batchAddUser(List<Object[]> lists) {
/* String sql = "update users set username = ?,password = ? where id = ?";*/
String sql = "insert into users values(?,?,?)";
//batchUpdate : 可以进行批量 插入,修改,删除
int[] ints = jdbcTemplate.batchUpdate(sql,lists);
return ints;
}
事务
数据库操作的最基本单元,逻辑上的一组操作,有一个失败,整个操作都会失败
事务的四个特性(ACID) : (1)原子性 (2)一致性 (3)隔离性 (4)持久性
利用 spring 进行事务管理
编程式事务声明
(一般不用,略…)
声明式事务管理(注解方式)
spring 中进行声明式事务管理,底层使用 AOP 原理
使用事务-示例
-
引入 tx 名称空间
-
创建事务管理器
<!-- 创建事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
-
开启事务注解
<!-- 开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
添加事务注解
@Transactional 添加到类上,类中的所有方法都会添加注解 添加到方法上,只为当前方法添加事务
@Transactional 常用参数
属性名 | 表示 |
---|---|
propagation | 事务传播行为 |
isolation | 事务隔离级别 |
timeout | 超时时间 |
readOnly | 是否只读 |
rollbackFor | 回滚 |
noRollbackFor | 不回滚 |
事务传播行为( propagation ) :多事务方法直接进行调用,这个过程中事务是如何进行管理的
@Transactional(propagation = Propagation.REQUIRED)
事务方法 :增 删 改等对数据库表数据进行变化的操作
事务传播行为 :
事务隔离级别( isolation ) : **解决 脏读,不可重复读,幻读 这三个问题 **
1. **脏读 :当一个事务读取另一个事务尚未提交的修改时,产生脏读**
2. **不可重复读 :同一查询在同一事务中多次进行,由其它提交事务所做的修改或删除,每次返回不同的结果集,此时发生不可重复读**
3. **幻读 : 同一查询在同一事务中多次进行,由于其他提交事务所作的**插入**操作,每次返回不同的结果集,此时发生幻读。**
//一共存在四种隔离级别
//Isolation.REPEATABLE_READ : 可重复读,不加锁 ,一般为 mysql 默认级别
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
超时时间( timeout ) : 事务需要在一定时间内进行提交,超出时间还未提交则进行回滚
默认值 :-1 (表示不超时) , 设置时间以 秒 为单位
timeout = -1 //不超时
timeout = 10 //超时时间10秒
是否只读 ( readOnly ):设置成 true 后,只能进行查询(读) ,不能进行增删改(写)
readOnly = true //默认 false
**回滚 ( rollbackFor ) : 设置出现哪些异常进行事务回滚 **
rollbackFor = NullPointerException.class
不回滚 ( noRollbackFor ) : 设置出现的哪些属性不进行回滚
noRollbackFor = NullPointerException.class
声明式事务管理(xml 方式)
-
配置事务管理器
<!-- 创建事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
-
配置通知
<!-- 配置通知--> <tx:advice id = "txadvice"> <tx:attributes> <!-- 指定在那种规则上面添加通知--> <tx:method name="transfer" propagation="REQUIRED"/> <!-- 方法名以 transfer 开头的添加事务 <tx:method name="transfer*"/> --> </tx:attributes> </tx:advice>
-
配置切入点和切面
<!-- 设置切入点和切面--> <aop:config> <!-- 设置切入点--> <aop:pointcut id="pt" expression="execution(* com.lte.service.AccountService.*(..))"/> <!-- 设置切面(将事务 txadvice 的通知设置到切入点 pt 上)--> <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/> </aop:config>
声明式事务管理(完全注解)
(省略------,尚硅谷 spring p-49)
其它一些特性
@Nullable 注解
可以使用在 类 \方法\属性\参数\上面,表示方法\属性值\参数值\返回可以为空
WebFlux
**Spring WebFlux :功能和 SpringMVC 类似,但使用的是响应式编程框架,相对于传统框架(例如 SpringMVC), WebFlux 是一种 异步非阻塞框架(该种框架在 Servlet 3.1 以后才支持的),核心是基于 Reactor 相关 API 实现的。 **
特点 :非阻塞式 ,在有限资源下,提高系统吞吐量和伸缩性,使用函数式编程实现路由请求。默认使用容器是 Netty,Netty 为高性能 NIO 异步非阻塞框架,
*WebFlux 其它知识学习中~~~~*