Spring:分层的javaSE/EE应用full-stack轻量级开源框架
一、底层是核心容器
1、Beans
2、Core
3、Context
4、SpringEI表达式
二、中间层技术
1、AOP(规范、思想)
2、Aspects(实现方案)
…
三、应用层技术
1、数据访问与数据集成
2、web集成
3、web实现
四、基于Test测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VSd4Oyts-1656900398010)(C:\Users\StarrySea\Desktop\MarkDown学习\图片\spring1.jpg)]
IoC:【控制反转】,Spring反向控制应用程序所需要使用的外部资源
Spring控制的资源全部放在Spring容器中,该容器称为IoC容器
以前主控权在类手中,我们需要取new获取,现在主控权在spring手中,由spring提供资源
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R3txAwsB-1656900398011)(C:\Users\StarrySea\Desktop\MarkDown学习\图片\spring2.jpg)]
IoC入门案例步骤:
1、pom文件中导入spring坐标(5.1.9.release)
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.19.RELEASE</version>
</dependency>
</dependencies>
2、编写业务层接口和实现类,作为资源
service层的接口
package com.lcl.service;
public interface UserService {
public void save();
}
service实现类
package com.lcl.service.impl;
import com.lcl.service.UserService;
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("user service running...");
}
}
3、建立spring配置文件
4、配置所需资源(service层的实现类)做为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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--创建spring控制的资源-->
<bean id="userService" class="com.lcl.service.impl.UserServiceImpl"/>
</beans>
5、表现层(App)通过spring获取资源(获取service层的实现类,创建对象,再调用方法)
import com.lcl.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserApp {
public static void main(String[] args) {
//2、加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//3、获取资源
UserService userService = (UserService)ctx.getBean("userService");
userService.save();
}
}
bean标签,归属于beans标签,作用:定义spring中的资源,受此标签定义的资源将收到spring控制
<bean id="userService" name="userService1,userService2" class="com.lcl.service.impl.UserServiceImpl"/>
属性:
id:bean的名称,将通过id来获取bean资源,一般为接口名(第一个字母小写)
class:bean的类型,也就是对应资源的实现类
name:它是bean的名称,将通过name来获取bean资源,用于多人配合是给bean起别名。也就说与id一样的功能,可以起很多名字
<bean id="userService3" scope="prototype" class="com.lcl.service.impl.UserServiceImpl"/>
scope:定义bean的作用范围。用于控制bean创建后的对象是否是单例
取值:
1、singleton:设定创建出的对象保存在spring容器中,是一个单例的对象,就是创建出来的对象是同一个对象(加载配置文件时就创建)
2、prototype:设定创建出的对象保存在spring容器中,是一个非单例的对象,就是创建出来的对象不是同一个对象(创建对象时才创建)
3、request、session、application、websocket:设定创建出的对象放置在web容器对应的位置
bean的生命周期:
属性:init-method、destroy-method
要在对应的类中定义方法init、destroy
<bean id="userService3" scope="singleton" init-method="init" destroy-method="destroy" class="com.lcl.service.impl.UserServiceImpl"/>
当scope为singleton,也就是单例时,int只执行一次,而非单例的,则是创建一个对象就执行一次init。单例时,关闭容器就执行destroy一次,非单例不执行,因为这时的对象销毁由垃圾回收机制gc()控制。
bean对象创建方式
属性:factory-bean、factory-method
作用:定义bean对象创建方式,使用实例工厂的形式创建bean,兼容早期遗留系统的升级工作
取值:工厂bean中用于获取对象的实例方法名
注意事项:1、使用实例工厂创建bean首先需要将实例工厂配置bean,交由spring进行管理。2、factory-bean是实例工厂的beanId
<!--静态工厂创建bean-->
<!-- <bean id="userService4" class="com.lcl.service.UserServiceFactory" factory-method="getService"/>-->
<!--实例工厂对应的bean-->
<bean id="factoryBean" class="com.lcl.service.UserServiceFactory2" />
<!--实例工厂创建bean,依赖工厂对象对应的bean-->
<bean id="userService5" factory-bean="factoryBean" factory-method="getService"/>
DI:依赖注入,应用程序运行依赖的资源由spring为其提供,资源进入应用程序的方式称为注入(与IoC是同时发生,只是站的角度不同,在spring上是IoC,在应用程序上是DI,一个提供资源,一个获取资源)
两种方式
1、set注入(主流方式)
使用property标签,归属于bean标签,作用是使用set方法的形式为bean提供资源
基本属性:
name:对应bean中的属性名,要求该属性必须提供可访问的set方法(严格规范此名称是set方法对应名称)(也就是在一个类中,把资源定义为变量,写个set方法,和平常那个get/set方法一样,只需要set方法)
value:设定非引用类型属性对应的值,不能与ref同时使用
ref:设定引用类型属性对应bean的id值,不能与value同时使用
<!--userService自己就是一个bean资源,被spring控制,其内自己用set注入获取userDao这个资源-->
<bean id="userService" class="com.lcl.service.impl.UserServiceImpl">
<!--3、将要注入的引用类型的变量,通过property注入,name对应set方法那个方法名中去除set部分,ref对应下面步骤2中注入bean 的id-->
<property name="userDao" ref="userDao"/>
<!--非引用类型,不用声明bean,直接写property-->
<property name="num" value="666"/>
</bean>
<!--2、将要注入的资源声明为bean-->
<bean id="userDao" class="com.lcl.dao.impl.UserDaoImpl"/>
2、构造器注入(了解)
使用constructor-arg标签,归属于bean标签,作用是使用构造方法的形式为bean提供资源,兼容早期遗留系统的升级工作(也就是构建一个有参构造)
基本属性:
name:对应bean中构造方法所携带的【参数名】
value:设定非引用类型构造方法参数对应的值,不能与ref同时使用
ref:设定引用类型属性对应bean的id值,不能与value同时使用
type:设定构造方法【参数的类型】,用于按类型匹配参数或进行类型校验
index:设定构造方法【参数的位置】,用于按位置匹配参数,参数index值从0开始计数(没吊用感觉,还不如用name)
注意:一个bean可以有多个constructor-arg标签
<bean id="userService" class="com.lcl.service.impl.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="num" value="666"/>
</bean>
<bean id="userDao" class="com.lcl.dao.impl.UserDaoImpl">
<!--如果不用name,值就不能对应相应变量,可以用index-->
<constructor-arg index="0" value="123456"/>
<constructor-arg index="1" value="lcl"/>
</bean>
【集合数据类型】的注入
五个标签:arry、list、set、map、props。都归属于property标签或constructor-arg标签
此处也是set注入,主要看怎么给集合数据注入,添加值
<!--集合数据类型的注入-->
<bean id="userService" class="com.lcl.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="userDao" class="com.lcl.dao.impl.UserDaoImpl">
<constructor-arg index="0" value="123456"/>
<constructor-arg index="1" value="lcl"/>
</bean>
<bean id="bookDao" class="com.lcl.dao.impl.BookDaoImpl">
<property name="arrayList">
<list>
<value>lcl</value>
<value>xwh</value>
</list>
</property>
<property name="properties">
<props>
<prop key="name">lcl</prop>
<prop key="value">666666</prop>
</props>
</property>
<property name="arr">
<array>
<value>3</value>
<value>6</value>
</array>
</property>
<property name="hashSet">
<set>
<value>lcl</value>
<value>xwh</value>
</set>
</property>
<property name="hashMap">
<map>
<entry key="name" value="lcl"/>
<entry key="value" value="666666"/>
</map>
</property>
</bean>
使用p命名空间简化配置(了解)
是bean中的属性,在使用之前需要在beans标签中添加对应的空间支持
xmlns:p="http://www.springframework.org/schema/p"
具体使用:
之前:
<bean id="userService" class="com.lcl.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
<property name="bookDao" ref="bookDao"/>
</bean>
使用p命名空间:
<bean
id="userService"
class="com.lcl.service.impl.UserServiceImpl"
p:userDao-ref="userDao"
p:bookDao-ref="bookDao"
/>
SpEL(了解)
spring提供了对EL表达式的支持,统一属性注入格式
是value属性的属性值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Gagg4B2-1656900398012)(C:\Users\StarrySea\Desktop\MarkDown学习\图片\spring3.jpg)]
properties文件
spring提供了读取外部properties文件机制,使用读取到的数据为bean的属性赋值。例如给set注入的非引用资源赋值
操作步骤:
1、准备外部properties文件
2、开启context命名空间支持
<!--1、加载context命名空间支持-->
<!--xmlns:context="http://www.springframework.org/schema/context"-->
<!--还需要在xsi:schemaLocation="",双引号内加
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
也就是把原有的复制一下,把bean改为context
-->
3、加载指定的properties文件
<!--2、加载配置文件,*.properties表示加载全部-->
<context:property-placeholder location="classpath:*.properties"/>
4、使用加载的数据
<bean id="userDao" class="com.lcl.dao.impl.UserDaoImpl">
<property name="username" value="${username}"/>
<property name="pwd" value="${pwd}"/>
</bean>
注意:
若是要要加载所有properties文件,可以使用*.properties。
读取数据使用${propertiesName},propertiesName指properties文件的属性名
团队开发,import导入配置文件(.xml文件,也就是其他配置bean的文件,主配置文件和子配置文件)
import是标签,归属于beans标签,作用是在当前配置文件中导入其他配置文件中的项
属性:
resource:加载的配置文件名
<!--import导入子配置文件。将上面的配置拆分成两个子配置文件,再导入进来-->
<import resource="applicationContext-book.xml"/>
<import resource="applicationContext-user.xml"/>
也可以使用下面这方式,主方法加载配置文件时。但很少用
//加载两个配置文件一起起作用
//ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-book.xml","applicationContext-user.xml");
bean定义冲突问题:同id的bean,后定义的覆盖先定义的,import导入顺序也是
BeanFactory是顶层接口,ApplicationContext是下级接口,前者创建的bean采用【延迟加载】,使用才创建。而后者创建bean默认采用【立即加载】形式
第三方资源配置
例如阿里云数据源方案Druid
<bean id="dataSource" 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>
综合案例:使用spring整合mybatis技术,完成账户模块(Account)的基础增删改查
最后有个错误,不知道啥情况:Exception in thread “main” org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.lcl.dao.AccountDao.save
APP类是测试类
import com.lcl.domain.Account;
import com.lcl.service.AccountService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class APP {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) ctx.getBean("accountService");
/* Account ac = accountService.findById(1);
System.out.println(ac);*/
Account account = new Account();
account.setName("Tom");
account.setMoney(111.111);
accountService.save(account);
}
}
domian包是实体类(略,根据数据表创建)
dao包下的接口是对应的数据库增删改查 的方法
package com.lcl.dao;
import com.lcl.domain.Account;
import java.util.List;
public interface AccountDao {
void save(Account account);
void delete(Integer id);
void update(Account account);
List<Account> findAll();
Account findById(Integer id);
}
service包下也有个接口和上面的一毛一样,还有个实现类,实现接口中的增删改查,获取数据
package com.lcl.service.impl;
import com.lcl.dao.AccountDao;
import com.lcl.domain.Account;
import com.lcl.service.AccountService;
import java.util.List;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void save(Account account) {
accountDao.save(account);
}
public void delete(Integer id) {
accountDao.delete(id);
}
public void update(Account account) {
accountDao.update(account);
}
public List<Account> findAll() {
return accountDao.findAll();
}
public Account findById(Integer id) {
return accountDao.findById(id);
}
}
resource包下有个com.lcl.dao包里面有个AccountDao.xml配置文件,设置增删改查的语句
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lcl.dao.AccountDao">
<select id="findById" resultType="account" parameterType="int">
select * from account where id = #{id}
</select>
<select id="findAll" resultType="account">
select * from account
</select>
<insert id="save" parameterType="account">
insert into account(name,money) values (#{name},#{money})
</insert>
<delete id="delete" parameterType="int">
delete from account where id = #{id}
</delete>
<update id="update" parameterType="account">
update account set name = #{name},money=#{money} where id = #{id}
</update>
</mapper>
还有一个jdbc.properties的文件,里面有数据库连接消息
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.23.129:3306/spring_db
username=root
password=root
最终要的还是applicationContext.xml,配置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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
">
<!--加载properties配置文件信息-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--加载druid信息-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
<!--配置service为spring的bean,并在service用set注入dao-->
<bean id="accountService" class="com.lcl.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--spring整合mybatis后控制的【创建连接】的对象-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.lcl.domain"/>
</bean>
<!--加载mybatis【映射配置】的扫描,将其作为spring的bean进行管理-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.lcl.dao"/>
</bean>
</beans>
pom.xml添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lcl</groupId>
<artifactId>Spring_day01_04_综合案例spring整合mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.19.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.19.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.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies>
</project>
注解开发
<!--启动注解驱动,扫描包和下级包,一直往下扫-->
<context:component-scan base-package="com.lcl"/>
在类上的注解
@Component("userService") //指定id,对应的类就是bean资源了,若是没写id,且有同类型的也没写id,那么别的类注入时,无法区分。可以使用@Primary来确定优先加载,这个玩意只能用一次,不然也无法区别
@Scope("prototype") //指定作用范围,单例或非单例,一般只写scope,默认是单例
在初始化和销毁的方法上的注解,设定声明周期
@PostConstruct
public void init(){
System.out.println("user service init");
}
@PreDestroy
public void destroy(){
System.out.println("user service destroy");
}
加载第三方资源,那个@Component不合适,把它干掉,应该在纯注解那个的SpringConfig那儿加一个@Import(JDBCConfig.class)
//@Component
public class JDBCConfig {
@Bean("dataSource")
public static DruidDataSource getDataSource(){
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;
}
}
bean非引用类型属性的注入,在成员变量上,或set方法上加注解都可以。重要的是,以后可以不用写set方法了,直接在成员变量上加注解就行了
//@Value("3")
private int num;
@Value("lcl.1.0.0")
private String version;
@Value("3")
public void setNum(int num) {
this.num = num;
}
public void setVersion(String version) {
this.version = version;
}
bean引用类型属性的注入
//bean引用类型属性的注入,也可以不写set方法,@Autowired系统自己去找 先类型,后bookDao名 匹配的bean资源,若是同名或没有对应名字则报错。
//可以使用@Qualifier指定bean
@Autowired
//@Qualifier("bookDao")
private BookDao bookDao;
加载properties文件的属性,类上加 @PropertySource(“classpath:文件名”),若是有多个properties文件,不能用*,用大括号,引用多个。ignoreResourceNotFound = true表示文件不存在则忽略,不会报错。
这个可以放在任何地方,其他地方也可以用,以后就放在SpringConfig类上
@PropertySource(value={"classpath:jdbc.properties","classpath:abc.properties"},ignoreResourceNotFound = true)
成员变量上加@Value(“${属性名}”)
@Component("userDao")
@PropertySource("classpath:jdbc.properties")
public class UserDaoImpl implements UserDao {
//加载properties文件属性
@Value("${jdbc.userName}")
private String userName;
@Value("${jdbc.password}")
private String password;
public void save(){
System.out.println("user dao is running"+userName+password);
}
}
纯注解开发
package com.lcl.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
//纯注解开发,干掉.xml文件。下面就是干掉了applicationContext.xml
@Configuration //干掉beans那一坨
@ComponentScan("com.lcl") //干掉 <context:component-scan base-package="com.lcl"/>
@Import(JDBCConfig.class)//第三方资源bean,用大括号加入多个
public class SpringConfig {
}
测试类中
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
不用那个加载配置文件对象了,加载上面那个类
依赖加载
1、@DependsOn(“beanId”),在类或是方法上,控制【加载顺序】,加载有这个注解的类前,需要先加载它依赖的beanId资源。
2、@Order(value),这是配置类注解,在多个【配置类】前加这个,value从1开始,越小越先加载
3、@Lazy,在类或方法上,叫延迟加载
整合第三方技术-注解整合mybatis
主要把.xml文件给干掉
首先干掉 AccountDao.xml
package com.lcl.dao;
import com.lcl.domain.Account;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface AccountDao {
@Insert("insert into account(name,money) values (#{name},#{money})")
void save(Account account);
@Delete("delete from account where id = #{id}")
void delete(Integer id);
@Update("update account set name = #{name},money = #{money} where id = #{id}")
void update(Account account);
@Select("select * from account")
List<Account> findAll();
@Select("select * from account where id = #{id}")
Account findById(Integer id);
}
再干掉applicationContext.xml
package com.lcl.conifg;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.lcl")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MyBatisConfig.class})
public class SpringConfig {
}
package com.lcl.conifg;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
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("dataSource")
public DruidDataSource getDataSource(){//不加static
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
package com.lcl.conifg;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.lcl.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.lcl.dao");
return msc;
}
}
注解整合Junit测试
添加依赖坐标
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
</dependencies>
在test包下写测试类测试方法
//设定spring专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
//设定加载的spring上下文对应的配置
@ContextConfiguration(classes = SpringConfig.class)
package com.lcl.service;
import com.lcl.conifg.SpringConfig;
import com.lcl.domain.Account;
import org.junit.Assert;
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;
//设定spring专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
//设定加载的spring上下文对应的配置
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
Account ac = accountService.findById(2);
//System.out.println(ac);
Assert.assertEquals("jock",ac.getName());
}
}
IoC底层核心原理
1、核心接口
2、组件扫描:开发过程中,需要根据需求加载必要的bean,排除指定的bean
设定组件扫描加载过滤器:
1)、按注解过滤,SpringConfig类中加:
@ComponentScan(
value = "com.lcl",
//排除指定的bean,使之不被加载
excludeFilters = @ComponentScan.Filter( //设置过滤器
type = FilterType.ANNOTATION, //五种策略,此处是按注解过滤
classes = Service.class //过滤所有Service(可改为其他)修饰的bean
)
)
2)、自定义类型过滤
先写个类实现Filter接口,自定义类型
public class MyTypeFilter implements TypeFilter {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//加载的类满足要求,匹配成功
ClassMetadata classMetadata = metadataReader.getClassMetadata();
String className = classMetadata.getClassName();
System.out.println(className);
if(className.equals("com.lcl.service.impl.UserServiceImpl")){
return true;//拦截
}
return false;//全部放行
}
}
再用过滤器过滤,SpringConfig类中加:
@ComponentScan(
value = "com.lcl",
//自定义类型
excludeFilters = @ComponentScan.Filter(
type = FilterType.CUSTOM,
classes = MyTypeFilter.class
)
)
3、自定义导入器:
bean只有通过配置才可以进入spring容器,被spring加载并控制,bean配置有两种方式:一是XML的bean标签,二是@Component以及衍生的注解配置。
企业开发中,通常需要配置【大量】的bean,需要一种快速高效配置大量bean的方法,即自定义导入器,定义一个类,实现ImportSelector接口
//自定义导入器
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//单个bean,从配置文件获取的字符串不需要分割
/* ResourceBundle bundle = ResourceBundle.getBundle("import");//根据文件名获取
String className = bundle.getString("className");
return new String[]{className};*/
//多个bean,从配置文件获取的字符串需要分割
ResourceBundle bundle = ResourceBundle.getBundle("import");
String className = bundle.getString("className");
return className.split(",");
//return new String[]{"com.lcl.dao.impl.BookDaoImpl"};//上面用配置文件,此处是直接使用
}
}
配置文件
#className=com.lcl.dao.impl.BookDaoImpl 单个
#className=com.lcl.dao.impl.BookDaoImpl,com.lcl.dao.impl.AccountDaoImpl 多个
#通配,包下所有的
path=com.lcl.dao.impl.*
SpringConfig类中加:
@Import(MyImportSelector.class)//自定义导入器的使用
还有通过路径通配,包下所有都可以(直接拿来使用即可,不必记住)
SpringConfig类中加:
@Import(CustomerImportSelector.class)
定义一个类,实现ImportSelector接口:
package config.selector;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AspectJTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class CustomerImportSelector implements ImportSelector {
private String expression;
public CustomerImportSelector() {
try {
//初始化时指定加载的properties文件名
Properties loadAllProperties = PropertiesLoaderUtils.loadAllProperties("import.properties");
//设定加载的属性名
expression = loadAllProperties.getProperty("path");
} catch (IOException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//1.定义扫描包的名称
String[] basePackages = null;
//2.判断有@Import注解的类上是否有@ComponentScan注解
if (importingClassMetadata.hasAnnotation(ComponentScan.class.getName())) {
//3.取出@ComponentScan注解的属性
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
//4.取出属性名称为basePackages属性的值
basePackages = (String[]) annotationAttributes.get("basePackages");
}
//5.判断是否有此属性(如果没有ComponentScan注解则属性值为null,如果有ComponentScan注解,则basePackages默认为空数组)
if (basePackages == null || basePackages.length == 0) {
String basePackage = null;
try {
//6.取出包含@Import注解类的包名
basePackage = Class.forName(importingClassMetadata.getClassName()).getPackage().getName();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//7.存入数组中
basePackages = new String[]{basePackage};
}
//8.创建类路径扫描器
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
//9.创建类型过滤器(此处使用切入点表达式类型过滤器)
TypeFilter typeFilter = new AspectJTypeFilter(expression, this.getClass().getClassLoader());
//10.给扫描器加入类型过滤器
scanner.addIncludeFilter(typeFilter);
//11.创建存放全限定类名的集合
Set<String> classes = new HashSet<>();
//12.填充集合数据
for (String basePackage : basePackages) {
scanner.findCandidateComponents(basePackage).forEach(beanDefinition -> classes.add(beanDefinition.getBeanClassName()));
}
//13.按照规则返回
return classes.toArray(new String[classes.size()]);
}
}
4、自定义注册器
//自定义注册器
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry,false);
//scanner.addExcludeFilter();
scanner.addIncludeFilter(new TypeFilter() {//定义过滤器规则
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;//全部要,都加载
}
});
scanner.scan("com.lcl");
}
}
SpringConfig类中加:
@Import(MyImportBeanDefinitionRegistrar.class)//自定义注册器,把@ComponentScan("com.lcl")干掉
5、bean初始化过程解析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T9HIThBy-1656900398012)(C:\Users\StarrySea\Desktop\MarkDown学习\图片\spring4.jpg)]
package config.postprocessor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
public class MyBeanFactory implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
System.out.println("bean工厂制作好了,好友什么事情需要处理");
}
}
package config.postprocessor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBean implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean之前。。。。。");
System.out.println(beanName);
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean之后。。。。。");
return null;
}
}
package com.lcl.dao.impl;
import com.lcl.dao.AccountDao;
import org.springframework.beans.factory.InitializingBean;
public class AccountDaoImpl implements AccountDao, InitializingBean {
@Override
public void save() {
System.out.println("Account Dao running......");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("AccountDaoImpl......初始化");
}
}
SpringConfig类中用@Import导入相应的类
AOP
AOP:面向切面编程,一种编程【范式】,隶属于软工范畴,指导开发者如何组织程序结构。基于OOP基础上进行横向开发,一切围绕共性功能进行,完成某个任务构建可能遇到的所有【共性功能】
从各个行业的标准化、规范化入手,将所有共性功能逐一开发完毕,最终以【功能组合】来完成开发,实现“插拔式组件体系结构”,走向半自动化/全自动化
相关概念:1、连接点:所有方法
2、切入点:具有共性功能的方法
3、通知:将共性功能的方法挖出来变成的功能模块
4、切面:切入点和通知的关系,也就是从哪挖的通知,以后
放回原来的切入点,位置和共性功能的关系
5、通知类型:通知概念的一部分,表明通知放在哪,前啊还
是后啊,具体位置
6、目标对象:也就是挖去共性功能的简化版的方法
7、织入:将共性功能放回目标对象的过程即为织入
8、代理:上面的共性功能织入目标对象最后生成的类
9、引入:代理类可以自己无中生有一些成员变量/方法
AOP开发过程:1、正常的制作程序 2、将非共性功能开发到对应的目标对象类中,并制作成切入点方法 3、将共性功能独立开发出来,制作【通知】4、在配置文件中,声明【切入点】5、在配置文件中,声明切入点与通知的关系(含通知类型),即【切面】
后面的织入、代理等等由spring自动完成
AOP入门方法方式:XML、XML+注解、注解
入门案例制作分析:1、导入相关坐标 2、确认要抽取的功能,将其制作成方法保存到专用的类中,删除原始业务中对应的功能 3、将所有进行AOP操作的资源加载到IoC容器中 4、使用配置文件的方式描述被抽取功能的位置,并描述被抽取功能与对应位置的关系 5、运行程序
1、
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.19.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
2、
package com.lcl.service.impl;
import com.lcl.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public void save() {
//System.out.println("这是共性功能!"); 挖走
System.out.println("user service running......");
}
}
package com.lcl.aop;
public class AOPAdvice {
public void function(){
System.out.println("这是共性功能!");
}
}
3、4、
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--上面的beans中要加入三行有关aop的 内容-->
<bean id="userService" class="com.lcl.service.impl.UserServiceImpl"/>
<!--通知,作为bean资源放入IoC容器中-->
<bean id="myAdvice" class="com.lcl.aop.AOPAdvice"/>
<!--aop配置,指定切入点、通知,以及切面-->
<aop:config>
<!--切入点,id随便取-->
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<!--切面,指定通知bean-->
<aop:aspect ref="myAdvice">
<!--将共性功能对应的方法function织入切入点中,通知类中某一个共性功能方法织入对应的切入点-->
<aop:before method="function" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
一、AOP配置(XML)
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
<aop:config></aop:config>
设置AOP,可以有多个,都生效
<aop:aspect ref="myAdvice"></aop:aspect>
切面,设置具体的AOP通知对应的切入点,ref对应上面的bean资源,也就是通知类,通知类里面是共性功能对应方法。可以有多个,同时生效
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
切入点。id随便,一般就pt,expression是对切入点的描述(切入点表达式),也就是位置
<aop:before method="before" pointcut-ref="pt"/>
method即通知类里面是共性功能对应方法,pointcut-ref切入点
切入点表达式的组成:
关键字(访问修饰符 返回值 包名.类名.方法名(参数) 异常名)
关键字:描述表达式的匹配模式(参照关键字列表)
访问修饰符:方法的访问控制权限修饰符
类名:方法所在的类(此处可以配置接口名称)
异常:方法定义中指定抛出的异常
范例:
execution(public User com.lcl.service.UserService.findById(int))
通配符:1、*:单个独立的任意符号,可以单独出现,也可以作为前缀或后缀的匹配符出现 2、… :多个连续的任意符号,可以单独出现,常用于简化包名与参数的书写 3、+:专用于匹配子类类型
范例作为一个整体,可以用&&、||、!来连接多个表达式
<!--切入点的三种配置-->
<aop:config>
<!--<aop:pointcut id="pt" expression="execution(* com.lcl.service.UserService.save())"/>-->
<!--公共切入点-->
<aop:pointcut id="pt" expression="execution(* *..*())"/>
<aop:aspect ref="myAdvice">
<!--局部切入点-->
<aop:pointcut id="pt2" expression="execution(* *..*())"/>
<!--直接加-->
<aop:before method="before" pointcut="execution(* *..*())"/>
<!--<aop:before method="before" pointcut-ref="pt2"/>-->
</aop:aspect>
</aop:config>
通知类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jlGCgv7s-1656900398013)(C:\Users\StarrySea\AppData\Roaming\Typora\typora-user-images\image-20220113213028298.png)]
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*())"/>
<aop:aspect ref="myAdvice">
<!--<aop:before method="before" pointcut-ref="pt"/>-->
<!--<aop:after method="after" pointcut-ref="pt"/>-->
<!--<aop:after-returning method="afterReturning" pointcut-ref="pt"/>-->
<!--<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>-->
<aop:around method="around" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
package com.lcl.aop;
import org.aspectj.lang.ProceedingJoinPoint;
public class AOPAdvice {
public void before(){
System.out.println("before");
}
public void after(){
System.out.println("after");
}
public void afterReturning(){
System.out.println("afterReturning");
}
public void afterThrowing(){
System.out.println("afterThrowing");
}
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("before");
//对原始方法的调用,如果没有这个,则原方法不执行!!!
//因为无法预知原方法是否会出异常,需要抛出异常
pjp.proceed();
System.out.println("after");
}
}
通知顺序:当同一个切入点配置了多个通知时,通知会存在运行的先后顺序,该顺序以通知配置的顺序为准
通知获取原始方法的数据:参数、返回值、异常
1、通知获取参数数据(给原始方法参数,调用时传实参,该实参就会被下面的通知获取)
方法一(五种通知通用):
public void before(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("before..."+args[0]);
}
方法二(了解即可):
public void before1(int x){
System.out.println("before1..."+x);
}
<aop:before method="before1" pointcut="execution(* *..*(int)) && args(x)"/>
x:是强绑定的,对应通知的参数,不可变
如果是多个参数,将上面的改一下即可<aop:before method="before1" pointcut="execution(* *..*(int,int)) && args(x,y)"/>
其中有个arg-names的属性,可以改变,多个参数的顺序,按照arg-names的值排序,例如arg-names="y,x",那么就是按照y,x,而不是x,y了
2、通知获取返回值数据(只有after-returning、around可以使用,能获取返回值。原方法得有返回值才行)
after-returning:
public void afterReturning(Object ret){
System.out.println("afterReturning..."+ret);
}
<aop:after-returning method="afterReturning" pointcut-ref="pt" returning="ret"/>
加一个returning属性,值对应通知方法中的参数Object ret
around:
public Object around1(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("before...");
//对原始方法的调用
Object ret = pjp.proceed();
System.out.println("after..."+ret);
return ret;
}
配置文件的aop不变
3、通知中获取异常对象(只有after-throwing、around可以使用)
after-throwing:
public void afterThrowing1(Throwable t){
System.out.println("afterThrowing..."+t.getMessage());
}
<aop:after-throwing method="afterThrowing1" pointcut-ref="pt" throwing="t"/>
多个throwing属性,值对应通知方法的参数Throwable t
around:
public Object around2(ProceedingJoinPoint pjp) {
System.out.println("around before...");
//对原始方法的调用
Object ret = null;
try {
ret = pjp.proceed();
} catch (Throwable throwable) {
System.out.println("around...exception...."+throwable.getMessage());
}
System.out.println("around after..."+ret);
return ret;
}
原本的异常抛出删除,用try catch。配置文件不变
二、AOP配置(注解+XML)
一、在配置文件中开启AOP注解
<aop:aspectj-autoproxy/>
二、在切面类,也就是通知的那个类中使用注解
package com.lcl.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect //1、切面类注解,对应<aop:aspect ref="myAdvice">
public class AOPAdvice {
@Pointcut("execution(* *..*(..))")//2、切点注解,对应<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
public void pt(){}
@Before("pt()")//3、对应<aop:before method="before" pointcut-ref="pt"/>
public void before(){
System.out.println("before...");
}
@After("pt()")
public void after(){
System.out.println("after...");
}
@AfterReturning("pt()")
public void afterThrowing(){
System.out.println("afterReturning...");
}
@AfterThrowing("pt()")
public void afterThrowing1(){
System.out.println("afterThrowing...");
}
@Around("pt()")
public Object around1(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before...");
Object ret = pjp.proceed();
System.out.println("around after..."+ret);
return ret;
}
}
注意:
1、切入点最终体现为一个方法pt(),无参无返回值,无方法体,不能为抽象方法
2、引入切入点时必须使用方法调用名称,方法后的()不能省略
3、切面类内定义的切入点pt()只能在当前类使用,想引用,使用 类名.方法名()
4、可以在注解中加参数,实现XML配置的属性,例如returning属性
@AfterReturning(value="pt()",returning="ret")
AOP使用XML配置下,通知的执行顺序按照配置顺序决定,使用注解配置,则参照通知所配置的方法名字字符串对应的编码顺序,可以理解为字母顺序。
1、同一个类中,相同的通知类型,以方法名排序
2、不同通知类(切面类),以类名排序
3、使用@Order注解通过变更bean的加载顺序改变通知的加载顺序
通知名由三部分组成,前缀、顺序编码、功能描述。执行顺序由顺序编码控制
三、AOP配置(纯注解)
pom文件加两个依赖,测试时使用
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
在spring注解配置类SpringConfig上加一个@EnableAspectJAutoProxy
@Configuration//代替beans
@ComponentScan("com.lcl")//代替<context:component-scan base-package="com.lcl"/>
@EnableAspectJAutoProxy//代替<aop:aspectj-autoproxy/>
public class SpringConfig {
}
AOP底层原理
1、静态代理
1)装饰者模式:在不惊动原始设计的基础上,为其添加功能
两个类实现同一个接口,一个类是原始功能,另一个类,则有原始功能类对象作为成员变量,还需要构造有参构造,在实现方法中,调用原始功能并添加新功能
public interface UserService {
public void save();
}
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("水泥墙");
}
}
public class UserServiceImplDecorator implements UserService {
private UserService userService;
public UserServiceImplDecorator(UserService userService) {
this.userService = userService;
}
public void save() {
userService.save();//原始功能
//增强功能
System.out.println("刮大白");
}
}
2、动态代理——JDK Proxy
针对对象做代理,要求原始对象具有接口实现,并对接口方法进行增强
public class UserServiceJDKProxy {
public static UserService createUserServiceJDKProxy(final UserService userService){
ClassLoader classLoader = userService.getClass().getClassLoader();//类加载器
Class[] interfaces = userService.getClass().getInterfaces();//所有接口
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret = method.invoke(userService,args);
System.out.println("刮大白");
System.out.println("贴壁纸");
return ret;
}
};
UserService service = (UserService)Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
return service;
}
}
3、动态代理——CGLIB:Code生成类库
CGLIB动态代理不限定是否具有接口,可以对任意操作进行增强
CGLIB动态代理不需要原始被代理对象,动态创建新的代理对象
package base.cglib;
import com.lcl.service.UserService;
import org.springframework.aop.TargetSource;
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 UserServiceCglibProxy {
public static UserService createUserServiceCglibProxy(Class clazz){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);//参数clazz变成了enhancer的父类
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//o:是最后被代理出来的对象
Object ret = methodProxy.invokeSuper(o,objects);//对原始方法的调用
//此方法能为所有方法增强,若是要指定save方法,用if判断
if(method.getName().equals("save")){
System.out.println("刮大白");
System.out.println("贴壁纸");
}
return ret;
}
});
UserService userService = (UserService) enhancer.create();
return userService;
}
}
内存中的字节码继承某一个类,由字节码生成对象,拦截原始功能前后都能增强
AOP配置中,默认使用JDKProxy,属性值改为true,则为Cglib.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r0us5EvB-1656900398013)(C:\Users\StarrySea\AppData\Roaming\Typora\typora-user-images\image-20220115145913267.png)]
Srping事务核心对象:
J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差异,当业务中包含多个数据层的调用时,需要【在业务层开启事务】,对数据层多个操作进行组合归并属于一个事务进行处理
Spring为业务层提供了整套的事务解决方案(下面三个都是接口):
1、PlatformTransactionManager
2、TransactionDefinition
3、TransactionStatus
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zSDbMQQr-1656900398013)(C:\Users\StarrySea\AppData\Roaming\Typora\typora-user-images\image-20220115200946511.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0hjOtwKL-1656900398014)(C:\Users\StarrySea\AppData\Roaming\Typora\typora-user-images\image-20220115201021349.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbmy4IGR-1656900398014)(C:\Users\StarrySea\AppData\Roaming\Typora\typora-user-images\image-20220115201045881.png)]
事务控制方式:编程式、声明式(XML)、声明式(注解)
1、编程式
public void transfer(String outName, String inName, Double money) {
//开启事务,PlatformTransactionManager接口定义了事务的基本操作,获取、提交、回滚事务
//上面需要注入一个private DataSource dataSource资源,指定数据源(数据库),然后传参给下面
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
//事务定义对象,TransactionDefinition接口定义事务的基本信息,传给事务状态,此处是默认对象DefaultTransactionDefinition
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
//事务状态,TransactionStatus接口定义事务在某个时间点上的状态信息和状态操作
TransactionStatus ts = ptm.getTransaction(td);
accountDao.inMoney(outName,money);
int i = 1/0;
accountDao.outMoney(inName,money);
//提交事务,参数是事务状态
ptm.commit(ts);
}
}
上面的可以用AOP改造(这里用注解有问题,未解决,还是用xml)
@Component
@Aspect
public class TxAdvice {
@Autowired
private DataSource dataSource;
@Pointcut("execution(* *..*(..))")
public void pt(){}
@Around("pt()")
public Object tx(ProceedingJoinPoint pjp) throws Throwable {
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
DefaultTransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus ts = ptm.getTransaction(td);
//调用原方法
Object ret = pjp.proceed(pjp.getArgs());
ptm.commit(ts);
return ret;
}
}
2、声明式(XML):干掉AOP那个类,或者说干掉编程式事务相关操作
先需要在beans加三行开启tx
再加
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BZ5jVrDr-1656900398014)(C:\Users\StarrySea\AppData\Roaming\Typora\typora-user-images\image-20220117104622223.png)]
事务传播行为:描述的是事务协调员(编程式代码中,两个accountDao方法,在里面)对事务管理员(编程式代码中,对事务相关的操作,在外面)所携带事务的处理态度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SwPn3jzX-1656900398015)(C:\Users\StarrySea\AppData\Roaming\Typora\typora-user-images\image-20220117105602497.png)]
3、声明式(注解):干掉声明式(XML)图中内容(dataSource不动)
先加<tx:annotation-driven transaction-manager=“txManage”/>
再在
@Component("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String outName, String inName, Double money) {
//AOP改造
accountDao.inMoney(outName,money);
//int i = 1/0;
accountDao.outMoney(inName,money);
}
}
tansfer方法上加@Transactional
其实最好配在接口中(接口上或方法上),不要配在实现类中
纯注解看看代码,在业务层接口上加@Transactional,在SpringConfig类上加@EnableTransactionManagement,最后在JDBCConfig类中加
@Bean
public PlatformTransactionManager getTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
上面这个方法可以写个类,放在类里面
Spring模板对象:哎嘿,没记笔记,气不气。。。。。。我懒!