Sping全面复习

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;
    }
}
相关推荐
煤泥做不到的!3 分钟前
挑战一个月基本掌握C++(第六天)了解函数,数字,数组,字符串
开发语言·c++
Edward-tan3 分钟前
【全栈开发】----用pymysql库连接MySQL,批量存入
数据库·mysql·pymysql
潜意识起点6 分钟前
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
java·spring boot·后端
mxbb.8 分钟前
单点Redis所面临的问题及解决方法
java·数据库·redis·缓存
智能与优化11 分钟前
C++打造局域网聊天室第十一课: 程序关闭及线程的结束
开发语言·c++
lsx20240625 分钟前
MongoDB 更新文档
开发语言
云和数据.ChenGuang32 分钟前
《XML》教案 第1章 学习XML基础
xml·java·学习
大霸王龙34 分钟前
在 Django 中使用 SMTP 发送邮件是一个常见的需求
数据库·django·sqlite
王·小白攻城狮·不是那么帅的哥·天文40 分钟前
Java操作Xml
xml·java
爱数学的程序猿1 小时前
Python入门:1.Python介绍
开发语言·python