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的接口定义如下:
java
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName + ":postProcessBeforeInitialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName + ":postProcessAfterInitialization");
return bean;
}
}
java
@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整合第三方框架
原始操作
java
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;
}
@Override
public String toString() {
return "Userbean{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
java
package JDBC_Template;
import java.util.List;
public interface UserMapper
{
List<Userbean> findUser();
}
java
<?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="JDBC_Template.UserMapper">
<select id="findUser" resultType="JDBC_Template.Userbean">
select * from dsuser
</select>
</mapper>
java
<?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>
<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>
java
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
{
@Test
public 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的相关坐标
java
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
java
package JDBC_Template;
import java.util.List;
public interface UserMapper
{
List<Userbean> findUser();
}
java
<?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="JDBC_Template.UserMapper">
<select id="findUser" resultType="JDBC_Template.Userbean">
select * from dsuser
</select>
</mapper>
java
<?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(SqlSessionFactory一旦被创建就在应用的运行期间一直存在)
我们来看看SqlSessionFactoryBean的定义是怎样的:
java
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
}
当实现了这个接口的 Bean 在配置为被 Spring 接管时,存入IoC容器中的实例类型将会是实例化泛型的那个类型,从IoC容器中获取时也是实例化泛型的那个类型,这种情况下,Spring 将会在应用启动时为你创建 SqlSessionFactory 对象,然后将它以 SqlSessionFactory 为名来存储。当把这个bean注入到Spring中去了以后,IoC容器中的其他类型就可以拿到 SqlSession 实例了,就可以进行相关的SQL执行任务了。
注:MapperScannerConfigurer的作用是将Mapper的对象存储到Spring容器中
例如:
java
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入mapper接口所在的包-->
<property name="basePackage" value="com.bus.mapper,com.sys.mapper,com.stat.mapper">
</property>
<!--注入sqlsessionfactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
java
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());
}
}
java
@Test
public 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文件的位置
java
<context:property-placeholder location="jdbc.properties"/>
基于bean的注解开发
使用注解代替xml文件
也就是说告诉spring扫描哪些包,spring会扫描基本包及其下面子包
java
<context:component-scan base-package="com.wang"></context:component-scan>
@Autowired根据类型进行注入,如果同一类型有多个名字不同的bean那就按照名字进行二次匹配
【重温SSM框架系列】3 - Spring注解开发(注解代替xml文件配置)
java
<bean id="userService" class="com.wang.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
@Autowired结合@Qualifier根据名字进行注入
非自定义bean的配置
java
@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配置方式如下:
java
@Configuration
@ComponentScan("com.itheima")
@MapperScan("com.itheima.mapper")
public class ApplicationContextConfig {
@Bean
public 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;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
}
注解方式,Spring整合MyBatis的原理,关键在于@MapperScan,@MapperScan不是Spring提供的注解,而是MyBatis为了整合Spring,在整合包org.mybatis.spring.annotation中提供的注解,源码如下:
java
@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接口的类
java
@Configuration
@ComponentScan("com.itheima")
@Import({MyImportSelector.class})
public class ApplicationContextConfig {
}
java
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 返回要进行注册的Bean的全限定名数组
return new String[]{User2.class.getName()};
}
}
ImportSelector接口selectImports方法的参数AnnotationMetadata代表注解的媒体数据,可以获得 当前注解(@Import({MyImportSelector.class})) 修饰的类(ApplicationContextConfig ) 的 其他注解的元信息,例如:@Configuration、@ComponentScan("com.itheima")注解的元信息
java
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 获得指定类型注解的全部信息
Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(ComponentScan.class.getName());
// 获得全部信息中basePackages信息
String[] basePackages = (String[]) annotationAttributes.get("basePackages");
// 打印结果是com.itheima
System.out.println(basePackages[0]);
return new String[]{User2.class.getName()};
}
}
@Import导入实现ImportBeanDefinitionRegistrar接口的类,实现了该接口的类的registerBeanDefinitions方法会被自动调用,在该方法内可以注册BeanDefinition
java
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 使用给定的BeanDefinitionRegistry参数,手动注册BeanDefinition
BeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClassName("com.itheima.pojo.User2");
registry.registerBeanDefinition("user2", beanDefinition);
}
}
Aop
AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程
引入坐标
编写目标对象userService
java
public interface UserService {
void show1();
void show2();
}
public class UserServiceImpl implements UserService {
@Override
public void show1() {
System.out.println("show1 ...");
}
@Override
public void show2() {
System.out.println("show2 ...");
}
}
编写增强(通知)对象myAdvice
java
// 增强类,内部提供增强方法
public class MyAdvice {
public void beforeAdvice(){
System.out.println("前置增强....");
}
public void afterReturningAdvice(){
System.out.println("后置增强....");
}
}
编写bean后处理器模拟AOP实现
java
public class MockAopBeanPostProcessor implements BeanPostProcessor , ApplicationContextAware {
private ApplicationContext applicationContext = null;
@Override
public 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;
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
配置文件:载入容器
java
<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"/>
测试
java
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的一种实现方式
java
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
Spring-context坐标下已经包含spring-aop的包了,所以就不用额外导入了
2.准备目标类、准备增强(通知)类,并配置给Spring管理
目标类
java
public interface UserService {
void show1();
void show2();
}
public class UserServiceImpl implements UserService {
@Override
public void show1() {
System.out.println("show1 ...");
}
@Override
public void show2() {
System.out.println("show2 ...");
}
}
通知类
java
// 增强类,内部提供增强方法
public class MyAdvice {
public void beforeAdvice(){
System.out.println("前置增强....");
}
public void afterReturningAdvice(){
System.out.println("后置增强....");
}
}
配置文件(需引入aop的命名空间)
java
<bean id="myAdvice" class="com.mem.advice.MyAdvice"/>
<bean id="userService" class="com.mem.service.impl.UserServiceImpl"/>
3.配置切点表达式(哪些方法被增强);
4.配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。
java
<!--配置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的配置的两种方式
前置通知和后置通知接口
通知类实现了前置通知和后置通知接口
java
public class MyAdvice2 implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("后置通知*****");
}
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("前置通知*****");
}
}
java
<!--配置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>
环绕通知
通知类实现了方法拦截器接口
java
public class MyAdvice3 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕前*****");
// 执行目标方法
Object res = methodInvocation.getMethod().invoke(methodInvocation.getThis(), methodInvocation.getArguments());
System.out.println("环绕后*****");
return res;
}
}
java
<!--配置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管理、通知与切点的织入(切面),如下:
java
<!--配置通知 -->
<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>
通知
java
@Service // 第一步
public interface UserService {
void show1();
}
目标
java
@Component // 第二步
public class MyAdvice {
public void beforeAdvice(){
System.out.println("前置增强....");
}
public void afterReturningAdvice(){
System.out.println("后置增强....");
}
}
用注解替代配置aop
配置aop,其实配置aop主要就是配置通知类中的哪个方法(通知类型)对应的切点表达式是什么
java
@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/
java
<!-- aspectj 自动代理-->
<aop:aspectj-autoproxy/>
<!-- 容器包扫描 -->
<context:component-scan base-package="com.mem"/>
配置类:需要加上@EnableAspectJAutoProxy
java
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;
java
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两个方法;
java
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方法
java
public interface AccountService {
void transferMoney(String outAccount,String inAccount,Integer money);
}
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public void transferMoney(String outAccount, String inAccount, Integer money) {
accountMapper.decrMoney(outAccount,money);
// int i = 1/0;
accountMapper.incrMoney(inAccount,money);
}
}
在applicationContext文件中进行Bean的管理配置;
java
<!--组件扫描-->
<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>
java
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.91.135:3306/mybatis?useSSL=false
jdbc.username=root
jdbc.password=123456
测试正常转账与异常转账。
java
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坐标
java
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.12</version>
</dependency>
配置目标类AccountServiceImpl (注解方式已完成)
java
<context:component-scan base-package="com.mem"/>
使用advisor标签配置切面
java
<!-- 事务增强的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标签配置提供的
配置详情:
java
<!-- 引入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,即没有超时时间限制
基于注解声明式事务控制
java
<!--配置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代替上面两个配置
java
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private 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);
}
}
同样,使用的事务的注解,平台事务管理器仍然需要配置,还需要进行事务注解开关的开启
java
<!--配置平台事务管理器 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
java
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://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>
替换成全注解方式,如下:
java
@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 {
@Bean
public 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;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}