一、Spring5 框架概述
Spring框架是一个轻量级的&开放源代码的JAVA EE框架,可以解决企业级应用开发的复杂性。
Spring 有两个核心部分:IOC 和 Aop
IOC:控制反转,把创建对象过程交给 Spring 进行管理
Aop:面向切面,不修改源代码进行功能增强
Spring提供了展现层 SpringMVC和持久层 Spring JDBCTemplate以及业务层事务管理等众多的企业应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架
Spring 特点
- 方便解耦,简化开发
- Aop 编程支持
- 方便程序测试
- 方便和其他框架进行整合
- 方便进行事务操作
- 降低 API 开发难度
Spring的体系结构
二、IOC 控制反转
1、Spring快速入门
(1)导入Spring的基本包坐标
<properties>
<spring.version>5.0.5.RELEASE</spring.version>
</properties>
<!--导入spring的context坐标,context依赖core、beans、expression-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
(2)创建普通类,在这个类创建普通方法
public class User {
public void add() {
System.out.println("add......");
}
}
(3)创建Spring核心配置文件,在配置文件配置创建的对象
在类路径下(resources)创建applicationContext.xml配置文件,直接引入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"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--配置类User对象创建-->
<bean id="user" class="com.demo.pojo.User"/>
</beans>
(4)编写测试代码
public class BeanTest {
@Test
public void testAdd() {
//1 加载 spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2 获取配置创建的对象
User user = context.getBean("user", User.class);
System.out.println(user);
user.add();
}
}
2、IOC(概念和原理)
1、什么是 IOC
(1)控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理
(2)使用 IOC 目的:为了耦合度降低
(3)做入门案例就是 IOC 实现
2、IOC 底层原理
(1)xml 解析、工厂模式、反射
3、画图讲解 IOC 底层原理
4、IOC(BeanFactory 接口)
1、IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂
2、Spring 提供 IOC 容器实现两种方式:(两个接口)
(1)BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用
* 加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
(2)ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人
员进行使用
* 加载配置文件时候就会把在配置文件对象进行创建
3、ApplicationContext 接口有实现类
5、IOC 操作 Bean 管理(概念)
1、什么是 Bean 管理 (0)Bean 管理指的是两个操作 (1)Spring 创建对象 (2)Spirng 注入属性
Bean 管理操作有两种方式,基于 xml 配置文件方式实现和基于注解方式实现
3 基于xml配置文件方式实现Bean管理
3.1 基于 xml 方式创建对象
<!--配置类User对象创建-->
<bean id="user" class="com.demo.pojo.User"/>
在 Spring 配置文件 applicationContext.xml 中使用 bean 标签,标签里面添加对应属性,就可以实现对象创建。
在 bean 标签有很多属性,介绍常用的属性
id 属性:IOC容器中的唯一标识
class 属性:类全路径(包类路径)
创建对象时候,默认也是执行无参数构造方法完成对象创建
3.2 基于 xml 方式依赖注入
依赖注入DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。
在编写程序时通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。依赖注入,就是注入属性。
IOC 解耦只是降低他们的依赖关系,但不会消除。
3.3 Bean的依赖注入方式
(1)使用有参数构造进行注入
创建类,定义属性,创建属性对应有参数构造方法
public class Orders {
private String oname;
private String address;
public Orders(String oname,String address) {
this.oname = oname;
this.address = address;
}
}
在 spring 配置文件配置对象创建,配置属性注入
<!--有参数构造注入属性-->
<bean id="orders" class="com.demo.pojo.Orders">
<constructor-arg name="oname" value="电脑"></constructor-arg>
<constructor-arg name="address" value="China"></constructor-arg>
</bean>
(2)使用 set 方法进行注入
创建类,定义属性和对应的 set 方法
public class Book {
//创建属性
private String bname;
private String bauthor;
//创建属性对应的 set 方法
public void setBname(String bname) {
this.bname = bname;
}
public void setBauthor(String bauthor) {
this.bauthor = bauthor;
}
}
在 spring 配置文件配置对象创建,调用set方法进行注入
<!-- set 方法注入属性-->
<bean id="book" class="com.demo.pojo.Book">
<!--使用 property 完成属性注入
name:类里面属性名称
value:向属性注入的值
-->
<property name="bname" value="易筋经"></property>
<property name="bauthor" value="达摩老祖"></property>
</bean>
(3)p命名空间注入
p命名空间注入本质也是set方法注入,但比起上述的set方法注入更加方便,主要体现在配置文件中
首先,需要引入P命名空间
xmlns:p="http://www.springframework.org/schema/p"
其次,进行属性注入在 bean 标签里面进行操作
<!--p命名空间注入属性-->
<bean id="book" class="com.demo.pojo.Book" p:bname="九阳神功" p:bauthor="无名氏"></bean>
(4)注入属性-外部 bean
创建两个类 service 类和 dao 类,在 service 调用 dao 里面的方法
public class UserDao {
public void update() {
System.out.println("userDao.update ......");
}
}
public class UserService {
//创建 UserDao 类型属性,生成 set 方法
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("userService add...............");
userDao.update();
}
}
<bean id="userDao" class="com.demo.service.UserDao"/>
<bean id="userService" class="com.demo.service.UserService">
<!--注入 userDao 对象
name 属性:类里面属性名称
ref 属性:创建 userDao 对象 bean 标签 id 值
-->
<property name="userDao" ref="userDao"/>
</bean>
(5)注入属性-内部 bean
创建两个类 员工类和 部门类,员工表示所属部门,使用对象类型属性进行表示
//部门类
public class Dept {
private String dname;
public void setDname(String dname) {
this.dname = dname;
}
}
//员工类
public class Emp {
private String ename;
private String gender;
//员工属于某一个部门,使用对象形式表示
private Dept dept;
public void setDept(Dept dept) {
this.dept = dept;
}
public void setEname(String ename) {
this.ename = ename;
}
public void setGender(String gender) {
this.gender = gender;
}
}
<!--内部 bean-->
<bean id="emp" class="com.demo.pojo.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--设置对象类型属性-->
<property name="dept">
<bean id="dept" class="com.demo.pojo.Dept">
<property name="dname" value="安保部"></property>
</bean>
</property>
</bean>
(6)注入属性-级联赋值
第一种写法
<!--级联赋值-->
<bean id="emp1" class="com.demo.pojo.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="com.demo.pojo.Dept">
<property name="dname" value="财务部"></property>
</bean>
第二种写法
需要Dept的get方法
public String getDname() {
return dname;
}
<!--级联赋值-->
<bean id="emp2" class="com.demo.pojo.Emp">
<!--设置两个普通属性-->
<property name="ename" value="lucy"></property>
<property name="gender" value="女"></property>
<!--级联赋值-->
<property name="dept" ref="dept"></property>
<property name="dept.dname" value="技术部"></property>
</bean>
<bean id="dept" class="com.demo.pojo.Dept">
<property name="dname" value="财务部"></property>
</bean>
(7)xml 注入集合属性
创建类,定义数组、list、map、set 类型属性,生成对应 set 方法
public class Stu {
//1 数组类型属性
private String[] courses;
//2 list 集合类型属性
private List<String> list;
//3 map 集合类型属性
private Map<String,String> maps;
//4 set 集合类型属性
private Set<String> sets;
public void setSets(Set<String> sets) {
this.sets = sets;
}
public void setCourses(String[] courses) {
this.courses = courses;
}
public void setList(List<String> list) {
this.list = list;
}
public void setMaps(Map<String, String> maps) {
this.maps = maps;
}
}
<!--集合类型属性注入-->
<bean id="stu" class="com.demo.pojo.Stu">
<!--数组类型属性注入-->
<property name="courses">
<array>
<value>java 课程</value>
<value>数据库课程</value>
</array>
</property>
<!--list 类型属性注入-->
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!--map 类型属性注入-->
<property name="maps">
<map>
<entry key="JAVA" value="java"></entry>
<entry key="PHP" value="php"></entry>
</map>
</property>
<!--set 类型属性注入-->
<property name="sets">
<set>
<value>MySQL</value>
<value>Redis</value>
</set>
</property>
</bean>
3.4 工厂 bean(FactoryBean)
Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
普通 bean:在配置文件中定义 bean 类型就是返回类型
工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样
第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean
第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
public class MyBean implements FactoryBean<Orders> {
//定义返回 bean
@Override
public Orders getObject() throws Exception {
Orders orders = new Orders();
orders.setOname("手机");
return orders;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
<bean id="myBean" class="com.demo.service.MyBean"/>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Orders orders = context.getBean("myBean", Orders.class);
System.out.println(orders);
3.5 bean 作用域
Spring 里面默认情况下,bean 是单实例对象。bean 标签里面有属性 scope 用于设置单实例还是多实例。
设置 scope 值是 singleton
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了
对象运行:只要容器在,对象一直活着
对象销毁:当应用卸载,销毁容器时,对象就被销毁了
设置 scope 值是 prototype
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时实例化Bean
Bean的生命周期:
对象创建:当使用对象时,创建新的对象实例
对象运行:只要对象在使用中,就一直活着
对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
scope 指对象的作用范围,取值如下:
取值范围 | 说明 |
singleton | 默认值,单例的 |
prototype | 多例的 |
在 bean 标签里面进行操作
<bean id="book" class="com.demo.pojo.Book" scope="prototype"/>
写测试代码
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Book book1 = context.getBean("book", Book.class);
Book book2 = context.getBean("book", Book.class);
System.out.println(book1);
System.out.println(book2);
//内存地址不同,代表不同对象
//com.demo.pojo.Book@69a10787
//com.demo.pojo.Book@2d127a61
3.6 Bean生命周期
(1)普通 bean 的生命周期,从对象创建到对象销毁的过程
- 通过构造器创建 bean 实例(无参数构造)
- 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- 调用 bean 的初始化的方法(需要进行配置初始化的方法)
- bean 可以使用了(对象获取到了)
- 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
演示 bean 生命周期
public class Orders {
private String oname;
//无参数构造
public Orders() {
System.out.println("第一步 执行无参数构造创建 bean 实例");
}
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步 调用 set 方法设置属性值");
}
//创建执行的初始化的方法
public void initMethod() {
System.out.println("第三步 执行初始化的方法");
}
//创建执行的销毁的方法
public void destroyMethod() {
System.out.println("第五步 执行销毁的方法");
}
}
<bean id="orders" class="com.demo.pojo.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"/>
</bean>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建 bean 实例对象");
System.out.println(orders);
//手动让 bean 实例销毁
context.close();
执行结果
第一步 执行无参数构造创建 bean 实例
第二步 调用 set 方法设置属性值
第三步 执行初始化的方法
第四步 获取创建 bean 实例对象
com.demo.pojo.Orders@60438a68
第五步 执行销毁的方法
(2)bean 的后置处理器,bean 生命周期有七步
- bean 的后置处理器,bean 生命周期有七步
- 通过构造器创建 bean 实例(无参数构造)
- 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
- 把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization
- 调用 bean 的初始化的方法(需要进行配置初始化的方法)
- 把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
- bean 可以使用了(对象获取到了)
- 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
演示添加后置处理器效果 ,创建类,实现接口 BeanPostProcessor,创建后置处理器
public class MyBeanPost 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 id="myBeanPost" class="com.demo.service.MyBeanPost"/>
<bean id="orders" class="com.demo.pojo.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="手机"/>
</bean>
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Orders orders = context.getBean("orders", Orders.class);
System.out.println("第四步 获取创建 bean 实例对象");
System.out.println(orders);
//手动让 bean 实例销毁
context.close();
执行结果
第一步 执行无参数构造创建 bean 实例
第二步 调用 set 方法设置属性值
在初始化之前执行的方法
第三步 执行初始化的方法
在初始化之后执行的方法
第四步 获取创建 bean 实例对象
com.demo.pojo.Orders@4fcd19b3
第五步 执行销毁的方法
3.7 引入外部属性文件
(1)引入相关依赖坐标
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
(2)引入外部属性文件配置数据库连接池
创建外部属性文件,properties 格式文件,写数据库信息
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.134.66:3306/Train?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root123
(3)把外部 properties 属性文件引入到 spring 配置文件中 ,引入 context 名称空间
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
在 spring 配置文件使用标签引入外部属性文件
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" 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>
3.8 引入其他配置文件(分模块开发)
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载
<import resource="applicationContext-xxx.xml"/>
4 注解方式实现Bean管理
4.1 Spring原始注解
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。
Spring原始注解主要是替代<Bean>的配置
注解 | 说明 |
@Component | 使用在类上用于实例化Bean |
@Controller | 使用在web层类上用于实例化Bean |
@Service | 使用在service层类上用于实例化Bean |
@Repository | 使用在dao层类上用于实例化Bean |
@Autowired | 使用在字段上用于根据类型依赖注入 |
@Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 |
@Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 |
@Value | 注入普通属性 |
@Scope | 标注Bean的作用范围 |
@PostConstruct | 使用在方法上标注该方法是Bean的初始化方法 |
@PreDestroy | 使用在方法上标注该方法是Bean的销毁方法 |
4.2 基于注解方式实现对象创建
(1)引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
(2)开启组件扫描
<!--开启组件扫描
1 如果扫描多个包,多个包使用逗号隔开
2 扫描包上层目录
-->
<context:component-scan base-package="com.demo"/>
(3)Spring进行实例化
使用@Repository标识UserDao
@Repository(value = "userDao")
public class UserDao {
public void update() {
System.out.println("userDao.update ......");
}
}
使用@Service标识UserService
使用@Autowired或者@Autowired+@Qulifier或者@Resource进行userDao的注入
@Service(value = "userService")
public class UserService {
@Autowired
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void add() {
System.out.println("userService add...............");
userDao.update();
}
}
编写测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
userService add...............
userDao.update ......
4.3 开启组件扫描细节配置
use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter
context:include-filter ,设置扫描哪些内容
<context:component-scan base-package="com.demo" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
context:exclude-filter: 设置哪些内容不进行扫描
<context:component-scan base-package="com.demo">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
4.4 使用@Value进行字符串的注入
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Value("注入普通数据")
private String str;
@Value("${jdbc.driver}")
private String driver;
@Override
public void save() {
System.out.println(str);
System.out.println(driver);
System.out.println("save running... ...");
}
}
4.5 使用@Scope标注Bean的范围
//@Scope("prototype")
@Scope("singleton")
public class UserDaoImpl implements UserDao {
//此处省略代码
}
4.6 使用@PostConstruct标注初始化方法,使用@PreDestroy标注销毁方法
@PostConstruct
public void init(){
System.out.println("初始化方法....");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法.....");
}
5 完全注解开发
使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:
注解 | 说明 |
@Configuration | 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解 |
@ComponentScan | 用于指定 Spring 在初始化容器时要扫描的包。 |
@Bean | 使用在方法上,标注将该方法的返回值存储到 Spring 容器中 |
@PropertySource | 用于加载properties 文件中的配置 |
@Import | 用于导入其他配置类 |
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource") //Spring会将当前方法的返回值以指定名称存储到Spring容器中
public DruidDataSource getDataSource() throws PropertyVetoException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
//标志该类是Spring的核心配置类
@Configuration
@ComponentScan("com.demo")
@Import({DataSourceConfiguration.class})
public class SpringConfiguration {
}
测试加载核心配置类创建Spring容器
@Test
public void beanTest() throws SQLException {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserService userService = applicationContext.getBean("userService",UserService.class);
userService.add();
DruidDataSource dataSource = applicationContext.getBean("dataSource", DruidDataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
6 Spring整合Junit
(1)导入spring集成Junit的坐标
<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
(2)创建测试方法进行测试
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private UserService userService;
@Test
public void testUserService(){
userService.save();
}
}
三、Spring AOP
1 什么是AOP
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
通俗描述:不通过修改源代码方式,在主干功能里面添加新功能
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
2 AOP的底层实现
AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
常用的动态代理技术
JDK 代理 : 基于接口的动态代理技术
cglib 代理:基于父类的动态代理技术
2.1 JDK 的动态代理
(1)目标类接口
public interface TargetInterface {
public void save();
}
(2)目标类
public class Target implements TargetInterface{
public void save() {
System.out.println("save running...");
}
}
(3)增强对象代码
public class Advice {
public void before(){
System.out.println("前置通知");
}
public void afterReturning(){
System.out.println("后置增强");
}
}
(4)动态代理代码
public class ProxyTest {
public static void main(String[] args) {
final Target target = new Target(); //创建目标对象
final Advice advice = new Advice(); //创建增强对象
//创建代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
advice.before();
Object invoke = method.invoke(target, args);
advice.afterReturning();
return invoke;
}
}
);
proxy.save();//执行测试方法
}
}
2.2 cglib 的动态代理
(1)目标类
public class Target{
public void save() {
System.out.println("save running...");
}
}
(2)增强对象代码
public class Advice {
public void before(){
System.out.println("前置通知");
}
public void afterReturning(){
System.out.println("后置增强");
}
}
(3)动态代理代码
public class ProxyTest {
public static void main(String[] args) {
final Target target = new Target(); //创建目标对象
final Advice advice = new Advice(); //创建增强对象
Enhancer enhancer = new Enhancer(); //创建增强器
enhancer.setSuperclass(Target.class); //设置父类
enhancer.setCallback(new MethodInterceptor() { //设置回调
@Override
public Object intercept(Object o, Method method, Object[] objects,MethodProxy methodProxy) throws Throwable {
advice.before();
Object invoke = method.invoke(target, objects);
advice.afterReturning();
return invoke;
}
});
Target proxy = (Target) enhancer.create(); //创建代理对象
proxy.save();//执行测试方法
}
}
3 AOP 相关概念
Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语,常用的术语如下:
AOP | 说明 |
Joinpoint(连接点) | 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点 |
Pointcut(切入点) | 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义 |
Advice(通知/ 增强) | 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知 |
Aspect(切面) | 是切入点和通知(引介)的结合 |
Weaving(织入) | 是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入 |
AOP 开发明确的事项
需要编写的内容
编写核心业务代码(目标类的目标方法)
编写切面类,切面类中有通知(增强功能方法)
在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
AOP 技术实现的内容
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
AOP 底层使用哪种代理方式
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
4 基于 XML 的 AOP 开发
Spring 框架一般都是基于 AspectJ 实现 AOP 操作 ,AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,进行 AOP 操作
4.1 快速入门
(1)导入 AOP 相关坐标
<!--导入spring的context坐标,context依赖aop-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!-- aspectj的织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
(2)创建类,在类里面定义方法
@Component
public class User {
public void add() {
System.out.println("add......");
}
}
(3)创建增强类,编写增强逻辑。在增强类里面创建方法,让不同方法代表不同通知类型
@Component
public class MyAspect {
public void before(){
System.out.println("前置增强..........");
}
public void afterReturning(){
System.out.println("后置增强..........");
}
//Proceeding JoinPoint: 正在执行的连接点===切点
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强....");
Object proceed = pjp.proceed();//切点方法
System.out.println("环绕后增强....");
return proceed;
}
public void afterThrowing(){
System.out.println("异常抛出增强..........");
}
public void after(){
System.out.println("最终增强..........");
}
}
(4)在 applicationContext.xml 中开启注解扫描,将目标类和切面类的对象创建权交给 spring,并且配置织入关系,配置切点表达式和增强的织入关系
<context:component-scan base-package="com.demo"/>
<aop:config>
<!--引用userProxy的Bean为切面对象-->
<aop:aspect ref="myAspect">
<!--配置Target的method方法执行时要进行userProxy的before方法前置增强-->
<aop:before method="before" pointcut="execution(public void com.demo.pojo.User.add())"></aop:before>
<aop:after-returning method="afterReturning" pointcut="execution(* com.demo.pojo.*.*(..))"/>
<aop:around method="around" pointcut="execution(* com.demo.pojo.*.*(..))"/>
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.demo.pojo.*.*(..))"/>
<aop:after method="after" pointcut="execution(* com.demo.pojo.*.*(..))"/>
</aop:aspect>
</aop:config>
(5)编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:applicationContext.xml"})
public class BeanTest {
@Autowired
private User user;
@Test
public void testBean(){
user.add();
}
}
(6)执行结果
前置增强..........
环绕前增强....
add......
最终增强..........
环绕后增强....
后置增强..........
4.2 切入点表达式
切入点表达式作用:知道对哪个类里面的哪个方法进行增强
语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
表达式语法:
- 返回值类型、包名、类名、方法名可以使用星号* 代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
- 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表
execution(public void com.demo.pojo.User.add())
execution(void com.demo.pojo.User.*(..))
execution(* com.demo.pojo.*.*(..))
execution(* com.demo..*.*(..))
execution(* *..*.*(..))
4.3 通知的类型
通知的配置语法:
<aop:通知类型 method=“切面类中方法名” pointcut=“切点表达式"></aop:通知类型>
4.4 切点表达式的抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。
<aop:config>
<!-- 声明切面 -->
<aop:aspect ref="myAspect">
<!-- 抽取切点表达式 -->
<aop:pointcut id="myPointcut" expression="execution(* com.demo.pojo.*.*(..))"/>
<!-- 切面:切点+通知 -->
<aop:before method="before" pointcut-ref="myPointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
<aop:around method="around" pointcut-ref="myPointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
<aop:after method="after" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
5 基于注解的 AOP 开发
5.1 快速入门
基于注解的aop开发步骤:
(1)创建类,在类里面定义方法,将对象给 spring 管理
@Component
public class User {
public void add() {
System.out.println("add......");
}
}
(2)创建切面类(内部有增强方法),将对象交给spring 管理,在切面类中使用注解配置织入关系
@Component
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {
@Before("execution(* com.demo.pojo.*.*(..))")
public void before(){
System.out.println("前置增强..........");
}
@AfterReturning("execution(* com.demo.pojo.*.*(..))")
public void afterReturning(){
System.out.println("后置增强..........");
}
@Around("execution(* com.demo.pojo.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强....");
Object proceed = pjp.proceed();//切点方法
System.out.println("环绕后增强....");
return proceed;
}
@AfterThrowing("execution(* com.demo.pojo.*.*(..))")
public void afterThrowing(){
System.out.println("异常抛出增强..........");
}
@After("execution(* com.demo.pojo.*.*(..))")
public void after(){
System.out.println("最终增强..........");
}
}
(3)在配置文件中开启组件扫描和 AOP 的自动代理
<!--组件扫描-->
<context:component-scan base-package="com.demo"/>
<!--aop自动代理-->
<aop:aspectj-autoproxy/>
(4)编写测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:applicationContext.xml"})
public class BeanTest {
@Autowired
private User user;
@Test
public void testBean(){
user.add();
}
}
5.2 注解配置 AOP 详解
通知的配置语法:@通知注解(“切点表达式")
5.3 切点表达式的抽取
同 xml配置 aop 一样,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在在增强注解中进行引用。具体如下:
@Component
@Aspect
public class MyAspect {
//定义切点表达式
@Pointcut("execution(* com.demo.pojo.*.*(..))")
public void myPoint(){}
@Before("MyAspect.myPoint()")
public void before(){
System.out.println("前置增强..........");
}
@AfterReturning("MyAspect.myPoint()")
public void afterReturning(){
System.out.println("后置增强..........");
}
@Around("MyAspect.myPoint()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强....");
Object proceed = pjp.proceed();//切点方法
System.out.println("环绕后增强....");
return proceed;
}
@AfterThrowing("MyAspect.myPoint()")
public void afterThrowing(){
System.out.println("异常抛出增强..........");
}
@After("MyAspect.myPoint()")
public void after(){
System.out.println("最终增强..........");
}
}
6 完全使用注解开发
@Configuration
@ComponentScan("com.demo")
@Import({DataSourceConfiguration.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfiguration {
}
四、JdbcTemplate基本使用
1 JdbcTemplate概述
JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。
2 JdbcTemplate开发步骤
导入spring-jdbc和spring-tx坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
创建数据库表和实体
CREATE TABLE `account` (
`username` varchar(20) NOT NULL,
`money` double DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
public class Account {
private String username;
private Double money;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"username='" + username + '\'' +
", money=" + money +
'}';
}
}
JdbcTemplate-spring管理模板对象
配置如下:
<!--加载jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--数据源对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!--jdbc模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:applicationContext.xml"})
public class BeanTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testUserService(){
jdbcTemplate.update("INSERT INTO account VALUES(?,?)", "zs", 5000);
jdbcTemplate.update("UPDATE account SET money=? WHERE username=?",4000,"lisi");
jdbcTemplate.update("DELETE FROM account WHERE username=?","zs");
Account ac = jdbcTemplate.queryForObject("SELECT * FROM account WHERE username=?", new BeanPropertyRowMapper<Account>(Account.class), "lisi");
System.out.println(ac);
}
}
五、Spring事务管理
1 PlatformTransactionManager
PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。
注意:
PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,
例如:
Dao 层技术是jdbc 或 mybatis 时:
org.springframework.jdbc.datasource.DataSourceTransactionManager
Dao 层技术是hibernate时:
org.springframework.orm.hibernate5.HibernateTransactionManager
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为
ioslation:事务隔离级别 (1)事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题 (2)有三个读问题:脏读、不可重复读、虚(幻)读 (3)脏读:一个未提交事务读取到另一个未提交事务的数据
2 基于 XML 的声明式事务管理
Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。
声明式事务处理的作用,事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便
注意:Spring 声明式事务控制底层就是AOP。
引入相关的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
转账业务代码
@Repository(value="accountDao")
public class AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void out(String outMan, double money) {
jdbcTemplate.update("UPDATE account SET money=money-? WHERE username=?",money,outMan);
}
public void in(String inMan, double money) {
jdbcTemplate.update("UPDATE account SET money=money+? WHERE username=?",money,inMan);
}
}
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String outMan, String inMan, double money){
accountDao.out(outMan,money);
int i=1/0;
accountDao.in(inMan,money);
}
}
配置事务增强 配置事务 AOP 织入
<!--组件扫描-->
<context:component-scan base-package="com.demo"/>
<!--加载jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--数据源对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!--jdbc模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--事务增强配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--设置事务的属性信息-->
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--事务的aop增强-->
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.demo.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"></aop:advisor>
</aop:config>
测试事务控制转账业务代码,出现异常时2条sql都没有提交
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:applicationContext.xml"})
public class BeanTest {
@Autowired
private AccountService accountService;
@Test
public void testUserService(){
accountService.transfer("zs","lisi",1000);
}
}
3 基于注解的声明式事务控制
编写 AccoutDao
@Repository(value="accountDao")
public class AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void out(String outMan, double money) {
jdbcTemplate.update("UPDATE account SET money=money-? WHERE username=?",money,outMan);
}
public void in(String inMan, double money) {
jdbcTemplate.update("UPDATE account SET money=money+? WHERE username=?",money,inMan);
}
}
编写 AccoutDao
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
@Transactional
public void transfer(String outMan, String inMan, double money){
accountDao.out(outMan,money);
int i=1/0;
accountDao.in(inMan,money);
}
}
编写 applicationContext.xml 配置文件
<!--组件扫描-->
<context:component-scan base-package="com.demo"/>
<!--加载jdbc.properties-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--数据源对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!--jdbc模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--事物的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
注解配置声明式事务控制解析
- 使用 @Transactional 在需要进行事务控制的类或是方法上修饰,注解可用的属性同 xml 配置方式,例如隔离级别、传播行为等。
- 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置。
- 使用在方法上,不同的方法可以采用不同的事务参数配置。
- Xml配置文件中要开启事务的注解驱动<tx:annotation-driven />