Java-Spring
Spring
一、基本概念
IOC(Inversion of Control)控制反转:使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
IOC容器:Spring提供了一个容器,称为IOC容器,用来充当IOC思想中的"外部"。
Bean:被创建或被管理的对象在IOC容器中统称为Bean。
DI(Dependency Injection)依赖注入:在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入。如业务层需要依赖数据层,service就要和dao建立依赖关系。
二、入门案例
2.1 IOC入门案例
需求分析:将BookServiceImpl和BookDaoImpl交给Spring管理,并从容器中获取对应的bean对象进行方法调用。
1.创建Maven的java项目
2.pom.xml添加Spring的依赖jar包
3.创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类
4.resources下添加spring配置文件,并完成bean的配置
5.使用Spring提供的接口完成IOC容器的创建
6.从容器中获取对象进行方法调用
- 创建Maven项目,添加Spring的依赖jar包
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
- 添加案例中需要的类
创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
- 添加spring配置文件
resources下添加spring配置文件applicationContext.xml
,并完成bean的配置
- 在配置文件中完成bean的配置
<?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">
<!--bean标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="fun.it.service.impl.BookServiceImpl"/>
</beans>
注意事项:bean定义时id属性在同一个上下文中(配置文件)不能重复
- 获取IOC容器,从容器中获取对象进行方法调用
public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// bookDao.save();
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
结果:
在BookServiceImpl
的类中依然存在BookDaoImpl
对象的new操作,它们之间的耦合度还是比较高,就需要用到下面的DI:依赖注入
。
2.2 DI入门案例
需求:基于IOC入门案例,在BookServiceImpl类中删除new对象的方式,使用Spring的DI完成Dao层的注入
1.删除业务层中使用new的方式创建的dao对象
2.在业务层提供BookDao的setter方法
3.在配置文件中添加依赖注入的配置
4.运行程序调用方法
- 去除代码中的new
在BookServiceImpl类中,删除业务层中使用new的方式创建的dao对象
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
- 为属性提供setter方法
在BookServiceImpl类中,为BookDao提供setter方法
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
- 修改配置完成注入
<?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">
<!--bean标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="fun.it.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
注意:配置中property的两个bookDao的含义是不一样的
- name="bookDao"中
bookDao
的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前面加set找对应的setBookDao()
方法进行对象注入 - ref="bookDao"中
bookDao
的作用是让Spring能在IOC容器中找到id为bookDao
的Bean对象给bookService
进行注入 - 综上所述,对应关系如下:
- 运行结果
三、IOC相关
3.1 Bean基础配置
- id与class属性
id:使容器可以通过id获取对应的bean,在一个容器中id唯一
class:bean的类型,即配置的bean的全路径类名
<bean id="bookService" class="fun.it.service.impl.BookServiceImpl"/>
- name属性
用来给bean配置别名,程序中可以根据别名来获取bean对象。
<bean id="bookService" name="service service4 bookEbi"
class="fun.it.service.impl.BookServiceImpl" />
- scope属性
控制bean的作用范围,singleton
为单例模式(默认),prototype
为非单例模式。
<bean id="bookDao" name="dao" class="fun.it.dao.impl.BookDaoImpl"
scope="singleton"/>
3.2 Bean实例化
3.2.1 构造方法实例化
- 准备一个BookDao和BookDaoImpl类
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
- 将类配置到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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"/>
</beans>
- 运行程序
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
Spring底层通过反射访问无参的构造方法,来创建bean对象。
3.2.2 静态工厂实例化
- 准备一个OrderDao和OrderDaoImpl类
public interface OrderDao {
public void save();
}
public class OrderDaoImpl implements OrderDao {
public void save() {
System.out.println("order dao save ...");
}
}
- 创建一个工厂类OrderDaoFactory并提供一个静态方法
//静态工厂创建对象
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
- 编写配置文件
<bean id="orderDao" class="fun.it.factory.OrderDaoFactory"
factory-method="getOrderDao"/>
- 编写AppForInstanceOrder运行类,使用从IOC容器中获取bean的方法进行运行测试
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
3.2.3 实例工厂与FactoryBean接口
实例工厂实例化
- 准备一个UserDao和UserDaoImpl类
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("user dao save ...");
}
}
- 创建一个工厂类OrderDaoFactory并提供一个普通方法,并非静态方法
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
- 编写配置文件
<bean id="userFactory" class="fun.it.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
- 在AppForInstanceUser运行类,使用从IOC容器中获取bean的方法进行运行测试
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
FactoryBean接口的使用
- 创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
}
- 在Spring的配置文件中进行配置
<bean id="userDao" class="fun.it.factory.UserDaoFactoryBean"/>
- 运行程序
如要设置非单例模式,重写接口中的
isSingleton
方法。
3.3 Bean的生命周期
bean生命周期是指bean对象从创建到销毁的整体过程。包含bean创建之前和bean销毁之后两个过程。
要观察到bean销毁的过程,需要关闭容器或注册钩子关闭容器。
ClassPathXmlApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
ctx.close(); // 调用ctx的close()方法来关闭容器
// 调用ctx的registerShutdownHook()方法,让JVM在退出之前回调此函数来关闭容器
ctx.registerShutdownHook();
控制bean生命周期的两种方式:
方式一:
- 添加初始化和销毁方法
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destory...");
}
}
- 配置生命周期
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
方式二:
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
public void destroy() throws Exception {
System.out.println("service destroy");
}
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
}
配置文件中不需要额外配置,初始化方法会在类中属性设置之后(set操作)执行。
3.4 容器的创建方式
// 通过类路径下的XML配置文件创建
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// 通过文件系统下的XML配置文件创建
ApplicationContext ctx2 = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
3.5 Bean的三种获取方式
// 方式一
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// 方式二
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
// 方式三
BookDao bookDao = ctx.getBean(BookDao.class);
3.6 容器类层次结构
3.7 BeanFactory
通过使用BeanFactory来创建IOC容器
public class AppForBeanFactory {
public static void main(String[] args) {
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean(BookDao.class);
bookDao.save();
}
}
BeanFactory是延迟加载,只有在获取bean对象的时候才会去创建
ApplicationContext是立即加载,容器加载的时候就会创建bean对象
配置ApplicationContext为延迟加载
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl" lazy-init="true"/>
四、DI
4.1 setter注入
4.1.1 注入引用数据类型
public class BookServiceImpl implements BookService {
private BookDao bookDao;
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
配置文件:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="fun.it.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="fun.it.service.impl.BookServiceImpl">
<!--配置中使用property标签ref属性注入引用类型对象-->
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
4.1.2 注入简单数据类型
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
}
配置文件:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl">
<property name="databaseName" value="mysql"/>
<property name="connectionNum" value="10"/>
</bean>
4.2 构造器注入
4.2.1 构造器注入引用数据类型
public class BookServiceImpl implements BookService{
private BookDao bookDao;
private UserDao userDao;
public BookServiceImpl(BookDao bookDao,UserDao userDao) {
this.bookDao = bookDao;
this.userDao = userDao;
}
}
配置文件:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="fun.it.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="fun.it.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
标签
<constructor-arg>
中,name属性对应的值为构造函数中方法形参的参数名,必须要保持一致
<contructor-arg>
的配置顺序可以任意
4.2.2 构造器注入简单数据类型
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public BookDaoImpl(String databaseName, int connectionNum) {
this.databaseName = databaseName;
this.connectionNum = connectionNum;
}
}
配置文件:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="666"/>
</bean>
其他写法:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl">
<constructor-arg type="int" value="10"/>
<constructor-arg type="java.lang.String" value="mysql"/>
</bean>
其他写法:
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl">
<constructor-arg index="1" value="100"/>
<constructor-arg index="0" value="mysql"/>
</bean>
4.3 自动配置
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
配置文件:
<bean class="fun.it.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="fun.it.service.impl.BookServiceImpl"
autowire="byType"/>
<!--一个类型在IOC中有多个对象时可以按照名称注入-->
<bean id="bookService" class="fun.it.service.impl.BookServiceImpl"
autowire="byName"/>
注意
- 需要注入属性的类中对应属性的setter方法不能省略
- 被注入的对象必须要被Spring的IOC容器管理
- 按名称注入指的是set方法去掉set后首字母小写得到的名字
4.4 集合注入
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;
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);
}
//setter....方法省略,自己使用工具生成
}
- 注入数组类型
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
- 注入List类型数据
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
- 注入Set类型数据
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
- 注入Map类型数据
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
- 注入Properties类型数据
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
- 集合中注入引用类型
<property name="array">
<array>
<ref bean="beanId1">
<ref bean="beanId2">
</array>
</property>
property标签表示setter方式注入,构造方式注入constructor-arg标签内部也可以写
<array>
、<list>
、<set>
、<map>
、<props>
标签
五、IOC/DI配置管理第三方bean
5.1 数据源对象管理
准备环境:
- 创建Maven项目,添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
- resources下添加spring的配置文件
applicationContext.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">
</beans>
- 编写一个运行类App
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
5.1.1 实现Druid管理
- 导入
druid
的依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
- 配置第三方bean
在applicationContext.xml配置文件中添加DruidDataSource
的配置
<?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">
<!--管理DruidDataSource对象-->
<bean class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
- 从IOC容器中获取对应的bean对象
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
}
}
- 运行程序
打印如下结果: 说明第三方bean对象已经被spring的IOC容器进行管理
5.1.2 实现C3P0管理
- 导入
C3P0
的依赖
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
<!--C3P0在初始化的时候需要去加载驱动-->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
查找依赖的坐标:从mvn的仓库https://mvnrepository.com/
中进行搜索。
- 配置第三方bean
在applicationContext.xml配置文件中添加配置
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="maxPoolSize" value="1000"/>
</bean>
- 运行程序
5.2 加载properties文件
5.2.1 第三方bean属性优化
- 准备properties配置文件
resources下创建一个jdbc.properties
文件,并添加对应的属性键值对
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root
- 开启
context
命名空间
在applicationContext.xml中开context
命名空间
<?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">
</beans>
- 加载properties配置文件
在配置文件中使用context
命名空间下的标签来加载properties配置文件
<context:property-placeholder location="jdbc.properties"/>
- 完成属性注入
使用${key}
来读取properties配置文件中的内容并完成属性注入
<?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">
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<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>
</beans>
5.2.2 读取单个属性
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
private String name;
public void setName(String name) {
this.name = name;
}
public void save() {
System.out.println("book dao save ..." + name);
}
}
配置文件:
<?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">
<context:property-placeholder location="jdbc.properties"/>
<bean id="bookDao" class="fun.it.dao.impl.BookDaoImpl">
<property name="name" value="${jdbc.driver}"/>
</bean>
</beans>
5.2.3 其他问题
- 键值对的key为
username
键值对的key为username
时,属性注入时会将系统变量中的username进行注入,即自己电脑的用户名。设置context的system-properties-mode属性为NEVER,表示不加载系统属性,就可以解决上述问题。
<context:property-placeholder location="jdbc.properties"
system-properties-mode="NEVER"/>
- 加载多个配置文件
<!--方式一 -->
<context:property-placeholder location="jdbc.properties,jdbc2.properties"
system-properties-mode="NEVER"/>
<!--方式二-->
<context:property-placeholder location="*.properties"
system-properties-mode="NEVER"/>
<!--方式三 -->
<context:property-placeholder location="classpath:*.properties"
system-properties-mode="NEVER"/>
<!--方式四-->
<context:property-placeholder location="classpath*:*.properties"
system-properties-mode="NEVER"/>
说明:
- 方式一:可以实现,如果配置文件多的话,每个都需要配置
- 方式二:
*.properties
代表所有以properties结尾的文件都会被加载,可以解决方式一的问题,但是不标准 - 方式三:标准的写法,
classpath:
代表的是从根路径下开始查找,但是只能查询当前项目的根路径 - 方式四:不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的properties配置文件
六、IOC/DI注解开发
6.1 环境准备
- 创建Maven项目,添加Spring的依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
- 添加BookDao、BookDaoImpl、BookService、BookServiceImpl类
public interface BookDao {
public void save();
}
@Component("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ..." );
}
}
public interface BookService {
public void save();
}
// 注解如果不起名称,会有一个默认值就是当前类名首字母小写
@Component
public class BookServiceImpl implements BookService {
public void save() {
System.out.println("book service save ...");
}
}
- resources下添加applicationContext.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">
<!--配置Spring的注解包扫描-->
<context:component-scan base-package="fun.it"/>
</beans>
- 创建运行类App
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
BookService bookService = (BookService)ctx.getBean("bookServiceImpl");
System.out.println(bookService);
}
}
衍生注解:
@Component注解,有三个衍生注解@Controller
、@Service
、@Repository
,便于区分出这个类是属于表现层
、业务层
还是数据层
的类。
6.2 纯注解开发
- 创建配置类
创建配置类SpringConfig
,标识该类为配置类,并添加包扫描注解。
@Configuration
@ComponentScan("fun.it")
public class SpringConfig {
}
- 创建运行类并执行
public class AppForAnnotation {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao);
BookService bookService = ctx.getBean(BookService.class);
System.out.println(bookService);
}
}
6.3 注解开发bean作用范围与生命周期管理
- 配置bean为非单例模式,用
@scope
注解
@Repository
// @Scope设置bean的作用范围
// 单例singleton(默认),非单例prototype
@Scope("prototype")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
- 管理bean的生命周期,用
@PostConstruct
和@PreDestroy
注解
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
@PostConstruct //在构造方法之后执行,替换 init-method
public void init() {
System.out.println("init ...");
}
@PreDestroy //在销毁方法之前执行,替换 destroy-method
public void destroy() {
System.out.println("destroy ...");
}
}
注意:@PostConstruct和@PreDestroy注解如果找不到,需要导入下面的jar包。从JDK9以后jdk中的javax.annotation包被移除了,这两个注解刚好就在这个包中。
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
6.4 依赖注入
6.4.1 按类型注入
使用@Autowired
注解,可以写在属性上,也可也写在setter方法上。
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
// 自动装配基于反射为属性进行赋值,不需要set方法
// public void setBookDao(BookDao bookDao) {
// this.bookDao = bookDao;
// }
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
6.4.2 按名称注入
使用@Qualifier
注解,@Qualifier
不能独立使用,必须和@Autowired
一起使用。
@Service
public class BookServiceImpl implements BookService {
@Autowired
@Qualifier("bookDao1")
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
6.4.3 简单数据类型注入
使用@Value
注解,将值写入注解的参数中即可。
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("MyBook")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
6.4.4 注解读取properties配置文件
- 使用注解加载resource下的properties配置文件
在配置类上添加@PropertySource
注解
@Configuration
@ComponentScan("fun.it")
@PropertySource("jdbc.properties")
public class SpringConfig {
}
- 使用@Value读取配置文件中的内容
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("${name}")
private String name;
public void save() {
System.out.println("book dao save ..." + name);
}
}
6.5 常用注解
@Component
作用:设置该类为spring管理的bean
用法:写在类上方
备注:
- @Component("bookDao"):设置value属性来定义bean的id
- @Controller/@Service/@Repository为 三个衍生注解
@Configuration
作用:设置该类为spring配置类
用法:写在配置类上方
@ComponentScan
作用:设置spring配置类扫描路径,用于加载使用注解格式定义的bean
用法:写在配置类上方,@ComponentScan("com.qq")
备注:
- @ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据使用数组格式
- @ComponentScan({"com.qq.service","com.qq.dao"})
@Scope
作用:设置该类创建对象的作用范围,可用于设置创建出的bean是否为单例对象
用法:写在类上方
备注:value(默认):定义bean作用范围,默认值singleton(单例),可选值prototype(非单例)
@PostConstruct
作用:设置该方法为初始化方法
用法:写在方法上
@PreDestroy
作用:设置该方法为销毁方法
用法:写在方法上
@Autowired
作用:按属性类型自动装配,为引用类型属性设置值
用法:属性定义上方或set方法上方
备注:属性required:true/false,定义该装配的属性是否允许为null
@Qualifier
作用:为引用类型属性指定注入的beanId
用法:属性定义上方或set方法上方,@Qualifier("bookDao1")
备注:需搭配@Autowired
一起使用
@Value
作用:为基本数据类型或字符串类型属性设置值
用法:属性定义上方或set方法上方,@Value("QQ")
@PropertySource
作用:加载properties文件中的属性值
用法:写在类上方,@PropertySource("jdbc.properties")
备注:
@PropertySource
注解属性中不支持使用通配符*
- 多个配置文件,@PropertySource({"jdbc.properties","xxx.properties"})
- 注解属性中可以把
classpath:
加上,代表从当前项目的根路径找文件,@PropertySource({"classpath:jdbc.properties"})
@Bean
作用:设置该方法的返回值作为spring管理的bean
用法:写在方法上方
备注:属性value(默认):定义bean的id
@Import
作用:导入配置类
用法:写在类上方,@Import(JdbcConfig.class)
备注:多个配置类时,@Import({JdbcConfig.class,Xxx.class})
@RunWith
作用:设置JUnit运行器
用法:写在测试类上方,@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
作用:设置JUnit加载的Spring核心配置
用法:写在测试类上方
备注:
- 加载配置类:@ContextConfiguration(classes = {SpringConfiguration.class})
- 加载配置文件:@ContextConfiguration(locations={"classpath:applicationContext.xml"})
@EnableAspectJAutoProxy
作用:开启注解格式AOP功能
用法:写在配置类上方
@Aspect
作用:设置当前类为AOP切面类
用法:写在切面类定义上方
@Pointcut
作用:设置切入点方法
用法:写在切入点方法定义上方
备注:切入点表达式
@Pointcut("execution(* fun.it.*.*Service.*(..))")
@Before
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
用法:写在通知方法定义上方,@Before("pt()")
@After
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
用法:写在通知方法定义上方,@After("pt()")
@AfterReturning
作用:设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行
用法:写在通知方法定义上方,@AfterReturning("pt()")
@AfterThrowing
作用:设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
用法:写在通知方法定义上方,@AfterThrowing("pt()")
@Around
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
用法:写在通知方法定义上方,@Around("pt()")
@EnableTransactionManagement
作用:设置当前Spring环境中开启注解式事务支持
用法:写在配置类定义上方
@Transactional
作用:为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务)
用法:写在业务层接口上方 业务层实现类上方 业务方法上方
备注:事务属性
作用:
用法:
备注:
作用:
用法:
备注:
七、IOC/DI注解开发管理第三方bean
- 引入Druid对应的jar包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
- 创建
JdbcConfig
配置类,在返回bean的方法上添加@Bean
注解
public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
}
- 在Spring配置类中使用
@Import
引入
@Configuration
// 也可以在Jdbc配置类上加@Configuration注解,然后去扫描
//@ComponentScan("fun.it.config")
@Import({JdbcConfig.class})
public class SpringConfig {
}
- 创建的DataSource对象就已经交给了IOC容器进行管理
为第三方bean注入资源
- 简单数据类型,使用@Value注解
public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/spring_db")
private String url;
@Value("root")
private String userName;
@Value("password")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 引用数据类型
在返回bean的方法中设置形参即可,容器会根据类型自动装配对象。
@Bean
public DataSource dataSource(BookDao bookDao){
System.out.println(bookDao);
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
八、Spring整合
8.1 Spring整合MyBatis
8.1.1 环境准备
- 准备数据库
创建数据库和表
create database spring_db character set utf8;
use spring_db;
create table tbl_account(
id int primary key auto_increment,
name varchar(35),
money double
);
- 创建项目导入jar包
项目的pom.xml添加相关依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
- 根据表创建模型类
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
//setter...getter...toString...方法略
}
- 创建Dao接口
public interface AccountDao {
@Insert("insert into tbl_account(name,money)values(#{name},#{money})")
void save(Account account);
@Delete("delete from tbl_account where id = #{id} ")
void delete(Integer id);
@Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")
void update(Account account);
@Select("select * from tbl_account")
List<Account> findAll();
@Select("select * from tbl_account where id = #{id} ")
Account findById(Integer id);
}
- 创建Service接口和实现类
public interface AccountService {
void save(Account account);
void delete(Integer id);
void update(Account account);
List<Account> findAll();
Account findById(Integer id);
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void save(Account account) {
accountDao.save(account);
}
public void update(Account account){
accountDao.update(account);
}
public void delete(Integer id) {
accountDao.delete(id);
}
public Account findById(Integer id) {
return accountDao.findById(id);
}
public List<Account> findAll() {
return accountDao.findAll();
}
}
- 添加jdbc.properties文件
resources目录下添加,用于配置数据库连接四要素
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root
useSSL:关闭MySQL的SSL连接
- 添加Mybatis核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--读取外部properties配置文件-->
<properties resource="jdbc.properties"></properties>
<!--别名扫描的包路径-->
<typeAliases>
<package name="fun.it.domain"/>
</typeAliases>
<!--数据源-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!--映射文件扫描包路径-->
<mappers>
<package name="fun.it.dao"></package>
</mappers>
</configuration>
- 编写应用程序
public class App {
public static void main(String[] args) throws IOException {
// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 加载SqlMapConfig.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 3. 创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 4. 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5. 执行SqlSession对象执行查询,获取结果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
Account ac = accountDao.findById(1);
System.out.println(ac);
// 6. 释放资源
sqlSession.close();
}
}
- 运行程序
8.1.2 Spring整合MyBatis
- 项目中导入整合需要的jar包
<dependency>
<!--Spring操作数据库需要该jar包-->
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<!--
Spring与Mybatis整合的jar包
这个jar包mybatis在前面,是Mybatis提供的
-->
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
- 创建Spring的主配置类
//配置类注解
@Configuration
//包扫描,主要扫描的是项目中的AccountServiceImpl类
@ComponentScan("fun.it")
public class SpringConfig {
}
- 创建数据源的配置类
在配置类中完成数据源的创建
public class JdbcConfig {
@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
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
- 主配置类中读properties并引入数据源配置类
@Configuration
@ComponentScan("fun.it")
@PropertySource("classpath:jdbc.properties")
@Import(JdbcConfig.class)
public class SpringConfig {
}
- 创建Mybatis配置类并配置SqlSessionFactory
public class MybatisConfig {
//定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
//设置模型类的别名扫描
ssfb.setTypeAliasesPackage("fun.it.domain");
//设置数据源
ssfb.setDataSource(dataSource);
return ssfb;
}
//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("fun.it.dao");
return msc;
}
}
- 主配置类中引入Mybatis配置类
@Configuration
@ComponentScan("fun.it")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
- 编写运行类
public class App2 {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
AccountService accountService = ctx.getBean(AccountService.class);
Account ac = accountService.findById(1);
System.out.println(ac);
}
}
- 运行程序
8.2 Spring整合Junit
- 创建项目,引入依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
- 在test\java下创建一个AccountServiceTest,这个名字任意
//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = {SpringConfiguration.class}) //加载配置类
//@ContextConfiguration(locations={"classpath:applicationContext.xml"})//加载配置文件
public class AccountServiceTest {
//支持自动装配注入bean
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
System.out.println(accountService.findById(1));
}
@Test
public void testFindAll(){
System.out.println(accountService.findAll());
}
}
九、AOP
9.1 基本概念
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。可以在不惊动原始设计的基础上为其进行功能增强。内部使用了代理模式。
连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。在SpringAOP中,理解为方法的执行。
切入点(Pointcut):匹配连接点的式子。在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法。
通知(Advice):在切入点处执行的操作,也就是共性功能。在SpringAOP中,功能最终以方法的形式呈现。
通知类:定义通知的类。
切面(Aspect):描述通知与切入点的对应关系。
9.2 入门案例
9.2.1 环境准备
- 创建Maven项目,添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
- 添加BookDao和BookDaoImpl类
public interface BookDao {
public void save();
public void update();
}
@Repository
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println(System.currentTimeMillis());
System.out.println("book dao save ...");
}
public void update(){
System.out.println("book dao update ...");
}
}
- 创建Spring的配置类
@Configuration
@ComponentScan("fun.it")
public class SpringConfig {
}
- 编写App运行类
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.save();
}
}
9.2.2 AOP实现步骤
目标:使用SpringAOP的方式在不改变update方法的前提下让其具有打印系统时间的功能。
- 添加依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
- 因为
spring-context
中已经导入了spring-aop
,所以不需要再单独导入spring-aop
- 导入AspectJ的jar包,AspectJ是AOP思想的一个具体实现,Spring有自己的AOP实现,但是相比于AspectJ来说比较麻烦,所以我们直接采用Spring整合ApsectJ的方式进行AOP开发。
- 定义接口与实现类
环境准备的时候,BookDaoImpl已经准备好,不需要做任何修改。
- 定义通知类和通知
通知就是将共性功能抽取出来后形成的方法,共性功能指的就是当前系统时间的打印。
需要将通知类配给容器并标识其为切面类。
@Component
@Aspect
public class MyAdvice {
public void method(){
System.out.println(System.currentTimeMillis());
}
}
类名和方法名没有要求,可以任意。
- 定义切入点
@Component
@Aspect
public class MyAdvice {
// 切入点
@Pointcut("execution(void fun.it.dao.BookDao.update())")
private void pt(){}
public void method(){
System.out.println(System.currentTimeMillis());
}
}
切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。
- 制作切面
@Component
@Aspect
public class MyAdvice {
// 切入点
@Pointcut("execution(void fun.it.dao.BookDao.update())")
private void pt(){}
// 切面
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
- 开启注解格式AOP功能
@Configuration
@ComponentScan("fun.it")
@EnableAspectJAutoProxy
public class SpringConfig {
}
- 运行程序
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.update();
}
}
结果:
9.3 AOP工作流程
工作流程:
- Spring容器启动
加载被增强的类和aop通知类,此时bean对象还没有被创建。
- 读取所有切面配置中的切入点
读取被配置并且被使用的切入点。
- 初始化bean
判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建原始对象
- 匹配失败说明不需要增强,直接调用原始对象的方法即可。
- 匹配成功,创建原始对象(目标对象)的代理对象
- 匹配成功说明需要对其进行增强
- 对哪个类做增强,这个类对应的对象就叫做目标对象
- 因为要对目标对象进行功能增强,而采用的技术是动态代理,所以会为其创建一个代理对象
- 最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强
- 获取bean执行方法
- 获取的bean是原始对象时,调用方法并执行,完成操作
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
核心概念:
- 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
- 代理对象(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
9.4 AOP配置管理
9.4.1 切入点表达式
execution(public User fun.it.service.UserService.findById(int))
- execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
- public:访问修饰符,还可以是public,private等,可以省略
- User:返回值,写返回值类型
- fun.it.service:包名,多级包使用点连接
- UserService:类/接口名称
- findById:方法名
- int:参数,直接写参数的类型,多个类型用逗号隔开
- 异常名:方法定义中抛出指定异常,可以省略
通配符:
-
*
:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现execution(public * fun.it.*.UserService.find*(*))
匹配fun.it包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
-
..
:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
-
+
:专用于匹配子类类型execution(* *..*Service+.*(..))
使用率较低,描述子类。*Service+,表示所有以Service结尾的接口的子类。
书写技巧:
- 所有代码按照标准规范开发,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了
- 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
- 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配
- 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名
- 方法名书写以动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAll书写成selectAll
- 参数规则较为复杂,根据业务方法灵活调整
- 通常不使用异常作为匹配规则
9.4.2 通知类型
- 前置通知
追加功能到方法执行前。
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void fun.it.dao.BookDao.update())")
private void pt(){}
@Before("pt()")
//此处也可以写成 @Before("MyAdvice.pt()"),不建议
public void before() {
System.out.println("before advice ...");
}
}
- 后置通知
追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行。
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void fun.it.dao.BookDao.update())")
private void pt(){}
@After("pt()")
public void after() {
System.out.println("after advice ...");
}
}
- 环绕通知
可以追加功能到方法执行的前后,它可以实现其他四种通知类型的功能。
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void fun.it.dao.BookDao.update())")
private void pt(){}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("around before advice ...");
//表示对原始操作的调用
Object ret = pjp.proceed();
System.out.println("around after advice ...");
return ret;
}
}
- 返回后通知
追加功能到方法执行后,只有方法正常执行结束后才进行。
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(int fun.it.dao.BookDao.select())")
private void pt2(){}
@AfterReturning("pt2()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}
}
- 抛出异常后通知
追加功能到方法抛出异常后,只有方法执行出异常才进行。
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(int fun.it.dao.BookDao.select())")
private void pt2(){}
@AfterReturning("pt2()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
}
9.4.3 AOP通知获取数据
- 获取执行签名信息
@Around("ProjectAdvice.servicePt()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
//获取执行的签名对象
Signature signature = pjp.getSignature();
//通过签名获取执行操作名称(接口名)
String className = signature.getDeclaringTypeName();
//通过签名获取执行操作名称(方法名)
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
}
- 获取参数
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
// 环绕通知通过ProceedingJoinPoint对象来获取参数
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = null;
try {
// 可以在执行切入点时放入修改过的参数
ret = pjp.proceed(args);
} catch (Throwable t) {
t.printStackTrace();
}
return ret;
}
@Before("pt()")
public void before(JoinPoint jp) {
// 通过JoinPoint对象来获取执行切入点的参数
// 除Around外,其余的都相同
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ..." );
}
- 获取返回值
对于返回值,只有返回后AfterReturing
和环绕Around
这两个通知类型可以获取。
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
// 环绕通知通过ProceedingJoinPoint对象来获取参数
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = null;
try {
// 获取返回值
ret = pjp.proceed(args);
} catch (Throwable t) {
t.printStackTrace();
}
return ret;
}
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(JoinPoint jp,String ret) {
System.out.println("afterReturning advice ..."+ret);
}
注意:
- returning中的变量名与方法中接受返回值的参数名保持一致
- 为了能匹配更多的参数类型,接返回值的参数类型建议写成Object类型
- 如果有Join Point参数,参数必须要放在第一位
- 获取异常
对于获取抛出的异常,只有抛出异常后AfterThrowing
和环绕Around
这两个通知类型可以获取。
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 666;
Object ret = null;
try {
ret = pjp.proceed(args);
} catch (Throwable t) {
t.printStackTrace();
}
return ret;
}
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..."+t);
}
十、事务管理
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败。
10.1 事务的使用
- 引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
- 在需要被事务管理的方法上添加注解
@Transactional
public interface AccountService {
/**
* 转账操作
* @param out 传出方
* @param in 转入方
* @param money 金额
*/
//配置当前接口方法具有事务
public void transfer(String out,String in ,Double money) ;
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional
public void transfer(String out,String in ,Double money) {
accountDao.outMoney(out,money);
int i = 1/0;
accountDao.inMoney(in,money);
}
}
注意:
@Transactional可以写在接口类上、接口方法上、实现类上和实现类方法上
- 写在接口类上,该接口的所有实现类的所有方法都会有事务
- 写在接口方法上,该接口的所有实现类的该方法都会有事务
- 写在实现类上,该类中的所有方法都会有事务
- 写在实现类方法上,该方法上有事务
- 建议写在实现类或实现类的方法上
- 在JdbcConfig类中配置事务管理器
public class JdbcConfig {
@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
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
- 开启事务注解
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}
- 运行测试
会发现在转换的业务出现错误后,事务就可以控制回顾,保证数据的正确性。
10.2 事务角色
- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
10.3 事务属性
- 事务配置
上面这些属性都可以在@Transactional
注解的参数上进行设置。
-
readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
-
timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超时时间。
-
rollbackFor:当出现指定异常进行事务回滚
- Spring的事务只会对
Error异常
和RuntimeException异常
及其子类进行事务回顾,其他的异常类型是不会回滚的 - 可以使用rollbackFor属性来设置出现其他异常时进行回滚
- Spring的事务只会对
-
noRollbackFor:当出现指定异常不进行事务回滚
-
rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串
-
noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串
-
isolation设置事务的隔离级别
- DEFAULT :默认隔离级别, 会采用数据库的隔离级别
- READ_UNCOMMITTED : 读未提交
- READ_COMMITTED : 读已提交
- REPEATABLE_READ : 重复读取
- SERIALIZABLE: 串行化
- 事务传播行为
//propagation设置事务属性:传播行为设置为当前操作需要新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
事务传播行为的可选值:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!