Spring5框架🍻
一、Spring框架概述
1. 什么是Spring?
Spring是一种轻量级的开源JavaEE框架
2. Spring有什么用?
Spring解决企业应用开发的复杂性
3. Spring的核心组成
Spring有两大 核心 组成部分:IOC和AOP
- IOC: 控制反转,创建对象的过程交给Spring进行管理
- AOP: 面向切面,不修改源代码的情况下进行功能的添加或增强
4. Spring的特点
- 方便解耦,简化开发(IOC)
- Aop编程的支持(不改变源代码,可以进行功能的增强或添加)
- 方便测试
- 方便和其他框架进行整合
- 方便进行事务管理
- 降低API使用难度
二、入门案例
1. 下载地址
https://repo.spring.io/ui/repos/tree/General/release%2Forg%2Fspringframework%2Fspring
2. 需要的jar包
-
Spring 自身 JAR 包
- spring-beans-4.0.0.RELEASE.jar
- spring-context-4.0.0.RELEASE.jar
- spring-core-4.0.0.RELEASE.jar
- spring-expression-4.0.0.RELEASE.jar
-
commons-logging-1.1.1.jar
3. 创建一个普通的类,在类中创建普通的方法
4. 创建Spring配置文件
创建Spring配置文件,在配置文件中创建对象
- Spring配置文件使用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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--配置User类对象的创建-->
<bean id="user" class="cn.edu.tjpu.User"></bean>
</beans>
5. 测试
package cn.edu.tjpu;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class UserTest01 {
@Test
public void testAdd() {
// 1. 加载Spring的配置文件
ApplicationContext context = new FileSystemXmlApplicationContext("E:\\SSM\\Spring\\SpringDemo01\\src\\main\\java\\bean1.xml");
// 2. 获取配置创建的对象
User user = context.getBean("user", User.class);
System.out.println(user);
user.add();
}
}
三、IOC容器
1. IOC和概念底层原理
1.1 什么是IOC?
- 控制反转(Inversion of Control,缩写为IoC),把对象的创建和对象之间的调用过程,交给Spring进行管理
- 使用IOC的目的:降低耦合度
- 入门案例就是IOC的实现
1.2 IOC的底层原理
-
用到的技术:xml解析、工厂模式、反射
-
IOC解耦过程
-
xml配置文件,配置创建的对象
<bean id="user" class="cn.edu.tjpu.User"></bean>
-
有service类和dao类,创建工厂类
class UserFactory{ public static UserDao getDao(){ String classValue = class属性值; // 1.xml解析 // 2.通过反射创建对象 Class clazz = Class.forName(classValue); // 3.创建对象 return (UserDao)clazz.newInstance(); } }
-
进一步降低耦合度
-
2. IOC接口(BeanFactory)
-
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
-
Spring提供了IOC容器的两种实现方式(两个接口):
-
BeanFactory:IOC容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用
-
特点:加载配置文件时,不会创建对象,在获取对象(适用对象)才去创建对象
-
BeanFactory context = new FileSystemXmlApplicationContext("E:\\SSM\\Spring\\SpringDemo01\\src\\main\\java\\bean1.xml"); // 在这一步创建对象 User user = context.getBean("user", User.class);
-
-
ApplicationContext:BeanFactory接口的子接口,提供了更多更强大的功能,一般由开发人员进行使用
-
特点:加载配置文件时候,就会把在配置文件中的对象进行创建
-
// 在这一步创建对象 ApplicationContext context = new FileSystemXmlApplicationContext("E:\\SSM\\Spring\\SpringDemo01\\src\\main\\java\\bean1.xml");
-
-
-
ApplicationContext接口的实现类
FileSystemXmlApplicationContext
:全路径ClassPathXmlApplicationContext
:在src下
- BeanFactory接口中的实现类
3. IOC操作 Bean管理
3.1 什么是Bean管理
Bean管理操作是两个操作:
- Spring创建对象
- Spring注入属性
3.2 基于xml方式
-
创建对象
-
<bean id="user" class="cn.edu.tjpu.User"></bean>
-
在Spring配置文件中,使用bean标签,标签里面添加对应的属性,就可以实现对象创建
-
在bean标签中的常用属性:
- id:唯一标识
- class:类全路径(包和类路径)
-
创建对象时候,默认也是执行无参构造函数
-
-
注入属性
<property>
标签-
DI:依赖注入,就是注入属性(DI和IOC有什么区别?DI是IOC中的一种具体实现)
-
第一种注入方式:使用set方法进行注入
-
创建一个类,定义属性和对应的set方法
/** * 演示使用set方法进行属性注入 */ public class Book { private String name; private String author; public String getName() { return name; } public String getAuthor() { return author; } }
-
在Spring配置文件配字对象创建,再配置属性注入
<bean id="book" class="cn.edu.tjpu.Book"> <!--使用property标签完成属性注入 name:属性 value:属性对应的值 --> <property name="name" value="algorithm4"></property> <property name="author" value="Robert Sedgewick"></property> </bean>
-
-
第二种注入方式:使用有参构造
<constructor-arg>
标签-
创建一个类,定义属性并创造对应的有参构造函数
-
public class Orders {
private String name; private String address; public Orders(String name, String address) { this.name = name; this.address = address; } } ```
-
配置Spring配置文件
<bean id="orders" class="cn.edu.tjpu.Orders"> <constructor-arg name="name" value="算法4"></constructor-arg> <constructor-arg name="address" value="天津"></constructor-arg> </bean>
-
p名称空间注入(了解)
-
-
XML注入其他属性
-
字面量
-
null值:
<property name="author"> <null/> </property>
-
包含特殊符号
<!--属性值包含特殊符号 1. 把<>转义 2. 把带特殊符号的内容写到CDATA中去 --> <!--第一种方式:转义--> <property name="author" value="<南京>"></property> <!--第二种方式:CDATA--> <property name="author"> <value><![CDATA["南京"]]></value> </property>
-
-
-
注入属性-外部bean(service中使用dao)
-
创建两个类:service和dao。
-
在service调用dao里面的方法
-
在Spring配置文件中进行配置
<!--创建service对象--> <bean id="UserService" class="service.UserService"> <!--注入UserDao对象 name属性值:类里面的属性名称 ref属性:创建userDao对象bean标签的id值 --> <property name="userDao" ref="UserDaoImpl"> </property> </bean> <!--创建dao对象--> <bean id="UserDaoImpl" class="dao.UserDaoImpl"> </bean>
public class UserService { // 创建UserDao类型的属性,生成set方法 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add() { System.out.println("service add..."); userDao.update(); // 普通方式:创建dao的对象 /*UserDao dao = new UserDaoImpl(); dao.update();*/ } }
-
-
注入属性-内部bean
-
一对多关系:部门和员工。一个部门可以有多个员工,一个员工属于一个部门。部门是一,员工是多。
-
实体类之间表示一对多的关系,在员工类中,使用对象类型属性进行表示员工所属部门
-
在Spring配置文件中进行相关的配置
-
代码:
/** * 部门类 */ public class Department { private String deptName; /*// 一个部门中有多个员工 private Employee[] employees;*/ /*public void setEmployees(Employee[] employees) { this.employees = employees; }*/ public void setDeptName(String deptName) { this.deptName = deptName; } @Override public String toString() { return "Department{" + "deptName='" + deptName + '\'' + '}'; } }
/** * 员工类 */ public class Employee { private String empName; private String gender; // 员工属于某一个部门 private Department department; public void setDepartment(Department department) { this.department = department; } public void setEmpName(String empName) { this.empName = empName; } public void setGender(String gender) { this.gender = gender; } @Override public String toString() { return "Employee{" + "empName='" + empName + '\'' + ", gender='" + gender + '\'' + ", department=" + department + '}'; } }
<!--内部bean--> <bean id="employee" class="bean.Employee"> <property name="gender" value="男"></property> <property name="empName" value="hyb"></property> <property name="department"> <bean class="bean.Department" id="department"> <property name="deptName" value="开发部"></property> </bean> </property> </bean>
-
-
注入属性-级联复制
-
第一种方式:
<!--级联复制--> <bean class="bean.Employee" id="employee"> <property name="empName" value="ramsey"></property> <property name="gender" value="男"></property> <!--级联赋值--> <property name="department" ref="department"></property> </bean> <bean class="bean.Department" name="department"> <property name="deptName" value="开发部"></property> </bean>
-
第二种方式:
- 需要把Employee类中Department属性的get方法生成
<bean class="bean.Employee" id="employee"> <property name="empName" value="ramsey"></property> <property name="gender" value="男"></property> <property name="department" ref="department"></property> <property name="department.deptName" value="财务部"></property> </bean> <bean class="bean.Department" name="department"> </bean>
-
-
xml注入数组、List、Map和Set类型属性
- 在
<property>
标签中添加<list>
,<map>
,<array>
,<set>
标签 - 其中
<map>
标签中<entry key="语文" value="80"></entry>
- 其他三个标签,均在内部添加
<value>
标签
public class Student { private String[] courses; private List<String> list; private Map<String, String> map; private Set<String> set; public void setList(List<String> list) { this.list = list; } public void setMap(Map<String, String> map) { this.map = map; } public void setSet(Set<String> set) { this.set = set; } public void setCourses(String[] courses) { this.courses = courses; } @Override public String toString() { return "Student{" + "courses=" + Arrays.toString(courses) + ", list=" + list + ", map=" + map + ", set=" + set + '}'; } }
<bean class="collection.Student" id="student"> <property name="courses" > <array> <value>java</value> <value>database</value> </array> </property> <property name="list"> <list> <value>张三</value> <value>李四</value> </list> </property> <property name="map"> <map> <entry key="语文" value="80"></entry> <entry key="数学" value="90"></entry> </map> </property> <property name="set"> <set> <value>MySQL</value> <value>Redis</value> <value>Redis</value> </set> </property> </bean>
- 在
-
在集合里面设置对象类型的值
<bean class="collection.Student" id="student"> <!--注入list集合类型,值是对象--> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> </bean> <!--创建多个course对象--> <bean class="collection.Course" id="course1"> <property name="courseName" value="Spring5"></property> </bean> <bean class="collection.Course" id="course2"> <property name="courseName" value="MyBaits"></property> </bean>
-
把集合注入的部分提取出来
-
在Spring配置文件中引入名称空间 util
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
-
使用util标签完成list集合注入的提取
<!--提取list集合类型属性注入--> <util:list id="book_list"> <value>算法4</value> <value>算法导论</value> <value>多目标进化优化</value> </util:list> <bean class="collection.Book" id="book"> <property name="list" ref="book_list"></property> </bean>
-
3.3 FactoryBean
- Spring中有两种类型的bean:普通bean(自己创建的)和工厂bean(factory bean)
- 普通bean:在Spring配置文件中,定义的bean类型就是返回类型
- 工厂bean:在配置文件中定义的bean类型可以和返回值类型不一样
- 第一步:创建一个类,作为工厂bean,实现一个接口FactoryBean
- 第二步:实现接口方法,在实现的方法中定义返回的bean类型
3.4 Bean的作用域
-
在Spring里面,设置创建bean实例是单实例还是多实例
-
在Spring里面,在默认情况下,创建的bean是一个单实例对象
-
设置多实例
- 在Spring配置文件bean标签里面有一个scope属性,设置多实例。
- scope属性值
- 默认值:singleton。单实例对象
- prototye,多实例对象
- request
- session
- singleton和prototype区别
- singleton表示单实例;prototype表示多实例
- 设置singleton时,加载Spring配置文件时,就是创建单实例对象;设置prototype时,不是在加载Spring配置文件时创建对象,在调用getBean方法时,创建多实例对象
3.5 Bean的生命周期
-
生命周期
- 从对象创建到对象销毁的过程
-
bean生命周期(5步)
- 通过构造器创建bean实例(无参数构造方法)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 调用bean的初始化的方法
- bean可以使用了(对象获取到了)
- 当容器关闭时候,调用bean的销毁方法(需要进行配置销毁的方法)
-
代码
public class Person { private String name; public Person() { System.out.println("第一步 执行无参数构造创建bean实例"); } public void setName(String name) { System.out.println("第二步 调用set方法,设置属性的值"); this.name = name; } // 创建执行的初始化方法 public void initMethod() { System.out.println("第三步 执行初始化的方法"); } // 创建销毁时执行的方法 public void destroyMethod() { System.out.println("第五步 销毁的方法"); } }
public class TestPerson { @Test public void test01() { ApplicationContext context = new FileSystemXmlApplicationContext("E:\\SSM\\Spring\\SpringDemo01\\src\\bean01.xml"); Person person = context.getBean("person", Person.class); System.out.println("第四步 获取创建bean实例对象"); // 手动让bean的实例销毁 } }
<bean class="bean.Person" id="person" init-method="initMethod" destroy-method="destroyMethod"> <property name="name" value="ramsey"> </property> </bean>
-
bean的后置处理器(7步)
- 通过构造器创建bean实例(无参数构造方法)
- 为bean的属性设置值和对其他bean引用(调用set方法)
- 把bean实例传递给bean后置处理器方法
- 调用bean的初始化的方法
- 把bean实例传递给bean后置处理器方法
- bean可以使用了(对象获取到了)
- 当容器关闭时候,调用bean的销毁方法(需要进行配置销毁的方法)
3.6 自动装配
-
什么是自动装配?
- 根据指定装配规则(属性名称或属性类型),Spring自动将匹配的属性值进行注入
-
演示自动装配
-
根据属性名称进行自动装配
<!--自动装配 bean标签中autowire,配置自动装配 autowire属性常用两个值: byName根据属性名称注入:注入值bean的id值和类属性名称一样 byType根据属性类型注入 --> <bean class="autowire.Emp" id="emp" autowire="byName"> </bean>
-
根据属性类型进行自动装配
<bean class="autowire.Emp" id="emp" autowire="byType"> </bean>
-
3.7 引入外部属性文件
-
直接配置数据库信息
-
配置Druid连接池
-
引入Druid连接池jar包
<!--直接配置--> <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <property name="driverClassLoader" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/mysql_base"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean>
-
-
引入外部属性文件配置数据库连接池**【重点】**
-
创建外部属性文件,properties格式文件,写数据库格式信息
prop.driverClass = com.mysql.jdbc.Driver prop.url = jdbc:mysql://localhost:3306/mysql_base prop.username = root prop.password = root
-
把外部properties文件引入到spring属性文件中去
-
引入名称空间
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" 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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
-
<!--引入外部属性文件--> <context:property-placeholder location="src/jdbc.properties"/> <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <property name="driverClassLoader" value="${prop.driverClass}"></property> <property name="url" value="${prop.url}"></property> <property name="username" value="${prop.username}"></property> <property name="password" value="${prop.password}"></property> </bean>
-
-
4. Bean管理注解方式
4.1 创建对象
-
什么是注解?
- 注解是代码中特殊标记。
- 格式:@注解名称(属性名称1=属性值1,属性名称2=属性值2…)
- 注解可以作用在类上面、方法上面、属性上面
- 使用注解目的:简化xml配置(更优雅、更简洁)
-
Spring针对bean管理中创建对象提供注解
-
@Component
-
@Service
-
@Controller
-
@Repository
-
上面的四个注解功能是一样的,都可以用来创建bean实例
-
-
基于注解方式实现对象的创建(3步)
-
引入依赖:aop的jar包
-
开启组件扫描
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 开启组件扫描 1. 如果要扫描多个包,多个包使用逗号隔开 2. 扫描包的上层目录 --> <context:component-scan base-package="cn.edu.tjpu"> </context:component-scan> </beans>
-
创建类,在类上面添加创建对象的注解
// 在注解里面value可以省略不写 // 默认值就是类名称,且首字母小写 //@Component(value = "userService") // <bean id="userService" class="..."></bean> @Service public class UserService { public void add() { System.out.println("Service add method..."); } }
-
开启组件扫描的细节
<!--示例1:只扫描cn.edu.tjpu下带有Controller注解的类--> <context:component-scan base-package="cn.edu.tjpu" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!--示例2:只扫描cn.edu.tjpu下除去带有Controller注解的类--> <context:component-scan base-package="cn.edu.tjpu" use-default-filters="false"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
-
4.2 基于注解方式的属性注入
-
@Autowired
:根据属性**【类型】**自动装配(对象类型)- 把service和dao对象创建,在service和dao类添加创建对象的注解
- 在service里面注入dao对象
@Service public class UserService { // 定义dao类型属性,不需要添加set方法 @Autowired private UserDao userDao; public void add() { System.out.println("Service add method..."); userDao.add(); } }
@Repository public class UserDaoImpl implements UserDao { @Override public void add() { System.out.println("UserDao add method..."); } }
-
@Qualifier
:根据属性**【名称】**进行注入(对象类型)@Qualifier
和@Autowired
一起使用- 用于解决某个对象类型可能有多个值,去找具体的哪一个值。如:某个接口有多个实现类,使用
@Qualifier
去找具体使用哪个实现类
-
@Resource
:可以根据类型注入,可以根据名称注入(对象类型)- 它属于
import javax.annotation.Resource;
@Resource(name = "userDaoImpl") // 根据类型进行注入 private UserDao userDao;
- 它属于
-
@Value
:注入普通类型属性
4.3 完全注解开发
-
创建配置类,替代xml的配置文件
@Configuration // 作为配置类,替代xml配置文件 @ComponentScan(basePackages = {"cn.edu.tjpu"}) public class SpringConfig { }
-
编写测试类
@Test public void test02() { // 加载配置类 ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService userService = context.getBean("userService", UserService.class); userService.add(); }
四、AOP
1. AOP概念和底层原理
1.1 什么是AOP
-
AOP为Aspect Oriented Programming的缩写。意为:面向切面(方面)编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
-
通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
-
使用登录例子说明AOP
1.2底层原理
-
AOP底层使用动态代理
-
有两种情况动态代理:
- 第一种:有接口的情况,使用JDK动态代理
- 创建接口实现类的代理对象,增强类的方法
-
第二种:没有接口的情况,使用CGLIB动态代理
-
创建子类的代理对象,增强类的方法
-
1.3 JDK动态代理
-
使用JDK动态代理,使用Proxy类里面的方法实现动态代理,在java.lang包
-
调用newProxyInstance方法:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- 参数1:类加载器
- 参数2:增强方法所在的类,这个类实现的接口,支持多个接口
- 参数3:实现里面的接口InvocationHandler,创建代理对象,写增强的方法
-
编写JDK动态代理代码
-
创建接口,定义方法
public interface UserDao { public abstract int add(int a, int b); public abstract String update(String id); }
-
创建实现类,实现方法
public class UserDaoImpl implements UserDao{ @Override public int add(int a, int b) { return a + b; } @Override public String update(String id) { return id; } }
-
使用Proxy类创建接口的代理对象
public class JDKProxy { public static void main(String[] args) { Class[] interfaces = {UserDao.class}; // 创建接口实现类的代理对象 UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(new UserDaoImpl())); int res = dao.add(1, 2); System.out.println(res); } } // 常见代理对象代码 class UserDaoProxy implements InvocationHandler { // 1. 把床的谁的代理对象,把谁传递进来 // 有参构造 private Object obj; public UserDaoProxy(Object obj) { this.obj = obj; } // 增强的逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 方法之前处理 System.out.println("方法之前执行..." + method.getName() + ":传递参数" + Arrays.toString(args)); // // 被增强的方法执行 Object res = method.invoke(obj, args);// // 方法之后处理 System.out.println("方法之后执行..." + obj); return res; } }
-
2. AOP术语
2.1 连接点
在一个类中,哪些方法可以被增强,那这些方法就是连接点
2.2 切入点
实际上,被真正增强的方法,成为切入点
2.3 通知(增强)
-
实际增强的逻辑的部分,称为通知(增强)
-
通知有多种类型:
- 前置通知
@Before(value = "execution(* cn.edu.tjpu.aop.User.add(..))")
:在方法执行前 - 后置通知
@AfterReturning("execution(* cn.edu.tjpu.aop.User.add(..))")
:在方法执行后 - 环绕通知
@Around("execution(* cn.edu.tjpu.aop.User.add(..))")
:在方法执行前、后都 - 异常通知
@AfterThrowing("execution(* cn.edu.tjpu.aop.User.add(..))")
:出现异常时 - 最终通知
@After("execution(* cn.edu.tjpu.aop.User.add(..))")
:类似finally,无论出不出现异常,都会执行
/** * 增强的类 */ @Component @Aspect // 生成代理对象 public class UserProxy { /** * 前置通知 */ @Before(value = "execution(* cn.edu.tjpu.aop.User.add(..))") public void before() { System.out.println("before..."); } /** * 最终通知,无论有没有异常都会在方法执行后执行 */ @After("execution(* cn.edu.tjpu.aop.User.add(..))") public void after() { System.out.println("after..."); } /** * 异常通知 */ @AfterThrowing("execution(* cn.edu.tjpu.aop.User.add(..))") public void AfterThrowing() { System.out.println("AfterThrowing..."); } /** * 后置通知(返回通知):在方法返回值之后执行,如果有异常不会执行 */ @AfterReturning("execution(* cn.edu.tjpu.aop.User.add(..))") public void AfterReturning() { System.out.println("AfterReturning..."); } /** * 环绕通知 */ @Around("execution(* cn.edu.tjpu.aop.User.add(..))") public void Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕之前"); // 执行被增强的方法 proceedingJoinPoint.proceed(); System.out.println("环绕之后"); } }
- 前置通知
2.4 切面
是一个“动作”。把通知应用到切入点的过程就是切面。
3. AOP的操作
3.1 基于AspectJ
-
Spring框架中,一般基于AspectJ实现AOP操作
-
什么是AspectJ?
- AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作
-
基于AspectJ实现AOP操作
- 基于xml配置文件实现
- 基于注解方式实现**(使用)**
-
在项目工程里面引入AOP的相关依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.6.RELEASE</version> </dependency>
-
切入点表达式
- 切入点表达式的作用:知道对哪个类里面的方法进行增强
- 语法结构:
execution([权限修饰符] [返回类型] [类的全路径] [方法名称]([参数列表]))
- 举例1:对com.atguigu.dao.BookDao类里面的add方法进行增强**(某个类中的某个方法)**
execution(* com.atguigu.dao.BookDao.add(..))
- 举例2:对com.atguigu.dao.BookDao类里面的所有方法进行增强**(某个类中的所有方法)**
execution(* com.atguigu.dao.BookDao.*(..))
- 举例3:对com.atguigu.dao包里面的所有类和所有方法进行增强**(某个包中的所有类所有方法)**
execution(* com.atguigu.dao.*.*(..))
- 举例1:对com.atguigu.dao.BookDao类里面的add方法进行增强**(某个类中的某个方法)**
3.2 基于注解方式
-
创建一个类,在类里面定义一个方法
public class User { public void add() { System.out.println("User add method..."); } }
-
创建增强类(编写增强的逻辑)
-
在增强的类中,创建方法,让不同的方法代表不同类型通知
-
/** * 增强的类 */ @Component @Aspect // 生成代理对象 public class UserProxy { /** * 前置通知 */ @Before(value = "execution(* cn.edu.tjpu.aop.User.add(..))") public void before() { System.out.println("before..."); } }
-
-
进行通知的配置
-
在Spring配置文件中,开启注解扫描
<?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" xmlns:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--开启注解的扫描--> <context:component-scan base-package="cn.edu.tjpu.aop"> </context:component-scan> </beans>
-
使用注解创建User和UserProxy对象
-
- 在增强的类上面,添加注解
@Aspect
-
在Spring配置文件中开启生成代理对象
<!--开启AspectJ生成代理对象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
配置不同类型的通知
- 在增强类的里面,在作为通知的方法上面添加通知类型注解,使用切入点表达式配置
/** * 增强的类 */ @Component @Aspect // 生成代理对象 public class UserProxy { /** * 前置通知 */ @Before(value = "execution(* cn.edu.tjpu.aop.User.add(..))") public void before() { System.out.println("before..."); } }
-
测试类
public class TestAop { @Test public void test01() { ApplicationContext context = new ClassPathXmlApplicationContext("Aspect01.xml"); User user = context.getBean("user", User.class); user.add(); } }
-
相同切入点抽取
// 相同的切入点抽取 @Pointcut("execution(* cn.edu.tjpu.aop.User.add(..))") public void point() { } /** * 前置通知 */ @Before(value = "point()") public void before() { System.out.println("before..."); }
-
有多个增强类对同一个方法进行增强,设置增强类优先级
-
在增强类的上面添加注解
@Order(数字类型值)
,数字类型值越小优先级越高@Component @Aspect @Order(1) public class PersonProxy { @Before("execution(* cn.edu.tjpu.aop.User.add(..))") public void before() { System.out.println("PersonProxy Order"); } }
-
五、JdbcTemplate
1. 什么是JdbcTemplate
- Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据的操作
2. 准备工作
-
引入相关依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.6.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.26</version> </dependency>
-
在Spring配置文件中配置数据库的连接池
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql:///spring5"/> <property name="username" value="root"/> <property name="password" value="root"/> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> </bean>
-
配置JdbcTemplate对象,注入DateSource
<!--创建JdbcTemplate对象--> <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate"> <!--注入DataSource--> <property name="dataSource" ref="dataSource"> </property> </bean>
-
创建service类,创建dao类,在dao注入JdbcTemplate对象
package cn.edu.tjpu.service; public interface UserService { public abstract void select(); }
package cn.edu.tjpu.service; import cn.edu.tjpu.dao.UserDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class UserServiceImpl implements UserService { // 注入dao @Autowired private UserDao userDao; @Override public void select() { userDao.findById(); } }
package cn.edu.tjpu.dao; public interface UserDao { public abstract void findById( ); }
3. JdbcTemplate批量操作
批量操作:操作表里面多条记录
3.1 批量添加
代码:jdbcTemplate.batchUpdate(参数);
- 参数:
- sql语句
- List集合:添加的多条数据的数据
public interface UserService {
public abstract void find();
public abstract void add(User user);
public abstract void deleteById(int uid);
public abstract void batchAddUser(List<Object[]> userList);
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
@Qualifier("userDaoImpl")
private UserDao userDao;
@Override
public void find() {
userDao.find(13);
}
@Override
public void add(User user) {
userDao.add(user);
}
@Override
public void deleteById(int uid) {
userDao.deleteById(uid);
}
@Override
public void batchAddUser(List<Object[]> userList) {
userDao.batchAddUser(userList);
}
}
public interface UserDao {
public abstract void find(int id);
public abstract void add(User user);
public abstract void deleteById(int uid);
public abstract void batchAddUser(List<Object[]> userList);
}
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
@Qualifier("jdbcTemplate")
private JdbcTemplate jdbcTemplate;
@Override
public void find(int id) {
String sql = "select * from tab_user where uid=?";
User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), id);
System.out.println(user);
}
@Override
public void add(User user) {
String sql = "insert into tab_user values (null,?,?,?,?,?,?,?,null,null)";
jdbcTemplate.update(sql, user.getUsername(), user.getPassword(), user.getName(), user.getBirthday(),
user.getSex(), user.getTelephone(), user.getEmail());
}
@Override
public void deleteById(int uid) {
String sql = "delete from tab_user where uid=?";
jdbcTemplate.update(sql, uid);
}
@Override
public void batchAddUser(List<Object[]> userList) {
String sql = "insert into tab_user (uid,username,password) values (null, ?, ?)";
jdbcTemplate.batchUpdate(sql, userList);
}
}
@Test
public void testBatchAdd() {
ApplicationContext context = new ClassPathXmlApplicationContext("JdbcConfig.xml");
UserService userService = context.getBean("userServiceImpl", UserService.class);
List<Object[]> list = new ArrayList<>();
Object[] o1 = {"coco", "123123123"};
Object[] o2 = {"cococo", "123123123"};
list.add(o1);
list.add(o2);
userService.batchAddUser(list);
}
4. 事务
4.1 事务概念
- 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
- 事务用来管理 insert,update,delete 语句
- 一般来说,事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
- **原子性(不可再分割):**一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- **一致性:**在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。即操作前和操作后总量不变
- **隔离性:**数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
- **持久性:**事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
- 典型场景:银行转账
4.2 搭建事务操作的环境
- 创建数据库的表,添加记录
- 创建Service和Dao,完成对象的创建和属性注入
- 在Service里面注入dao,在dao中注入JdbcTemplate,在JdbcTemplate中注入DataSource
- 在dao中创建两个方法:多钱和少钱的方法;在service创建转账的方法
- 银行转账的代码如果正常执行,没有任何问题,如果代码在执行过程中,出现了异常,就有问题。
4.3 事务管理介绍
-
事务添加到JavaEE三层结构里面Service层(业务逻辑层)
-
在Spring里面进行事务管理操作
- 编程式事务管理
- 声明式事务管理**【常用】**
-
声明式事务管理
- 基于注解方式进行实现**【常用】**
- 基于xml配置文件方式
-
在Spring里面进行声明式的事务管理,底层使用AOP
-
Spring事务管理API
- 提供了一个接口,代表事务管理器,这个接口针对不同的框架提供了不同的实现类
4.4 注解方式实现声明式事务管理
-
在Spring配置文件中,配置事务管理器(PlatFformTransactionManager针对不同的接口有不同的实现类,所以需要配置)
<!--创建一个事务管理器--> <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
-
在spring配置文件中,开启事务注解
-
在spring配置文件中,引入名称空间tx
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
-
开启事务注解
<!--开启事务注解--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
-
在service类上面(或service类里面方法上面)添加事务注解
@Transactional
可以添加到类上面,可以添加到方法上面- 如果把这个注解添加到类上面,则表示:这个类里面所有的方法都添加事务
- 如果把这个注解添加到方法上面,即为这个方法添加事务
@Override @Transactional public void transaction(int transferId, int receiveId, double money) {
4.5 声明式事务管理参数配置
-
在service类上面添加注解
@Transactional
,在这个注解里面我们可以配置事务的相关参数 -
参数
-
propagation:事务传播行为
- 事务传播行为
- 有事务方法调用没事务方法;
- 没事务方法调用有事务方法;
- 有事务方法调用有事务方法
- 多事务方法之间进行调用,这个过程中事务是如何进行管理的
- **事务方法:**对数据库表数据进行变化的操作(增删改)
- Spring框架事务传播行为有7种
事务传播行为类型 说明 PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
如果add方法本身有事务,调用update方法之后,update使用当前add方法里面事务
如果add方法本身没有实物,调用update方法之后,创建新事务PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。 PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。 PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。 PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。 PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 - 事务传播行为
-
isolation:事务的隔离级别
-
隔离性:多事务之间操作不会产生影响。不考虑隔离性产生很多问题
-
有三个读的问题:脏读,不可重复读、虚(幻)读
-
脏读:一个未提交的事务,读取到了另一个未提交事务的数据
A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。就好像原本的数据比较干净、纯粹,此时由于B事务更改了它,这个数据变得不再纯粹。这个时候A事务立即读取了这个脏数据,但事务B良心发现,又用回滚把数据恢复成原来干净、纯粹的样子,而事务A却什么都不知道,最终结果就是事务A读取了此次的脏数据,称为脏读。
-
-
不可重复读:前后多次读取,数据内容不一致
-
事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读。
-
幻读:前后多次读取,数据总量不一致
-
事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。
-
解决:设置事务的隔离性解决以上问题
- MySQL数据库中默认的隔离级别就是
Repeatable Read
- MySQL数据库中默认的隔离级别就是
-
timeout:超时时间
- 设置在一定的时间内进行提交,如果超时则回滚
- 默认值为-1,设置时间以秒为单位
-
readonly:是否只读
- 读:查询操作
- 写:修改、添加、删除
- 默认值:false。表示可以读,也可以写
- 设置readOnly值为true时,只能读操作
-
rollbackFor:回滚
- 设置出现哪些异常进行事务回滚。
rollbackFor = { NullPointerException.class, IndexOutOfBoundsException.class}
-
noRollbackFor:不回滚
- 设置出现哪些异常不进行事务回滚
-
代码
@Override @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, timeout = 10, readOnly = false, rollbackFor = { NullPointerException.class, IndexOutOfBoundsException.class}) public void transaction(int transferId, int receiveId, double money) { // 1.开启事务操作 // 2.进行业务的操作 userDao.transfer(transferId, 100); // int i = 100/0; userDao.receiveMoney(receiveId, 100); // 3.没有发生异常,提交事务 }
4.6 xml方式声明事务管理
- 配置事务管理器,就是创建对象
- 配置通知
- 增强的那部分代码,就叫通知
- 配置切入点和切面
- 切入点:指加强类的方法
- 切面:指事务加到方法的过程
4.7 完全注解方式
-
创建配置类,使用配置类替代xml配置文件
@Configuration // 代表这是一个配置类 @ComponentScan(basePackages = {"cn.edu.tjpu"}) // 组件扫描 @EnableTransactionManagement // 开启事务 public class TxConfig { // 创建数据库的连接池 @Bean public DruidDataSource getDataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql:///:spring5"); dataSource.setUsername("root"); dataSource.setPassword("root"); return dataSource; } // 创建JdbcTemplate模板 @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource) { // 到ioc容器中根据类型找到dataSource return new JdbcTemplate(dataSource); } // 创建一个事务管理器 @Bean public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) { // 到ioc容器中根据类型找到dataSource return new DataSourceTransactionManager(dataSource); } }
-
测试类
/** * 测试完全注解方式 */ @Test public void test02() { ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class); UserService userService = context.getBean("userServiceImpl", UserService.class); userService.transaction(1, 2, 100); }
六、Spring5框架的新功能
- 整个Spring5框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除
6.1 日志
-
Spring5.0框架自带了通用的日志封装
-
Spring5已经移除了Log4jConfigListener,官方建议使用log4j2版本
-
Spring5框架整合log4j2
-
引入log4j和slf4j的依赖
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.11.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.11.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.11.2</version> <scope>test</scope> </dependency>
-
创建log4j2.xml配置文件(名字固定)
<?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration status="INFO" monitorInterval="30"> <!--先定义所有的appender--> <appenders> <!--这个输出控制台的配置--> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式--> <PatternLayout pattern="[%d{yyyy:HH:mm:ss:SSS}] [%p] - %l - %m%n"/> </console> <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用--> <File name="log" fileName="log/test.log" append="false"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/> </File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> </RollingFile> <RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 --> <DefaultRolloverStrategy max="20"/> </RollingFile> <RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> </RollingFile> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.springframework" level="INFO"></logger> <logger name="org.mybatis" level="INFO"></logger> <root level="all"> <appender-ref ref="Console"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileError"/> </root> </loggers> </configuration>
-
-
6.2 支持@Nullable注解
Spring5框架核心容器支持@Nullable
注解
-
@Nullable
注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空 -
注解用在方法上面,表示注解的返回值可以为空
-
注解使用在方法的参数里面,表示参数的值可以为空
-
注解使用在属性上面,表示属性值可以为空
6.3 支持函数式风格
Spring5核心容器里面,支持函数式风格GenericApplicationContext