Spring框架是一个功能强大且广泛使用的Java平台,它通过提供全面的基础设施支持,使得开发人员能够轻松构建高效、可移植、易于测试的代码。Spring的核心特性包括依赖注入(DI)、面向切面编程(AOP)和事件驱动模型,这些特性共同构成了一个灵活的编程范式,简化了数据访问层、业务逻辑层和表示层的开发。Spring还提供了对多种数据存储方案的支持,包括关系型数据库和NoSQL数据库,以及对消息传递系统的集成。此外,Spring Boot进一步简化了基于Spring的应用开发,通过自动配置和无需XML配置的方式,使得开发者可以快速启动和运行Spring应用程序。Spring生态系统的扩展性使其成为微服务架构、云计算和企业级应用开发的理想选择。
1、导入spring坐标
2.定义接口和实现类
3.编写xml文件
4.创建工厂测试类
beanfactory和Applicationcontext关系
beanfactory的继承体系
ApplicationContext的继承体系
Bean的配置详解
bean的范围配置
bean的延迟加载
bean的初始化方法一
bean的初始化方法一
bean的实例化方式
有两种:工厂方式和构造方式
构造方式实例化
工厂方式实例化
静态工厂实例化
找工厂方法的返回值作为对象作为bean的id值进行返回
实例工厂方法(如整合第三方bean的时候)
需要先配置工厂对象才能创建bean和静态实例化方法区分开
实例化factory的规范实例化Bean
bean的依赖注入配置
人为注入
自动注入
spring获取bean的三种方式
sping配置非定义的bean
需要考虑两个问题
1.bean的实例化方式是什么?2.bean是否需要注入必要属性?
bean实例化的基本流程
Spring Bean的基本流程
spring的后处理器
Spring的后处理器是Spring对外开发的重要扩展点,允许我们介入到Bean的整个实例化流程中来,以达到动态注册BeanDefinition,动态修改BeanDefinition,以及动态修改Bean的作用。Spring主要有两种后处理器:
1.BeanFactoryPostProcessor: Bean工厂后处理器,在BeanDefinitionMap填充完毕,Bean实例化之前执行;
2.BeanPostProcessor: Bean后处理器,一般在Bean实例化之后,填充到单例池sinqletonObiects之前执行。
Bean工厂后处理器-BeanFactoryPostProcessor
其中ConfigurableListableBeanFactory继承了ListableBeanFactory,ListableBeanFactory继承了BeanFactory,所以使用ConfigurableListableBeanFactory就可以操作BeanDefinition。
修改某个BeanDefinition:
Spring的后处理器
BeanPostProcessor的接口定义如下:
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println(beanName + ":postProcessBeforeInitialization");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println(beanName + ":postProcessAfterInitialization");return bean;}
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof UserDaoImpl) {UserDaoImpl userDao = (UserDaoImpl) bean;userDao.setUsername("zkt");}System.out.println(beanName + ":postProcessBeforeInitialization");return bean;
}
由此可见,postProcessBeforeInitialization和postProcessAfterInitialization中间还会执行afterPropertiesSet和init方法。
有了BeanPostProcessor后Bean实例化流程:
Bean的生命周期
Spring Bean的初始化过程涉及如下几个过程:
Bean实例的属性填充
Aware接口属性注入
BeanPostProcessor的before()方法回调
InitializingBean接口的初始化方法回调
自定义初始化方法init回调
BeanPostProcessor的after()方法回调
Bean实例的属性填充
Spring在进行属性注入时,会分为如下几种情况:
注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去;
注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;
注入双向对象引用属性时,就比较复杂了,涉及了**循环引用(循环依赖)**问题,下面会详细阐述解决方案。
当userService注入userDao时发现没有userDao,会到三级缓存中去找被封装为ObjectFactory的userDao,找到后会把userDao注入给userService,并把userDao在三级缓存中去掉,在二级缓存中添加userDao。
UserService和UserDao循环依赖的过程结合上述三级缓存描述一下:
UserService 实例化对象,但尚未初始化,将UserService存储到三级缓存;
UserService 属性注入,需要UserDao,从缓存中获取,没有UserDao;
UserDao 实例化对象,但尚未初始化,将UserDao存储到到三级缓存;
UserDao 属性注入,需要UserService,从三级缓存获取UserService,UserService从三级缓存移入二级缓存;
UserDao 执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
UserService 注入UserDao:
UserService 执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存。
常用的Aware接口
基于xml整合第三方框架
原始操作
package JDBC_Template;public class Userbean
{private int id;private String name;private String password;public Userbean() {}public Userbean(int id, String name, String password) {this.id = id;this.name = name;this.password = password;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "Userbean{" +"id=" + id +", name='" + name + '\'' +", password='" + password + '\'' +'}';}
}
package JDBC_Template;import java.util.List;public interface UserMapper
{List<Userbean> findUser();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="JDBC_Template.UserMapper"><select id="findUser" resultType="JDBC_Template.Userbean">select * from dsuser</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1/mybatis"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><mapper resource="/JDBC_Template/UserMapper.xml"/></mappers>
</configuration>
package JDBC_Template;import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.Test;import java.io.IOException;
import java.io.InputStream;
import java.util.List;public class MybatisTest
{@Testpublic void Test1() throws IOException {String resource = "Mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<Userbean> user = mapper.findUser();System.out.println(user);}
}
Spring整合mybatis的目的:将下列的冗余的代码放入bean中方便管理,并可重复使用
导入Mybatis整合Spring的相关坐标
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.2</version>
</dependency>
package JDBC_Template;import java.util.List;public interface UserMapper
{List<Userbean> findUser();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="JDBC_Template.UserMapper"><select id="findUser" resultType="JDBC_Template.Userbean">select * from dsuser</select>
</mapper>
<?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 https://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder location="jdbc.properties"/>
<!-- 配置数据源--><bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource"><property name="user" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/><property name="driverClass" value="${jdbc.driver}"/><property name="jdbcUrl" value="${jdbc.url}"/></bean>
<!-- 作用将SqlSessionfactory存储到Spring容器中--><bean id="bean" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 作用:扫描指定的包,产生Mapper对象存储到spring容器中--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="JdbcTemplate.UserMapper"></property></bean>
</beans>
注:SqlSessionFactoryBean的作用是向Spring提供SqlSessionFactory
注:MapperScannerConfigurer的作用是将Mapper的对象存储到Spring容器中
package JDBC_Template;public class UserDao
{private UserMapper userMapper;public void setUserMapper(UserMapper userMapper) {this.userMapper = userMapper;}public void show(){System.out.println(userMapper.findUser());}
}
@Testpublic void Test2(){ApplicationContext ioc=new ClassPathXmlApplicationContext("bean03.xml");
// List<Userbean> user = userMapper.findUser();UserDao userDao = ioc.getBean(UserDao.class);userDao.show();System.out.println("通过spring整合之后获取");}
需要引入第三方框架命名空间,需要使用Spring的配置文件配置第三方框架的本身内容,例如:Dubbo
为了将数据库的信息与bean文件中的信息加以区分,也为了更好操作、查找对应的数据库的驱动,地址,密码,账号,所以将这些数据放入properties文件中保存,加载外部propreties文件的标签为
location表示properties文件的位置
<context:property-placeholder location="jdbc.properties"/>
基于bean的注解开发
使用注解代替xml文件
也就是说告诉spring扫描哪些包,spring会扫描基本包及其下面子包
<context:component-scan base-package="com.wang"></context:component-scan>
@Autowired根据类型进行注入,如果同一类型有多个名字不同的bean那就按照名字进行二次匹配
【重温SSM框架系列】3 - Spring注解开发(注解代替xml文件配置)
<bean id="userService" class="com.wang.service.impl.UserServiceImpl"><property name="userDao" ref="userDao"></property></bean>
@Autowired结合@Qualifier根据名字进行注入
非自定义bean的配置
@Configuration //标志该类为Spring的核心配置类
@ComponentScan("com.wang")
@PropertySource("jdbc.properties")
public class SpringConfig {@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 DataSource getDataSource(){DruidDataSource dataSource = new DruidDataSource();
// dataSource.setDriverClassName("com.mysql.jdbc.Driver");
// dataSource.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai");
// dataSource.setUsername("root");
// dataSource.setPassword("123456");dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}
}
注解方式整合第三方框架
使用@Bean将DataSource和SqlSessionFactoryBean存储到Spring容器中,而MapperScannerConfigurer使用注解@MapperScan进行指明需要扫描的Mapper在哪个包下,使用注解整合MyBatis配置方式如下:
@Configuration
@ComponentScan("com.itheima")
@MapperScan("com.itheima.mapper")
public class ApplicationContextConfig {@Beanpublic DataSource dataSource(@Value("${jdbc.driver}") String driver,@Value("${jdbc.url}") String url,@Value("${jdbc.username}") String username,@Value("${jdbc.password}") String password){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Beanpublic SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSource);return sqlSessionFactoryBean;}
}
注解方式,Spring整合MyBatis的原理,关键在于@MapperScan,@MapperScan不是Spring提供的注解,而是MyBatis为了整合Spring,在整合包org.mybatis.spring.annotation中提供的注解,源码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {String[] value() default {};String[] basePackages() default {};Class<?>[] basePackageClasses() default {};Class<? extends Annotation> annotationClass() default Annotation.class;// ... ...
}
@Import整合第三方框架原理
@Import导入实现了ImportSelector接口的类
@Configuration
@ComponentScan("com.itheima")
@Import({MyImportSelector.class})
public class ApplicationContextConfig {
}
public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {// 返回要进行注册的Bean的全限定名数组return new String[]{User2.class.getName()};}
}
ImportSelector接口selectImports方法的参数AnnotationMetadata代表注解的媒体数据,可以获得 当前注解(@Import({MyImportSelector.class})) 修饰的类(ApplicationContextConfig ) 的 其他注解的元信息,例如:@Configuration、@ComponentScan(“com.itheima”)注解的元信息
public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {// 获得指定类型注解的全部信息Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(ComponentScan.class.getName());// 获得全部信息中basePackages信息String[] basePackages = (String[]) annotationAttributes.get("basePackages");// 打印结果是com.itheimaSystem.out.println(basePackages[0]);return new String[]{User2.class.getName()};}
}
@Import导入实现ImportBeanDefinitionRegistrar接口的类,实现了该接口的类的registerBeanDefinitions方法会被自动调用,在该方法内可以注册BeanDefinition
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 使用给定的BeanDefinitionRegistry参数,手动注册BeanDefinitionBeanDefinition beanDefinition = new RootBeanDefinition();beanDefinition.setBeanClassName("com.itheima.pojo.User2");registry.registerBeanDefinition("user2", beanDefinition);}
}
Aop
AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程
引入坐标
编写目标对象userService
public interface UserService {void show1();void show2();
}public class UserServiceImpl implements UserService {@Overridepublic void show1() {System.out.println("show1 ...");}@Overridepublic void show2() {System.out.println("show2 ...");}
}
编写增强(通知)对象myAdvice
// 增强类,内部提供增强方法
public class MyAdvice {public void beforeAdvice(){System.out.println("前置增强....");}public void afterReturningAdvice(){System.out.println("后置增强....");}
}
编写bean后处理器模拟AOP实现
public class MockAopBeanPostProcessor implements BeanPostProcessor , ApplicationContextAware {private ApplicationContext applicationContext = null;@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {// 目的:对UserServiceImpl中的show1,show2方法进行增强,增强方法存在与MyAdvice中// 问题1:筛选service.impl包下所有类和所有方法多可以进行增强,解决方案:if-else// 问题2:MyAdvice怎么获取? 解决方案:从spring容器中获取if (bean.getClass().getPackage().getName().equals("com.mem.service.impl")){//生成当前Bean的代理对象Object proxyInstance = Proxy.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),(Object proxy, Method method, Object[] args)->{MyAdvice myAdvice = (MyAdvice) applicationContext.getBean("myAdvice");//执行增强对象的前置方法myAdvice.beforeAdvice();//执行目标对象的目标方法Object result = method.invoke(bean, args);//执行增强对象的后置方法myAdvice.afterReturningAdvice();return result;});return proxyInstance;}else{return bean;}}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}
}
配置文件:载入容器
<bean id="myAdvice" class="com.mem.advice.MyAdvice"/>
<bean id="userService" class="com.mem.service.impl.UserServiceImpl"/>
<bean id="mockAopBeanPostProcessor" class="com.mem.processor.MockAopBeanPostProcessor"/>
测试
public class ApplicationContextTest {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");UserService userService = (UserService) applicationContext.getBean("userService");userService.show1();}
}
// 输出
前置增强....
show1 ...
后置增强....
** AOP相关概念**
基于xml配置的AOP
通过配置文件的方式去解决上述问题:
- 配置哪些包、哪些类、哪些方法需要被增强 (涉及切点表达式的配置)
- 配置目标方法要被哪些通知方法所增强,在目标方法执行之前还是之后执行增强(涉及织入的配置)
配置方式的设计、配置文件(注解)的解析工作,Spring已经帮我们封装好了
1.导入AOP相关坐标;aspectj是实现AOP的一种实现方式
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version>
</dependency>
Spring-context坐标下已经包含spring-aop的包了,所以就不用额外导入了
2.准备目标类、准备增强(通知)类,并配置给Spring管理
目标类
public interface UserService {void show1();void show2();
}public class UserServiceImpl implements UserService {@Overridepublic void show1() {System.out.println("show1 ...");}@Overridepublic void show2() {System.out.println("show2 ...");}
}
通知类
// 增强类,内部提供增强方法
public class MyAdvice {public void beforeAdvice(){System.out.println("前置增强....");}public void afterReturningAdvice(){System.out.println("后置增强....");}
}
配置文件(需引入aop的命名空间)
<bean id="myAdvice" class="com.mem.advice.MyAdvice"/>
<bean id="userService" class="com.mem.service.impl.UserServiceImpl"/>
3.配置切点表达式(哪些方法被增强);
4.配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。
<!--配置aop-->
<aop:config><!--配置切点表达式,目的:指定哪些方法要被增强--><aop:pointcut id="myPointcut" expression="execution(void com.mem.service.impl.UserServiceImpl.show*())"/><!--配置织入,目的:指定哪些切点与哪些通知进行结合--><aop:aspect id="" ref="myAdvice"><aop:before method="beforeAdvice" pointcut-ref="myPointcut"/><aop:after-returning method="afterReturningAdvice" pointcut-ref="myPointcut"/></aop:aspect>
</aop:config>
切点表达式的配置方式:
直接将切点表达式配置在通知上
可以将切点表达式抽取到外面,在通知上进行引用
切点表达式的配置语法
通知类型
AOP的配置的两种方式
前置通知和后置通知接口
通知类实现了前置通知和后置通知接口
public class MyAdvice2 implements MethodBeforeAdvice, AfterReturningAdvice {@Overridepublic void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {System.out.println("后置通知*****");}@Overridepublic void before(Method method, Object[] objects, Object o) throws Throwable {System.out.println("前置通知*****");}
}
<!--配置aop-->
<aop:config><!--配置切点表达式,目的:指定哪些方法要被增强--><aop:pointcut id="myPointcut" expression="execution(void com.mem.service.impl.UserServiceImpl.show*())"/><!--配置织入,目的:指定哪些切点与哪些通知进行结合--><aop:advisor advice-ref="myAdvice2" pointcut-ref="myPointcut"/>
</aop:config>
环绕通知
通知类实现了方法拦截器接口
public class MyAdvice3 implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("环绕前*****");// 执行目标方法Object res = methodInvocation.getMethod().invoke(methodInvocation.getThis(), methodInvocation.getArguments());System.out.println("环绕后*****");return res;}
}
<!--配置aop-->
<aop:config><!--配置切点表达式,目的:指定哪些方法要被增强--><aop:pointcut id="myPointcut" expression="execution(void com.mem.service.impl.UserServiceImpl.show*())"/><!--配置织入,目的:指定哪些切点与哪些通知进行结合--><aop:advisor advice-ref="myAdvice3" pointcut-ref="myPointcut"/>
</aop:config>
基于注解配置的AOP
Spring的AOP也提供了注解方式配置,使用相应的注解替代之前的xml配置,xml配置AOP时,我们主要配置了三部分:目标类被Spring容器管理、通知类被Spring管理、通知与切点的织入(切面),如下:
<!--配置通知 -->
<bean id="myAdvice" class="com.mem.advice.MyAdvice"/>
<!--配置目标 -->
<bean id="userService" class="com.mem.service.impl.UserServiceImpl"/>
<!--配置aop-->
<aop:config><!--配置织入,目的:指定哪些切点与哪些通知进行结合--><aop:aspect id="" ref="myAdvice"><aop:before method="beforeAdvice" pointcut="execution(void com.mem.service.impl.UserServiceImpl.show*())"/><aop:after-returning method="afterReturningAdvice" pointcut="execution(void com.mem.service.impl.UserServiceImpl.show*())"/></aop:aspect>
</aop:config>
通知
@Service // 第一步
public interface UserService {void show1();
}
目标
@Component // 第二步
public class MyAdvice {public void beforeAdvice(){System.out.println("前置增强....");}public void afterReturningAdvice(){System.out.println("后置增强....");}
}
用注解替代配置aop
配置aop,其实配置aop主要就是配置通知类中的哪个方法(通知类型)对应的切点表达式是什么
@Component
@Aspect // 第三步
public class MyAdvice {// <aop:before method="beforeAdvice" pointcut="execution(void com.mem.service.impl.UserServiceImpl.show*())"/>@Before("execution(void com.mem.service.impl.UserServiceImpl.show*())") // 第四步public void beforeAdvice(){System.out.println("前置增强....");}// <aop:after-returning method="afterReturningAdvice" pointcut="execution(void com.mem.service.impl.UserServiceImpl.show*())"/>@AfterReturning("execution(void com.mem.service.impl.UserServiceImpl.show*())") // 第四步public void afterReturningAdvice(){System.out.println("后置增强....");}
}
配置文件或配置类的修改
配置文件: 注解@Aspect、@Around需要被Spring解析,所以在Spring核心配置文件中需要配置aspectj的自动代理 aop:aspectj-autoproxy/
<!-- aspectj 自动代理-->
<aop:aspectj-autoproxy/>
<!-- 容器包扫描 -->
<context:component-scan base-package="com.mem"/>
配置类:需要加上@EnableAspectJAutoProxy
public class ApplicationContextTest {public static void main(String[] args) {// 配置文件ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext2.xml");UserService userService = (UserService) applicationContext.getBean("userService");userService.show1();// 配置类
// ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationConfig.class);
// UserService userService = (UserService) applicationContext.getBean("userService");
// userService.show1();}
}
// 两种方式的打印结果
前置增强....
show1 ...
后置增强....
基于AOP的声明式事务控制
事务是开发中必不可少的东西,使用JDBC开发时,我们使用connnection对事务进行控制,使用MyBatis时,我们使用SqlSession对事务进行控制,缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化, Spring 就将这些技术基础上,提供了统一的控制事务的接口。Spring的事务分为:编程式事务控制 和 声明式事务控制
搭建测试环境
搭建一个转账的环境,dao层一个转出钱的方法,一个转入钱的方法,service层一个转账业务方法,内部分别调 用dao层转出钱和转入钱的方法,准备工作如下:
数据库准备一个账户表tb_account;
DROP TABLE IF EXISTS `account`;CREATE TABLE `account` (`id` int NOT NULL AUTO_INCREMENT,`account_name` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,`money` int DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;insert into `account`(`id`,`account_name`,`money`) values (1,'tom',5000),(2,'lucy',5000);
dao层准备一个AccountMapper,包括incrMoney和decrMoney两个方法;
public interface AccountMapper {// 加钱@Update("update account set money=money+#{money} where account_name=#{accountName}")void incrMoney(@Param("accountName") String accountName, @Param("money") Integer money);// 减钱@Update("update account set money=money-#{money} where account_name=#{accountName}")void decrMoney(@Param("accountName") String accountName, @Param("money") Integer money);
}
service层准备一个transferMoney方法,分别调用incrMoney和decrMoney方法
public interface AccountService {void transferMoney(String outAccount,String inAccount,Integer money);
}@Service("accountService")
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountMapper accountMapper;@Overridepublic void transferMoney(String outAccount, String inAccount, Integer money) {accountMapper.decrMoney(outAccount,money);
// int i = 1/0;accountMapper.incrMoney(inAccount,money);}
}
在applicationContext文件中进行Bean的管理配置;
<!--组件扫描-->
<context:component-scan base-package="com.mem"/>
<!--加载properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源信息 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" 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>
</bean><!-- 配置SqlSessionFactoryBean,作用将SqlSessionFactory存储到Spring容器中-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/>
</bean><!--MapperScannerConfigurer,作用扫描指定的包,产生Mapper对象,存储到Spring容器中-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.mem.mapper"/>
</bean>
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.91.135:3306/mybatis?useSSL=false
jdbc.username=root
jdbc.password=123456
测试正常转账与异常转账。
public class AccountTest {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");AccountService accountService = (AccountService) applicationContext.getBean("accountService");accountService.transferMoney("tom","lucy",500);System.out.println("转账操作成功");}
}
// 打印结果
转账操作成功
打开AccountServiceImpl类中的错误代码,报错:java.lang.ArithmeticException: / by zero
此时数据库,tom用户的余额减了500,而lucy用户的余额却没有增加500
下面通过spring的声明式事务进行控制
导入Spring事务的相关的坐标,spring-jdbc坐标已经引入的spring-tx坐标
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.12</version>
</dependency>
配置目标类AccountServiceImpl (注解方式已完成)
<context:component-scan base-package="com.mem"/>
使用advisor标签配置切面
<!-- 事务增强的aop -->
<aop:config><!--配置切点表达式--><aop:pointcut id="myPointcut" expression="execution(void com.mem.service.impl.*.*(..))"/><!--配置织入关系,通知是谁? spring提供好的--><aop:advisor advice-ref="" pointcut-ref="myPointcut"/>
</aop:config>
疑问:Spring提供的通知类是谁?是spring-tx包下的advice标签配置提供的
配置详情:
<!-- 引入tx的命名空间 -->
<beans xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"
>
<!--配置平台事务管理器 PlatformTransactionManager-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean>
<!--配置Spring提供好的Advice 内部transaction-manager 需要一个平台事务管理器-->
<tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="*"/></tx:attributes>
</tx:advice>
<!-- 事务增强的aop -->
<aop:config><!--配置切点表达式--><aop:pointcut id="myPointcut" expression="execution(void com.mem.service.impl.*.*(..))"/><!--配置织入关系,通知是谁? spring提供好的--><aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
</aop:config>
MyBatis作为持久层框架时,使用的平台事务管理器实现是DataSourceTransactionManager。
Hibernate作为持久层框架时,使用的平台事务管理器是HibernateTransactionManager。
事务定义信息:
其次,事务定义信息配置,每个事务有很多特性,例如:隔离级别、只读状态、超时时间等,这些信息在开发时 可以通过connection进行指定,而此处要通过配置文件进行配置
ame属性名称指定哪个方法要进行哪些事务的属性配置
方法名在配置时,也可以使用进行模糊匹配,例如:
此处需要区分的是切点表达式指定的方法与此处指定的方法的区别?
切点表达式,是过滤哪些方法可以进行事务增强;
事务属性信息的name,是指定哪个方法要进行哪些事务属性的配置
不需要特殊配置的在最后的加一个<tx:method name=""/>
ead-only属性:设置当前的只读状态
如果是查询则设置为true,可以提高查询性能,如果是更新(增删改)操作则设置为false
timeout属性:设置事务执行的超时时间,单位是秒
如果超过该时间限制但事务还没有完成,则自动回滚事务 ,不在继续执行。默认值是-1,即没有超时时间限制
基于注解声明式事务控制
<!--配置Spring提供好的Advice 内部transaction-manager 需要一个平台事务管理器-->
<tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="*" isolation="REPEATABLE_READ" timeout="3" read-only="false" propagation="REQUIRED"/></tx:attributes>
</tx:advice>
<!-- 事务增强的aop -->
<aop:config><!--配置切点表达式--><aop:pointcut id="myPointcut" expression="execution(void com.mem.service.impl.*.*(..))"/><!--配置织入关系,通知是谁? spring提供好的--><aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
</aop:config>
用@Transactional代替上面两个配置
@Service("accountService")
public class AccountServiceImpl implements AccountService {@Autowiredprivate AccountMapper accountMapper;@Override@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.SUPPORTS,timeout = 3)public void transferMoney(String outAccount, String inAccount, Integer money) {accountMapper.decrMoney(outAccount,money);int i = 1/0;accountMapper.incrMoney(inAccount,money);}
}
同样,使用的事务的注解,平台事务管理器仍然需要配置,还需要进行事务注解开关的开启
<!--配置平台事务管理器 PlatformTransactionManager-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务的注解驱动 transaction-manager:默认值为transactionManager 可以不设置-->
<tx:annotation-driven transaction-manager="transactionManager"/>
改成全注解的方式需要替换成@Bean和@EnableTransactionManagement
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"><!--组件扫描--><context:component-scan base-package="com.mem"/><!--加载properties文件--><context:property-placeholder location="classpath:jdbc.properties"/><!-- 配置数据源信息 --><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" 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></bean><!-- 配置SqlSessionFactoryBean,作用将SqlSessionFactory存储到Spring容器中--><bean class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/></bean><!--MapperScannerConfigurer,作用扫描指定的包,产生Mapper对象,存储到Spring容器中--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.mem.mapper"/></bean><!-- 配置事务的注解驱动 transaction-manager:默认值为transactionManager 可以不设置--><tx:annotation-driven transaction-manager="transactionManager"/><!--配置平台事务管理器 PlatformTransactionManager--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!--配置Spring提供好的Advice 内部transaction-manager 需要一个平台事务管理器--><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="*" isolation="REPEATABLE_READ" timeout="3" read-only="false" propagation="REQUIRED"/></tx:attributes></tx:advice><!-- 事务增强的aop --><aop:config><!--配置切点表达式--><aop:pointcut id="myPointcut" expression="execution(void com.mem.service.impl.*.*(..))"/><!--配置织入关系,通知是谁? spring提供好的--><aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/></aop:config>
</beans>
替换成全注解方式,如下:
@Configuration
@ComponentScan("com.mem") // <context:component-scan base-package="com.mem"/>
@PropertySource("classpath:jdbc.properties") // <context:property-placeholder location="classpath:jdbc.properties"/>
/*** <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">* <property name="basePackage" value="com.mem.mapper"/>* </bean>*/
@MapperScan("com.mem.mapper")
@EnableTransactionManagement // <tx:annotation-driven transaction-manager="transactionManager"/>
public class AccountConfig {@Beanpublic DataSource dataSource(@Value("${jdbc.driver}") String driver,@Value("${jdbc.url}") String url,@Value("${jdbc.username}") String username,@Value("${jdbc.password}") String password){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}@Beanpublic SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSource);return sqlSessionFactoryBean;}@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource){DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return transactionManager;}
}