目录
1、Spring简介
1.1、Spring是什么
1.2、Spring发展历程
1.3、Spring的优势
1.3.1、方便解耦,简化开发
1.3.2、AOP编程的支持
1.3.3、声明式事务的支持
1.3.4、方便程序的测试
1.3.5、方便继承各种优秀框架
1.3.6、降低JavaEE API 的使用难度
1.3.7、Java源码的经典学习范例
1.4、Spring的体系结构
2、Spring快速入门
2.1、Spring程序开发步骤
2.2、Spring的开发步骤
3、Spring配置文件
3.1、Bean标签基本配置
3.2、Bean标签范围配置
3.3、Bean生命周期
3.3.1、生命周期控制方式1
3.3.2、生命周期控制方式2
3.4、Bean实例化的四种方式
3.5、Bean的依赖注入分析
3.5.1、Bean的依赖注入概念
3.6、Bean的依赖注入方式
3.6.1、setter注入——引用类型
3.6.2、setter注入——简单类型
3.6.3、构造器(构造方法)注入——引用类型
3.6.4、构造器(构造方法)注入——简单类型
3.6.5、p命名空间方式注入
3.6.6、注入集合数据类型
3.6.7、依赖注入方式的选择
3.6.8、案例——数据源对象管理
3.7、依赖自动装配
3.7.1、自动装配方式
3.7.2、依赖自动装配的特征
3.8、Spring加载外部properties文件
3.9、引入其他配置文件(分模块开发)
4、Spring相关API
4.1、ApplicationContext的继承体系
4.2、ApplicationContext的实现类
4.3、getBean()方法使用
5、Spring配置数据源
5.1、数据源(连接池)的作用
5.2、数据源的开发步骤
5.3、Spring配置数据源
5.4、抽取jdbc配置文件
6、Spring注解开发
6.1、Spring原始注解
6.1.1、@Scope & @PostConstruct & @PreDestroy
6.1.2、@Autowired & @Qualifier 实现引用类型依赖注入
6.1.3、@Value 实现简单类型依赖注入
6.2、Spring新注解
6.2.1、@Configuration & @ComponentScan实现替代Spring配置文件
6.2.2、@Import & @Bean 实现配置类分离和第三方bean管理
6.3、XML配置对比注解配置
7、Spring整合MyBatis
8、Spring整合Junit
8.1、原始Junit测试Spring的问题
8.2、上述问题解决思路
8.3、Spring整合Junit步骤
9、Spring集成Web环境
9.1、ApplicationContext应用上下文获取方式
9.2、Spring提供获取应用上下文的工具
9.3、Spring集成web环境步骤
10、Spring JdbcTemplate的基本使用
10.1、JdbcTemplate概述
10.2、JdbcTemplate开发步骤
10.3、Spring产生JdbcTemplate对象
11、Spring AOP
11.1、什么是AOP
10.2、AOP的作用及其优势
10.3、AOP的底层实现
10.4、AOP的动态代理技术
10.4.1、基于JDK的动态代理
10.4.2、基于cglib的动态代理
10.5、AOP相关概念
10.5.1、AOP切入点表达式
10.5.2、AOP通知类型
10.6、AOP开发明确的事项
10.6.1、需要编写的内容
10.6.2、AOP技术实现的内容
10.6.3、AOP底层使用哪种代理方式
10.7、AOP工作流程
10.8、基于XML的AOP开发
10.8.1、快速入门
10.8.2、XML配置AOP详解
10.9、基于注解的AOP开发
10.9.1、快速入门
10.9.2、注解配置AOP详解
10.9.3、AOP通知获取数据
10.10、AOP案例
10.10.1、案例1——测量业务层接口万次执行效率
10.10.2、案例2——百度网盘密码数据兼容处理
11、Spring的事务控制
11.1、事务相关定义
11.1.1、事务角色
11.1.2、事务的隔离级别
11.1.3、事务的传播行为
11.2、编程式事务控制相关对象
11.2.1、PlatformTransactionManager
11.2.2、TransactionDefinition
11.2.3、TransactionStatus
11.3、基于XML的声明式事务控制
11.3.1、什么是声明式事务控制
11.3.2、声明式事务控制的实现
11.4、基于注解的声明式事务控制
11.4.1、使用步骤
11.4.2、注解配置声明式事务控制解析
11.5、Spring事务相关配置
11.5、Spring事务案例
11.5.1、案例1——银行账户转账
11.5.2、案例2——转账业务追加日志
Spring是分层的Java SE/EE 应用full-stack轻量级开源框架,以IoC(Inversion Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核。
Spring技术对IoC思想进行了实现:
提供了展现层SpringMVC和持久层Spring JDBCTemplate以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的JavaEE企业应用开源框架。
通过Spring提供的IoC容器,可以讲对象间的依赖关系交由Spring进行控制,避免编码所造成的过度耦合,用户也不必在为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注上层的应用。
通过Spring的AOP功能,方便进行面向切面编程,许多不容易用传统OOP实现的功能可以通过AOP轻松实现。
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务管理,提高开发效率和质量。
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
Spring对各种优秀框架(Struts、Hibemate、Hessian、Quartz等)的支持。
Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低。
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无疑是Java技术的最佳实践的范例。
问题提出:
applicationContext.xml:
注意:bean定义时id属性在同一个上下文中不能重复。
package com.clp.impl;
class UserDaoImpl implements com.clp.UserDao {
@Override
public void save() {
System.out.println("save running ...");
}
}
package com.clp.demo;
import com.clp.UserDao;
import javafx.application.Application;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserDaoDemo {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("ApplicationContext.xml");
UserDao userDao = (UserDao) app.getBean("userDao");
userDao.save();
}
}
用于配置对象交由Spring来创建。默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
类别 | 描述 |
---|---|
名称 | |
类型 | 标签 |
所属 | |
功能 | 定义Spring核心容器管理的对象 |
格式 | |
属性列表 | id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一。 name:bean的别名,可以设置多个,使用,(逗号)或;(分号)或 (空格)分隔,可以用来代替id。 class:bean的类型,即配置的bean的全路径类名。 scope:定义bean的作用范围。singleton为单例(默认),prototype为非单例。 init-method:生命周期初始化方法。 destroy-method:生命周期销毁方法。 autowire:自动装配类型。 factory-method:bean工厂方法,应用于静态工厂或实例工厂。 factory-bean:实例工厂bean。 lazy-init:控制bean延迟加载。 |
范例 | |
类别 | 描述 |
---|---|
名称 | |
类型 | 标签 |
所属 | |
功能 | bean的属性注入 |
格式 | |
属性列表 | name:属性名称。 value:注入的普通属性值。 ref:注入的对象引用值。 |
范例 | |
注意事项:获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常NoSuchBeanDefinitionException。NoSuchBeanDefinitionException: No bean named 'bookServiceImpl' available 。
scope:指对象的作用范围,取值如下:
singleton:默认值,单例的。
prototype:多例的。
request:WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中。
session:WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中。
global session:WEB项目中,应用在Portlet环境,如果没有Portlet环境那么globalSession相当于session。
Bean的创建时机:
当scope的取值为singleton时:
Bean的实例化个数:1个;
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例。
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了;
对象运行:只要容器在,对象一直或者。
对象销毁:当应用卸载,销毁容器时,对象就被销毁了。
当scope的取值为prototype时:
Bean的实例化个数:多个。
Bean的实例化时机:当调用getBean()方法时实例化Bean。
对象创建:当使用对象时,创建新的对象实例。
对象运行:只要对象在使用中,就一直活着。
对象销毁:当对象长时间不用时,被Java的垃圾回收器回收了。
适合交给容器进行管理的bean:表现层对象;业务层对象;数据层对象;工具对象。
不适合交给容器进行管理的对象:封装实体的域对象。
生命周期:从创建到消亡的完整过程。
Bean生命周期:Bean从创建到销毁的整体过程。
- 初始化容器
1、创建对象(内存分配)
2、执行构造方法
3、执行属性注入(set操作)
4、执行bean初始化方法
- 使用bean
1、执行业务操作
- 关闭/销毁容器
1、执行bean销毁方法
Bean的销毁时机:
- 容器关闭前触发bean的销毁
- 关闭容器方式:
1、手工关闭容器
ConfigurationApplicationContext接口的close()操作
2、注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
ConfigurationApplicationContext接口registerShutdownHook()操作
Bean生命周期控制:在Bean创建后到销毁前做一些事情。
提供生命周期控制方法:
class UserDaoImpl implements com.clp.UserDao {
public UserDaoImpl() {
System.out.println("UserDaoImpl创建..");
}
public void init() {
System.out.println("初始化方法");
}
public void destroy() {
System.out.println("销毁方法");
}
@Override
public void save() {
System.out.println("save running ...");
}
}
配置生命周期控制方法:
init-method:指定类中的初始化方法名称。
destroy-method:指定类中销毁方法名称。
public class App {
public static void main(String[] args) {
// 3、获取IoC容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 4.1、获取bean:BookDao,参数为bean 的id
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
ctx.close();
// Spring容器注册关闭钩子。表示Java虚拟机在关闭之前会先将容器关闭掉
// ctx.registerShutdownHook();
// 4.2、获取bean:BookService,参数为bean的id
// BookService bookService = (BookService) aCtx.getBean("bookService");
// bookService.save();
}
}
提供生命周期控制方法:
/**
* 实现initializingBean和DisposableBean接口,重写其中的方法
*/
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
public BookDao bookDao;
public void setBookDao(BookDao bookDao) {
System.out.println("set ...");
this.bookDao = bookDao;
}
@Override
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
/**
* 在调用完所有setXxx()的setter方法之后会调用该方法,如本类的setBookDao()方法
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("book service afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
System.out.println("book service destroy...");
}
}
applicationContext.xml中配置bean:
bean本质上就是对象,创建bean使用构造方法完成。
- 无参构造方法实例化:
- 提供可访问的构造方法 public UserDao() {}
注意:无参构造方法如果不存在,将抛出异常BeanCreationException
- 配置
- 工厂静态方法实例化:
public class StaticFactory {
public static UserDao getUserDao() {
return new UserDaoImpl();
}
}
factory-method="getUserDao"
>
- 工厂实例方法实例化:
public class UserDaoFactory {
public UserDao getUserDao() {
return new UserDaoImpl();
}
}
- 工厂实例方法实例化-改进版(使用FactoryBean):
public class UserDaoFactoryBean implements FactoryBean {
/**
* 代替原始实例工厂中创建对象的方法
* @return
* @throws Exception
*/
@Override
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
/**
* 获取对象的类型
* @return
*/
@Override
public Class> getObjectType() {
return UserDao.class;
}
/**
* 该对象是否为单例
* @return
*/
@Override
public boolean isSingleton() {
return true;
}
}
目前UserService实例和UserDao实例都存在于Spring容器中,当前的做法是在容器外部获得UserService实例和UserDao实例,然后在程序中进行结合。
因为UserService和UserDao都在Spring容器中,而最终程序直接使用的是UserService,所以可以在Spring容器中,将UserDao设置到UserService内部。
依赖注入(Dependency Injection):它是Spring框架核心IOC的具体实现。
在编写程序时,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。IOC解耦只是降低它们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了。简单地说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
思考:怎么将UserDao注入到UserService内部呢?/ 向一个类中传递数据的方式有几种?
1、构造方法
2、普通方法(set方法)
思考:依赖注入描述了在容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
1、引用类型
2、简单类型(基本数据类型与String)
3、集合类型
- 依赖注入方式
setter注入:
简单类型
引用类型
构造器注入:
简单类型
引用类型
在bean中定义引用类型属性并提供可访问的set方法:
public class BookServiceImpl implements BookService {
public BookDao bookDao;
public UserDao userDao;
public void setBookDao(BookDao bookDao) {
System.out.println("set ...");
this.bookDao = bookDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
配置中使用property标签ref属性注入引用类型对象:
applicationContext.xml:
...
...
在bean中定义引用类型属性并提供可访问的set方法:
public class BookDaoImpl implements BookDao {
private int connectionNum;
private String databaseName;
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
@Override
public void save() {
System.out.println("book dao save ..." + databaseName + ", " + connectionNum);
}
}
配置中使用property标签value属性注入简单类型数据:
applicationContext.xml:
...
...
在bean中定义引用类型属性并提供可访问的构造方法:
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
private UserDao userDao;
public BookServiceImpl(BookDao bookDao, UserDao userDao) {
this.bookDao = bookDao;
this.userDao = userDao;
}
}
配置中使用constructor-arg标签ref属性注入引用类型对象:
applicationContext.xml:
...
...
public class BookDaoImpl implements BookDao {
private int connectionNum;
private String databaseName;
public BookDaoImpl(int connectionNum, String databaseName) {
this.connectionNum = connectionNum;
this.databaseName = databaseName;
}
}
applicationContext.xml:
...
...
P命名空间本质也是set()方法注入,但比起上述的set()方法注入更加方便,主要体现在配置文件中,如下:
package com.clp.service.impl;
import com.clp.dao.UserDao;
import com.clp.service.UserService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserServiceImpl implements UserService {
private UserDao userDao;
// public void setUserDao(UserDao userDao) {
// this.userDao = userDao;
// }
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
除了对象的引用可以注入,普通数据类型,集合等都可以在容器中进行注入。
public class BookDaoImpl implements BookDao {
private int[] array;
private List list;
private Set set;
private Map map;
private Properties properties;
public void setArray(int[] array) {
this.array = array;
}
public void setList(List list) {
this.list = list;
}
public void setSet(Set set) {
this.set = set;
}
public void setMap(Map map) {
this.map = map;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public void save() {
System.out.println("book dao save ...");
System.out.println("遍历数组" + Arrays.toString(array));
System.out.println("遍历List" + list);
System.out.println("遍历Set" + set);
System.out.println("遍历Map" + map);
System.out.println("遍历Properties" + properties);
}
}
applicationContext.xml:
...
100
200
300
Laaa
Lbbb
Lccc
Saaa
Sbbb
Sccc
Sccc
Pvalue1
Pvalue2
Pvalue3
...
public class App {
public static void main(String[] args) {
// 3、获取IoC容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 4.1、获取bean:BookDao,参数为bean 的id
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
结果:
book dao save ...
遍历数组[100, 200, 300]
遍历List[Laaa, Lbbb, Lccc]
遍历Set[Saaa, Sbbb, Sccc]
遍历Map{Mkey1=Mvalue1, Mkey2=Mvalue2, Mkey3=Mvalue3}
遍历Properties{Pkey3=Pvalue3, Pkey2=Pvalue2, Pkey1=Pvalue1}
添加依赖(坐标):
pom.xml:
...
com.alibaba
druid
1.1.16
c3p0
c3p0
0.9.1.2
mysql
mysql-connector-java
5.1.6
...
配置数据源对象作为Spring容器管理的bean:
applicationContext.xml:
...
...
测试代码:
public class App {
public static void main(String[] args) {
// 3、获取IoC容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource1 = (DataSource) ctx.getBean("dataSource1");
DataSource dataSource2 = (DataSource) ctx.getBean("dataSource2");
System.out.println(dataSource1);
System.out.println(dataSource2);
}
}
IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配。
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Override
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
- 配置中使用bean标签的autowire属性设置自动装配的类型
applicationContext.xml:
...
...
jdbc.properties:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssmdb
jdbc.username=root
jdbc.password=123456
applicationContext.xml:
...
...
实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他的配置文件中,而在Spring主配置文件通过import标签进行加载。
applicationContext:接口类型,代表应用上下文,可以通过其实例获得Spring容器中的Bean对象。
ClassPathXmlApplicationContext:
它是从类的根路径下(resource文件夹下)加载配置文件,推荐使用这种。
FileSystemXmlApplicationContext:
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext:
当使用注解配置容器对象时,需要使用此类来创建Spring容器,它用来读取注解。
public Object getBean(String name);
当参数的数据类型是字符串时,标识根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。
public T getBean(Class requiredType);
当参数的数据类型是Class类型时,标识根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错。
public T getBean(String beanName, Class beanType);
使用bean名称获取并指定类型。
常见的数据源(连接池):DBCP、C3P0、BoneCP、Druid等。
package test;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ResourceBundle;
public class DataSourceTest {
@Test
//测试手动创建 c3p0 数据源
public void test1() throws PropertyVetoException, SQLException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/bysj?characterEncoding=utf-8");
dataSource.setUser("root");
dataSource.setUser("123456");
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
@Test
//测试手动创建 c3p0 数据源(加载properties配置文件)
public void test3() throws PropertyVetoException, SQLException {
//读取配置文件
ResourceBundle rb = ResourceBundle.getBundle("jdbc");
String driver = rb.getString("jdbc.driver");
String url = rb.getString("jdbc.url");
String username = rb.getString("jdbc.username");
String password = rb.getString("jdbc.password");
//创建数据源对象,设置连接参数
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
@Test
//测试手动创建 druid 数据源
public void test2() throws PropertyVetoException, SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/bysj?characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
DruidPooledConnection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
可以将DataSource的创建权交给Spring去完成。
package test;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javafx.application.Application;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ResourceBundle;
public class DataSourceTest {
@Test
//测试Spring容器产生数据源对象
public void test4() throws PropertyVetoException, SQLException {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = app.getBean(DataSource.class);
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
applicationContext.xml加载jdbc.properties配置文件获得连接信息。
首先,需要引入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容器加载properties文件:
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。
@Component
使用在类上用于实例化Bean
- Spring提供@Component注解的3个衍生注解:
@Controller
使用在web层类上用于实例化Bean
@Service
使用在service层上用于实例化Bean
@Repository
使用在dao层类上用于实例化Bean
@Autowired
使用在字段上根据类型依赖注入
@Qualifier
结合@Autowired一起使用用于根据名称进行依赖注入
@Resource
相当于@Autowired+@Qualifier,按照名称进行注入。
@Value
注入普通属性
@Scope
标注Bean的作用范围
@PostConstruct
使用在方法上标注该方法是Bean的初始化方法
@PreDestroy
使用在方法上标注该方法是Bean的销毁方法
注意:使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。
//
//@Component("userServiceId")
@Service("userServiceId")
@Scope("singleton")
public class UserServiceImpl implements UserService {
@Value("${jdbc.driver}") //从容器中找键为jdbc.driver的值,并赋给driver
private String driver;
//
// @Autowired //按照数据类型从Spring容器中进行匹配的
// @Qualifier("userDaoId") //按照id名称从Spring容器中进行匹配的,但是注意此处@Qualifier需要结合@Autowired一起使用
@Resource(name = "userDaoId") //@Resource相当于@Qualifier+@Autowired
private UserDao userDao;
//使用xml配置需要set()方法,使用注解方式可以不写set()方法
// public void setUserDao(UserDao userDao) {
// this.userDao = userDao;
// }
@Override
public void save() {
System.out.println(driver);
userDao.save();
}
@PostConstruct
public void init() {
System.out.println("service对象的初始化方法");
}
@PreDestroy
public void destroy() {
System.out.println("service对象的销毁方法");
}
}
@Service
public class BookServiceImpl implements BookService {
/**
* 使用@Autowired注解开启自动装配模式:按类型依赖注入(通过暴力反射)
* 如果有多个相同类型的bean,则需再添加@Qualifier指定bean的名称(bean的id)
*/
@Autowired
@Qualifier("bookDao1")
private BookDao bookDao;
@Override
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
注意:
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("mysql")
private String dbName;
@Override
public void save() {
System.out.println("book dao save ..." + dbName);
}
}
注入properties文件中的值:
- 配置类加上@PropertiesSource注解指定要加载的properties文件
@Configuration
@ComponentScan("com.clp")
@PropertySource("classpath:jdbc.properties") <-这里
public class SpringConfig {
}
注意:如果需要加载多个properties文件,使用数组方式加载:
@PropertiesSource({"jdbc.properties", "xxx.properties"})
注意:路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符*。允许添加"classpath:"前缀。
- 配置依赖注入
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("${jdbc.url}")
private String dbName;
@Override
public void save() {
System.out.println("book dao save ..." + dbName);
}
}
使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:
Spring 3.0升级了纯注解开发模式,使用Java类代替配置文件,开启了Spring快速开发赛道。在纯注解开发中,Java类代替Spring核心配置文件。
@Configuration
用于指定当前类是一个Spring配置类(相当于一个applicationContext.xml),当创建容器时会从该类上加载注解。
@ComponentScan
用于指定Spring在初始化容器时要扫描的包。
作用和在Spring的xml配置文件中的 一样
@Bean
使用在方法上,标注该方法的返回值存储到Spring容器中。
@PropertySource
用于加载.properties文件中的配置
@Import
用于导入其他配置类
上述配置文件可用以下类替换:
@Configuration
@ComponentScan("com.clp")
public class SpringConfig {
}
@Configuration注解用于设定当前类为配置类。
@ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式:
@ComponentScan({"com.clp.service", "com.clp.dao"})
读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象:
public class AppAnno {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
测试代码:
public class AppAnno {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao1 = (BookDao) ctx.getBean(BookDao.class);
BookDao bookDao2 = (BookDao) ctx.getBean(BookDao.class);
System.out.println(bookDao1);
System.out.println(bookDao2);
}
}
- 使用独立的配置类管理第三方bean:
public class JdbcConfig {
// 1、定义一个方法获得要管理的对象
// 2、添加@Bean表示当前方法的返回值是一个bean
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/ssmdb");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
}
- 将独立的配置类加入核心配置:
- 方式1:导入式。使用@Import注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式
public class JdbcConfig {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
// 相关配置
return dataSource;
}
}
@Configuration
@Import({JdbcConfig.class})
public class SpringConfig {
}
- 方式2:扫描式。使用@ComponentScan注解扫描配置类所在的包,加载对应的配置类信息。
@Configuration
public class JdbcConfig {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
// 相关配置
return dataSource;
}
}
@Configuration
@ComponentScan({"com.clp.config", "com.clp.service", "com.clp.dao"})
public class SpringConfig {
}
第三方bean的依赖注入(创建该bean还需要其他东西):
public class JdbcConfig {
/*
* 简单类型的依赖注入使用@Value
* */
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/ssmdb")
private String url;
@Value("root")
private String username;
@Value("123456")
private String password;
// 1、定义一个方法获得要管理的对象
// 2、添加@Bean表示当前方法的返回值是一个bean
@Bean
/*
* 引用类型的依赖注入:主需要为bean定义方法设置形参即可,容器会根据 类型 自动装配对象。
* */
public DataSource dataSource(BookDao bookDao) {
System.out.println(bookDao);
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
功能 | XML配置 | 注解 |
---|---|---|
定义bean | bean标签: - id属性 - class属性 |
@Component @Controller @Service @Repository @ComponentScan |
设置依赖注入 | setter注入(set方法) 引用/简单 构造器逐日(构造方法) 引用/简单 自动装配 |
@Autowired @Qualifier @Value |
配置第三方bean | bean标签 静态工厂、实例工厂、FactoryBean |
@Bean |
作用范围 | bean标签: - scope属性 |
@Scope |
生命周期 | bean标签: init-method destroy-method |
@PostConstruct @PreDestroy |
MyBatis程序核心对象分析:
// 1、创建SqlSessionFactoryBean对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2、加载sqlMapConfig.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 3、创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactory.build(inputStream);
// 4、获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5、执行SqlSession对象执行查询,获取结果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
Account account = accountDao。findById(2);
System.out.println(account);
// 6、释放资源
sqlSession.close();
整合MyBatis:
pom.xml中导入坐标:
...
org.springframework
spring-jdbc
5.0.5.RELEASE
org.mybatis
mybatis-spring
1.3.1
...
编写配置类:
public class JdbcConfig {
/*
* 简单类型的依赖注入使用@Value
* */
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/ssmdb")
private String url;
@Value("root")
private String username;
@Value("123456")
private String password;
// 1、定义一个方法获得要管理的对象
// 2、添加@Bean表示当前方法的返回值是一个bean
@Bean
/*
* 引用类型的依赖注入:主需要为bean定义方法设置形参即可,容器会根据 类型 自动装配对象。
* */
public DataSource dataSource(BookDao bookDao) {
System.out.println(bookDao);
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
public class MyBatisConfig {
/**
* SqlSessionFactoryBean专门产生SqlSessionFactory
* @return
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.clp.domain"); // 设置实体类别名
ssfb.setDataSource(dataSource); // 设置数据源
return ssfb;
}
/**
* 加载Dao层的映射信息
* @return
*/
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.clp.dao"); // 设置映射配置所在的包
return msc;
}
}
@Configuration
@Import({JdbcConfig.class, MyBatisConfig.class})
@ComponentScan("com.clp")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
}
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接回提示空指针异常,所以又不能轻易删掉。
pom.xml:
...
junit
junit
4.12
test
org.springframework
spring-test
5.2.10.RELEASE
...
// 使用Spring整合JUnit专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
// 指定Spring的配置类
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testFindAll() {
System.out.println(accountService.findAll());
}
}
应用上下文是通过new ClasspathXmlApplicationContext(spring配置文件)方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件),这样的弊端是配置文件加载多次,应用上下文对象创建多次。
在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,再将其存储到最大的域servletContext域中,这样就可以再任意位置从域中获得应用上下文ApplicationContext对象了。
Spring提供了一个监听器ContextLoaderListener,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。
所以我们需要做的只有两件事:
在web.xml中配置ContextLoaderListener监听器(要导入spring-web坐标);
使用WebApplicationContextUtils获得应用上下文对象ApplicationContext。
web.xml:
Archetype Created Web Application
contextConfigLocation
classpath:applicationContext.xml
org.springframework.web.context.ContextLoaderListener
package listener;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
//将Spring的应用上下文对象存储到ServletContext域中
ServletContext servletContext = sce.getServletContext();
String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
ApplicationContext app = new ClassPathXmlApplicationContext(contextConfigLocation);
servletContext.setAttribute("app",app);
System.out.println("spring容器创建完毕");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
package web;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import service.UserService;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ServletContext servletContext = req.getServletContext();
//ApplicationContext app = (ApplicationContext) servletContext.getAttribute("app");
ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
UserService userService = app.getBean(UserService.class);
userService.save();
}
}
它是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。
package com.test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import java.beans.PropertyVetoException;
public class JdbcTemplateTest {
@Test
//测试JdbcTemplate开发步骤
public void test1() throws PropertyVetoException {
//创建数据源对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/db?characterEncoding=utf-8");
dataSource.setUser("root");
dataSource.setPassword("123456");
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//设置数据源对象,知道数据库在哪
jdbcTemplate.setDataSource(dataSource);
//执行操作
int row = jdbcTemplate.update("insert into account values(?,?)", "李四", 5000);
System.out.println(row);
}
}
我们可以将JdbcTemplate的创建权交给Spring,将数据源DataSource的创建权也交给Spring,在Spring容器内部将数据源DataSource注入到JdbcTemplate模板对象中,配置如下:
jdbc.properties:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db>characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456
applicationContext.xml:
@Test
//测试spring产生jdbcTemplate模板对象
public void test2() {
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
JdbcTemplate jdbcTemplate = app.getBean(JdbcTemplate.class);
int row = jdbcTemplate.update("insert into account values(?,?)", "王五", 30);
System.out.println(row);
app.close();
}
package com.test;
import com.domain.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTemplateCRUDTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void test1() {
jdbcTemplate.update("update account set money=? where name=?",10000,"张三");
}
@Test
public void test2() {
List accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper(Account.class));
System.out.println(accountList);
}
@Test
public void test3() {
Account account = jdbcTemplate.queryForObject("select * from account where name=?",
new BeanPropertyRowMapper(Account.class), "张三");
System.out.println(account);
}
@Test
public void test4() {
Long count = jdbcTemplate.queryForObject("select count(*) from account",
Long.class);
System.out.println(count);
}
}
AOP(Aspect Oriented Programming)的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间耦合度降低,提高程序的可重用性,同时提高了开发的效率。
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。
优势:减少重复代码,提高开发效率,并且易于维护。
实际上,AOP的底层是通过Spring提供的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态地生成代理对象,代理对象方法执行时进行增强功能地接入,再去调用目标对象地方法,从而完成功能的增强。
常用的动态代理技术:
package com.proxy.jdk;
public class Advance {
public void before() {
System.out.println("前置增强..");
}
public void after() {
System.out.println("后置增强..");
}
}
package com.proxy.jdk;
public interface TargetInterface {
public void save();
}
package com.proxy.jdk;
public class Target implements TargetInterface{
@Override
public void save() {
System.out.println("save running...");
}
}
package com.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//创建目标对象
final Target target = new Target();
//创建增强对象
final Advance advance = new Advance();
//返回值就是动态生成的代理对象
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
//目标对象的类加载器
target.getClass().getClassLoader(),
//目标对象相同的字节码对象数组
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
//调用代理对象的任何方法,实质执行的都是invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置增强
advance.before();
//执行目标方法
Object invoke = method.invoke(target, args);
//后置增强
advance.after();
return invoke;
}
}
);
//调用代理对象的方法
proxy.save();
}
}
结果:
前置增强..
save running...
后置增强..
package com.proxy.cglib;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class ProxyTest {
public static void main(String[] args) {
//创建目标对象
final Target target = new Target();
//创建增强对象
final Advance advance = new Advance();
//返回值就是动态生成的代理对象,基于cglib
//1、创建增强器
Enhancer enhancer = new Enhancer();
//2、设置父类(目标)
enhancer.setSuperclass(Target.class);
//3、设置回调
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//执行前置
advance.before();
//执行目标
Object invoke = method.invoke(target, args);
//执行后置
advance.after();
return invoke;
}
});
//4、创建代理对象
Target proxy = (Target) enhancer.create();
proxy.save();
}
}
Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
AOP常用的术语如下:
- 描述方式1:执行com.itheima.dao包下的BookDao接口中的无参数update()方法
execution(void com.itheima.dao.BookDao.update())
- 描述方式2:
execution(void com.itheima.dao.impl.BookDaoImpl.update())
1、切点表达式的写法:
- 切入点表达式标准格式:
动作关键字execution(访问修饰符 返回值类型 包名.类/接口名.方法名(参数) 异常名)
例: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+.*(..))
2、切入点表达式的书写技巧
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置。
AOP通知功分为5种类型:
Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完成的代码逻辑运行。
在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
/**
* 切面类
*/
@Component // 该类受Spring管理
@Aspect // 说明该类作为AOP处理
public class MyAdvice {
/**
* 切入点1:注解@Pointcut说明该方法是一个切入点方法。method1()注解配置了该切入点,故会被读取
*/
@Pointcut("execution(void com.clp.dao.BookDao.save())")
private void pt1() {}
/**
* 切入点2(没有配置该切入点的切面,故不会读取该切入点)
*/
@Pointcut("execution(void com.clp.dao.BookDao.update())")
private void pt2() {}
/**
* 切面:绑定切点和通知(作为切面)
*/
@Before("pt1()")
public void method1() {
System.out.println(System.currentTimeMillis());
}
}
pom.xml:
...
org.springframework
spring-context
5.0.5.RELEASE
org.aspectj
aspectjweaver
1.8.4
...
package com.aop;
public interface TargetInterface {
public void save();
}
package com.aop;
public class Target implements TargetInterface {
@Override
public void save() {
System.out.println("save running...");
}
}
package com.aop;
public class MyAspect {
public void before() {
System.out.println("前置增强...");
}
}
applicationContext.xml:
...
...
package com.test;
import com.aop.TargetInterface;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AOPTest {
@Autowired
private TargetInterface target;
@Test
public void test1() {
target.save();
}
}
1、通知的类型:
通知的配置语法:
名称 | 标签 | 说明 |
---|---|---|
前置通知 | 用于配置前置通知。指定增强的方法在切入点方法之前执行。 | |
后置通知 | 用于配置后置通知。指定增强的方法在切入点方法之后执行。 | |
环绕通知 | 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行。 | |
异常抛出通知 | 用于配置异常抛出通知。指定增强的方法在出现异常时执行。 | |
最终通知 | 用于配置最终通知。无论增强方式执行是否有异常都会执行。 |
package com.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class MyAspect {
public void before() {
System.out.println("前置增强...");
}
public void afterReturning() {
System.out.println("后置增强...");
}
//ProceedingJoinPoint :正在执行的连接点, 即 切点
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("异常抛出增强...");
}
private void after() {
System.out.println("最终增强...");
}
}
2、切点表达式的抽取
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取后的切点表达式。
applicationContext.xml:
...
...
pom.xml:
注意:spring-context坐标依赖spring-aop坐标
...
org.springframework
spring-context
5.2.10.RELEASE
org.aspectj
aspectjweaver
1.9.4
...
@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {
@Override
public void update() {
System.out.println("book dao update ...");
}
}
@Configuration
@ComponentScan("com.clp")
@EnableAspectJAutoProxy // 告诉Spring,容器中有注解开发的AOP
public class SpringConfig {
}
/**
* 切面类
*/
@Component // 该类受Spring管理
@Aspect // 说明该类作为AOP处理
public class MyAdvice {
/**
* 切入点:注解@Pointcut说明该方法是一个切入点方法
*/
@Pointcut("execution(void com.clp.dao.BookDao.update())")
private void pt() {}
/**
* 切面:绑定切点和通知(作为切面),方法为通知,加上@Before绑定切入点方法pt()
*/
@Before("pt()")
public void method1() {
System.out.println(System.currentTimeMillis());
}
public void method2() {
}
}
在applicationContext.xml中配置的方式代码案例:
applicationContext.xml:
...
...
package com.anno;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component("myAspect")
@Aspect //标注当前MyAspect是一个切面类
public class MyAspect {
//配置前置增强
@Before("execution(* com.anno.*.*(..))")
public void before() {
System.out.println("前置增强...");
}
public void afterReturning() {
System.out.println("后置增强...");
}
//ProceedingJoinPoint :正在执行的连接点, 即 切点
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("异常抛出增强...");
}
private void after() {
System.out.println("最终增强...");
}
}
1、切点表达式的抽取
同xml配置aop一样,我们可以将切点表达式抽取。抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后再在增强注解中进行引用。
@Component("myAspect")
@Aspect
public class MyAspect {
@Before("MyAspect.myPoint()")
public void before() {
System.out.println("前置代码增强..");
}
@Pointcut("execution(* com.anno.*.*(..))")
public void myPoint() {}
}
2、AOP注解通知的类型
通知的配置语法:@通知注解("切点表达式")
名称 | 类型 | 位置 | 作用 | 相关属性 |
---|---|---|---|---|
@Before | 方法注解 | 通知方法定义的上方 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行 | value(默认):切入点方法名,格式为类名.方法名() |
@After | 方法注解 | 通知方法定义的上方 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行 | value(默认):切入点方法名,格式为类名.方法名() |
@Around | 方法注解 | 通知方法定义的上方 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行 | |
@AfterReturning | 方法注解 | 通知方法定义的上方 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行 | value(默认):切入点方法名,格式为类名.方法名() |
@AfterThrowing | 方法注解 | 通知方法定义的上方 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行 |
@Around注解使用注意事项:
代码演示:
@Configuration
@ComponentScan("com.clp")
@EnableAspectJAutoProxy // 告诉Spring,容器中有注解开发的AOP
public class SpringConfig {
}
@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {
@Override
public void update() {
System.out.println("book dao update is running ...");
}
@Override
public int select() {
System.out.println("book dao select is running ...");
return 100;
}
}
/**
* 切面类
*/
@Component // 该类受Spring管理
@Aspect // 说明该类作为AOP处理
public class MyAdvice {
/**
* 切点
*/
@Pointcut("execution(void com.clp.dao.BookDao.update())")
private void pt() {
}
@Pointcut("execution(int com.clp.dao.BookDao.select())")
private void pt2() {
}
/**
* 切点 + 通知 = 切面
*/
@Before("pt()")
public void before() {
System.out.println("before advice ...");
}
@After("pt()")
public void after() {
System.out.println("after advice ...");
}
@After("pt2()")
public void afterSelect() {
System.out.println("after advice ...");
}
@Around("pt()")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
pjp.proceed(); // 表示对原始操作的调用
System.out.println("around after advice ...");
}
@Around("pt2()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
int res = (int) pjp.proceed();// 表示对原始操作的调用
System.out.println("around after advice ...");
return res + 111;
}
/**
* 切点方法正常执行完才会执行该通知
*/
@AfterReturning("pt2()")
public void afterReturning() {
System.out.println("after returning advice ...");
}
/**
* 切点方法抛出异常才会执行该通知
*/
@AfterThrowing("pt2()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
}
获取切入点方法的参数:
获取切入点方法返回值:
获取切入点方法运行异常信息:
@Repository("bookDaoImpl")
public class BookDaoImpl implements BookDao {
@Override
public String findName(int id, String password) {
System.out.println("id: " + id);
return "itcast";
}
}
/******************************************************************************/
/**
* 切面类
*/
@Component // 该类受Spring管理
@Aspect // 说明该类作为AOP处理
public class MyAdvice {
/**
* 切点
*/
@Pointcut("execution(* com.clp.dao.impl.BookDaoImpl.findName(..))")
private void pt() {
}
/**
* 切点 + 通知 = 切面
*
* @param jp :JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
*/
@Before("pt()")
public void before(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println("before advice ..." + Arrays.toString(args));
}
@After("pt()")
public void after(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println("after advice ..." + Arrays.toString(args));
}
/**
* ProceedingJoinPoint是JoinPoint的子类
* 环绕通知中可以手工获取切入点方法中出现的异常信息,得到的结果即为原始方法的返回值
*
* @param pjp
* @return
* @throws Throwable
*/
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
System.out.println("around before advice ..." + Arrays.toString(args));
args[0] = 666; // 修改切点方法的第1个参数
Object ret = pjp.proceed(args);// 表示对原始操作的调用
System.out.println("around after advice ..." + Arrays.toString(args));
return ret;
}
/**
* 如果原始方法有返回值,那么就将返回值 “装到” 变量ret中
*
* @param joinPoint:必须为方法中的第1个参数(可省略)
* @param ret:原始方法的返回值
*/
@AfterReturning(value = "pt()", returning = "ret")
public void afterReturning(JoinPoint joinPoint, Object ret) {
System.out.println("afterReturning advice ..." + ret); // ret 为返回结果(参数名必须与注解中returning=的值相同)
}
/**
* 抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
*
* @param t:方法抛出的异常
*/
@AfterThrowing(value = "pt()", throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ...");
}
}
/******************************************************************************/
public class AppAnno {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = (BookDao) ctx.getBean("bookDaoImpl");
bookDao.findName(100, "abc");
}
}
结果:
around before advice ...[100, abc]
before advice ...[666, abc]
id: 666
afterReturning advice ...itcast
after advice ...[666, abc]
around after advice ...[666, abc]
需求:任意业务层接口执行均可显示其执行效率(执行时长)。
分析:
补充说明:当前测试的接口执行效率仅仅是一个理论值,并不是一次完整的执行过程。
代码演示:
public class JdbcConfig {
/*
* 简单类型的依赖注入使用@Value
* */
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/ssmdb")
private String url;
@Value("root")
private String username;
@Value("123456")
private String password;
// 1、定义一个方法获得要管理的对象
// 2、添加@Bean表示当前方法的返回值是一个bean
/*
* 引用类型的依赖注入:主需要为bean定义方法设置形参即可,容器会根据 类型 自动装配对象。
* */
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
/*********************************************************************************/
public class MyBatisConfig {
/**
* SqlSessionFactoryBean专门产生SqlSessionFactory
* @return
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.clp.domain"); // 设置实体类别名
ssfb.setDataSource(dataSource); // 设置数据源
return ssfb;
}
/**
* 加载Dao层的映射信息
* @return
*/
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.clp.dao");
return msc;
}
}
/*********************************************************************************/
@Configuration
@Import({JdbcConfig.class, MyBatisConfig.class})
@ComponentScan("com.clp")
@PropertySource("classpath:jdbc.properties")
@EnableAspectJAutoProxy // 告诉Spring,容器中有注解开发的AOP
public class SpringConfig {
}
/*********************************************************************************/
@Component
@Aspect
public class ProjectAdvice {
/**
* 匹配业务层的所有方法
*/
@Pointcut("execution(* com.clp.service.*Service.*(..))")
private void servicePc() {}
@Around("ProjectAdvice.servicePc()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
// 获取执行的签名信息
Signature signature = pjp.getSignature();
// 通过签名获取执行类型(接口名)
String className = signature.getDeclaringTypeName();
// 通过签名获取执行操作名称(方法名)
String name = signature.getName();
// 记录时间
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行:"+ className + "." + name + " ----> " + (end - start) + "ms");
}
}
/*********************************************************************************/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testFindById() {
Account account = accountService.findById(3);
}
@Test
public void testFindAll() {
accountService.findAll();
}
}
结果:
万次执行:com.clp.service.AccountService.findAll---->1651ms
分析:
@Repository
public class ResourcesDaoImpl implements ResourcesDao {
@Override
public boolean readResources(String url, String password) {
System.out.println(password.length());
// 模拟校验
return password.equals("root");
}
}
@Component
@Aspect
public class DataAdvice {
@Pointcut("execution(boolean com.clp.service.*Service.*(*, *))")
private void servicePc() {}
@Around("servicePc()")
public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
// 对原始参数的每一个参数进行操作
for (int i = 0; i < args.length; i++) {
// 判断参数是不是字符串,如果是,就处理空格
if (args[i].getClass().equals(String.class)) {
// 去除数据,trim()操作后,更新数据
args[i] = args[i].toString().trim();
}
}
Object ret = pjp.proceed(args);
return ret;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class ResourcesServiceTest {
@Autowired
private ResourcesService resourcesService;
@Test
public void test() {
boolean flag = resourcesService.openUrl("http://pan.baidu.com/aaa", " root ");
System.out.println(flag);
}
}
结果:
4
true
事务作用:在数据层保障一系列的数据库操作同时成功同时失败。
Spring事务作用:在数据层或业务层保障一系列的数据库操作同时成功同时失败。
设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。
事务传播行为:事务协调员对事务管理员所携带事务的处理态度。
传播属性 | 事务管理员 | 事务协调员 |
---|---|---|
REQUIRED(默认) | 开启T |
加入T |
未开启T | 新建T2 | |
REQUIRES_NEW | 开启T |
新建T2 |
未开启T | 新建T2 | |
SUPPORTS | 开启T | 加入T |
未开启T | 无 | |
NOT_SUPPORTED | 开启T | 无 |
未开启T | 无 | |
MANDATORY | 开启T | 加入T |
未开启T | ERROR | |
NEVER | 开启T | ERROR |
未开启T | 无 | |
NESTED | 设置savePoint,一旦事务回滚,事务将回滚到savePoint处,交由客户端响应提交/回滚 |
PlatformTransactionManager接口是Spring的事务管理器,它里面提供了我们常用的操作事务的方法。
方法 | 说明 |
---|---|
TransactionStatus getTransaction(TransactionDefination defination) | 获取事务的状态信息 |
void commit(TransactionStatus status) | 提交事务 |
void rollback(TransactionStatus) | 回滚事务 |
注意:PlatformTransactionManager是接口类型,不同的Dao层技术则有不同的实现类,例如:Dao层技术是jdbc或mybatis时:org.springframework.jdbc.datasource.DataSourceTransactionManager;Dao层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager。
TransactionDefinition是事务的定义信息对象,里面有如下方法:
方法 | 说明 |
---|---|
int getIsolationLevel() | 获得事务的隔离级别 |
int getPropogationBehavior() | 获得事务的传播行为 |
int getTimeout() | 获得超时时间 |
boolean isReadOnly() | 是否只读 |
TransactionStatus接口提供的是事务具体的运行状态,方法介绍如下:
方法 | 说明 |
---|---|
boolean hasSavepoint() | 是否存储回滚点 |
boolean isCompleted() | 事务是否完成 |
boolean isNewTransaction() | 是否是新事务 |
boolean isRollbackOnly() | 事务是否回滚 |
Spring 的声明式事务控制顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在Spring配置文件中声明式地处理事务来代替代码式的处理事务。
声明式事务处理的作用:
注意:Spring声明式事务控制底层就是AOP。
声明式事务控制明确事项:
applicationContext.xml:
...
...
其中,
步骤:
- 方式1:在实现类配置:
@Service("accountService")
@Transactional(isolation = Isolation.REPEATABLE_READ) //表示该对象下的所有方法都为这种配置,若方法有自己的配置,则方法用自己的配置
public class AccountServiceImpl {
@Autowired
private AccountDaoImpl accountDao;
//
//
//
//
//
//
//
//
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public void transfer(String outMan, String inMan, double money) {
accountDao.out(outMan, money);
accountDao.in(inMan,money);
}
@Transactional(isolation = Isolation.DEFAULT)
public void xxx() {}
}
- 方式2:在接口配置
@Transactional(isolation = Isolation.REPEATABLE_READ) //表示该接口下的所有方法都为这种配置,若方法有自己的配置,则方法用自己的配置
public interface AccountService {
/**
* 转账操作
* @param out:转出方
* @param in:转入方
* @param money:金额
*/
@Transactional
void transfer(String out, String in, Double money);
}
- 配置平台事务管理器方式1:
applicationContext.xml:
...
...
- 配置平台事务管理器方式2:
public class JdbcConfig {
/*
* 简单类型的依赖注入使用@Value
* */
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/ssmdb")
private String url;
@Value("root")
private String username;
@Value("123456")
private String password;
// 1、定义一个方法获得要管理的对象
// 2、添加@Bean表示当前方法的返回值是一个bean
/*
* 引用类型的依赖注入:主需要为bean定义方法设置形参即可,容器会根据 类型 自动装配对象。
* */
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
/**
* 创建平台事务管理器
* @param dataSource
* @return
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager dtm = new DataSourceTransactionManager();
dtm.setDataSource(dataSource);
return dtm;
}
}
@Configuration
@Import({JdbcConfig.class, MyBatisConfig.class})
@ComponentScan("com.clp")
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement // 开启注解式事务驱动
public class SpringConfig {
}
public class MyBatisConfig {
/**
* SqlSessionFactoryBean专门产生SqlSessionFactory
* @return
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.clp.domain"); // 设置实体类别名
ssfb.setDataSource(dataSource); // 设置数据源
return ssfb;
}
/**
* 加载Dao层的映射信息
* @return
*/
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.clp.dao");
return msc;
}
}
注解声明式事务控制的配置要点:
属性 | 作用 | 示例 |
---|---|---|
readOnly | 设置是否为只读事务 | readOnly=true(只读事务) |
timeout | 设置事务超时时间 | timeout=-1(永不超时) |
rollbackFor | 设置事务回滚异常(class) | rollbackFor=如NullPointException.class |
rollbackForClassName | 设置事务回滚异常(String) | 同上,格式为字符串 |
noRollbackFor | 设置事务不回滚异常(class) | noRollbackFor=如NullPointException.class |
noRollbackForClassName | 设置事务不回滚异常(String) | 同上,格式为字符串 |
propagation | 设置事务传播行为 | ... |
public interface AccountService {
@Transactional(readOnly = true, timeout = -1, rollbackFor = {IOException.class})
void transfer(String out, String in, Double money);
}
模拟银行账户间转账业务。
需求:实现任意两个账户间转账操作。
需求微缩:A账户减钱,B账户加钱。
分析:
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String out, String in, Double money) {
accountDao.outMoney(out, money);
accountDao.inMoney(in, money);
}
}
结果分析:
需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕。
需求微缩:A账户减钱,B账户加钱,数据库记录日志。
分析:
实现效果预期:无论转账是否成功,均进行转账操作的日志留痕。
步骤:
@Service
public class LogServiceImpl implements LogService {
@Autowired
private LogDao logDao;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String out, String in, Double money) {
logDao.log("转账操作由" + out + "到" + in + ", 金额:" + money);
}
}