Spring:IOC、AOP和声明式事务

本文详细介绍了Spring框架的控制反转(IOC)和依赖注入(DI)概念,包括XML配置和注解方式的应用。此外,还探讨了Spring的AOP(面向切面编程)实现,包括动态代理、切面、通知类型以及事务管理。最后,提到了事务的ACID特性和隔离级别,以及Spring中声明式事务的配置。
摘要由CSDN通过智能技术生成

1、IOC:控制反转

1.1、HelloSpring

1.1.1 Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 使用alias标签可以给bean设置别名-->
    <alias name="fromName" alias="toName"/>

    <!-- 
        bean:使用bean标签将bean交由IOC容器管理
        id:唯一标识
        class:bean的全类名
        property:使用该标签为bean中的属性注入值(此处为set注入),bean中需要提供set方法
        scope:作用域
            singleton:单例(默认,每次获取bean是同一个)
            prototype:原型(每次获取bean不是同一个)
        name:使用name属性也可以设置别名
    -->
    <bean id="user" class="com.qf.entity.User" name="alias">
        <property name="name" value="黄本伟"/>
    </bean>
</beans>

1.1.2 bean的使用

@Test
public void test(){

        //使用ClassPathXmlApplicationContext()加载Spring配置文件
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");

        //使用getBean()获取bean对象
        User user=context.getBean("user",User.class);
        System.out.println("name:"+user.getName());
        }

1.2、Dependency Injection:依赖注入

1.2.1 构造器注入:constructor-arg


<bean id="address" class="com.qf.entity.Address">
    <property name="address" value="xxxxx"/>
</bean>

        <!--
            方式一:构造器注入
            constructor-arg:通过该标签进行构造器注入
            ref:引用类型注入
            value:基本数据类型注入
        -->
<bean id="user" class="com.qf.entity.User">
<constructor-arg ref="address"/>
<constructor-arg value="张三"/>
</bean>

1.2.2 Set注入:Property

<!--
    方式二:Set注入,通过Set方法注入
-->
<bean id="user" class="com.qf.entity.User">
    <!-- 基本数据类型-->
    <property name="id" value="1"/>
    <property name="name" value="xxx"/>
    <!-- 引用数据类型-->
    <property name="address" ref="address"/>
    <!-- 数组类型-->
    <property name="hobbies">
        <array>
            <value>听歌</value>
            <value>玩游戏</value>
        </array>
    </property>
    <!--
        集合类型:collection
        Set:set标签
        List:List标签
    -->
    <property name="brothers">
        <set>
            <value>老大</value>
            <value>老二</value>
        </set>
    </property>
    <property name="loves">
        <list>
            <value>听歌</value>
            <value>玩游戏</value>
        </list>
    </property>
    <!-- Map类型-->
    <property name="cards">
        <map>
            <entry key="身份证" value="111111222222223333"/>
            <entry key="学生卡" value="112336544789"/>
        </map>
    </property>
    <!-- Properties类型-->
    <property name="config">
        <props>
            <prop key="111">111</prop>
            <prop key="222">222</prop>
        </props>
    </property>
    <!-- null值注入-->
    <property name="wife">
        <null/>
    </property>
</bean>
  • 测试代码
@Test
public void testUser(){
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");

        User user=(User)context.getBean("user");

        System.out.println(user);
        }
/*
User(id=1, name=xxx, 
hobbies=[听歌, 玩游戏], loves=[听歌, 玩游戏], 
address=Address(address=xxxxx), 
brothers=[老大, 老二], 
cards={身份证=111111222222223333, 学生卡=112336544789}, 
wife=null, 
config={222=222, 111=111})
*/

1.2.3 Autowired:自动注入

  • autowire=“byName”
<!--
    autowire="byName":根据名称匹配实现自动注入
-->
<bean id="userService" class="com.qf.service.impl.UserServiceImpl" autowire="byName"/>
  • autowire=“byType”
<!--
    autowire="byType":根据类型匹配实现自动注入
-->
<bean id="userService" class="com.qf.service.impl.UserServiceImpl" autowire="byType"/>
  • @Autowired:自动装配
<?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约束并且声明注解-->
    <context:annotation-config/>

    <bean id="userMapper" class="com.qf.mapper.impl.UserMapperImpl"/>
    <bean id="userService" class="com.qf.service.impl.UserServiceImpl"/>
</beans>
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public User selectUser() {
        return this.userMapper.selectUser();
    }
}

    //测试
    @Test
    public void testUserService() {

        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userService = (UserService) context.getBean("userService");

        System.out.println(userService.selectUser());
    }

2、AOP

2.1 动态代理

2.1.1 JDK动态代理实现(基于接口)

  • 需要被代理的类
public class UserMapperImpl implements IUserMapper {

    @Override
    public void add() {
        System.out.println("UserMapperImpl.add");
    }

    @Override
    public void delete() {
        System.out.println("UserMapperImpl.delete");
    }

    @Override
    public void update() {
        System.out.println("UserMapperImpl.update");
    }

    @Override
    public void insert() {
        System.out.println("UserMapperImpl.insert");
    }
}
  • 调用Proxy.newProxyInstance()方法生成代理类
public class UserMapperProxy {

    IUserMapper userMapper;

    public UserMapperProxy(IUserMapper userMapper) {
        this.userMapper = userMapper;
    }

    public IUserMapper getProxy() {

        /*
            获取代理类对象:
            Proxy.newProxyInstance(ClassLoader loader,
                                  @NotNull Class<?>[] interfaces,
                                  @NotNull reflect.InvocationHandler h)
            ClassLoader loader:类加载器
            Class<?>[] interfaces:被代理类的接口
            reflect.InvocationHandler h:处理接口,添加额外的功能,
            需重写invoke(Object proxy, Method method, Object[] args)方法
            Object proxy:生成的代理类对象
            Method method:需要执行的方法
            Object[] args:被执行方法的参数
         */
        return (IUserMapper) Proxy.newProxyInstance(UserMapperProxy.class.getClassLoader(), this.userMapper.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                System.out.println("执行方法前。。。");

                Object invoke = method.invoke(UserMapperProxy.this.userMapper, args);

                System.out.println("执行方法前。。。");

                return invoke;
            }
        });
    }
}
  • 测试
public class TestProxy {

    @Test
    public void testProxy() {

        //初始化
        UserMapperProxy userMapperProxy = new UserMapperProxy(new UserMapperImpl());

        //获取代理类对象
        IUserMapper proxy = userMapperProxy.getProxy();

        //调用代理后的方法
        proxy.add();

        proxy.delete();

        proxy.insert();

        proxy.update();

        /*
            执行方法前。。。
            UserMapperImpl.add
            执行方法前。。。
        */
    }
}
  • 动态代理的好处
  • 静态代理有的它都有,静态代理没有的,它也有!
  • 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
  • 公共的业务由代理来完成 . 实现了业务的分工 ,
  • 公共业务发生扩展时变得更加集中和方便 .
  • 一个动态代理 , 一般代理某一类业务
  • 一个动态代理可以代理多个类,代理的是接口!

2.2 AOP相关概念

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等
  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知 执行的 “地点”的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。

2.3 使用spring实现AOP

2.3.1 导入AOP织入依赖

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
</dependency>

2.3.2 方式一:通过Spring API实现

  • SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
  • 前置通知:MethodBeforeAdvice
  • 后置通知:AfterReturningAdvice
  • 环绕通知:MethodInterceptor
  • 异常抛出通知:ThrowsAdvice
  • 引介通知:IntroductionInterceptor
  • Aop在不改变原有代码的情况下 , 去增加新的功能

  • 编写两个通知类,分别实现MethodBeforeAdvice接口和AfterReturningAdvice接口

@Component
public class BeforeLog implements MethodBeforeAdvice {

    /**
     * 前置通知
     *
     * @param method 要执行的目标对象的方法
     * @param args   被调用的方法的参数
     * @param target 目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getSimpleName() + "的" + method.getName() + "方法被执行了。。。");
    }
}

@Component
public class AfterLog implements AfterReturningAdvice {

    /**
     * 带返回值的后置通知
     *
     * @param returnValue 返回值
     * @param method      被调用的方法
     * @param args        方法的参数
     * @param target      目标对象
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + target.getClass().getName()
                + "的" + method.getName() + "方法,"
                + "返回值:" + returnValue);
    }
}
  • 在spring配置文件中配置AOP切入实现,需要先导入aop约束
<?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"
       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">

    <!-- 告知spring,哪些包中 有被注解的类、方法、属性 -->
    <!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
    <context:component-scan base-package="com.qf"/>

    <!--aop的配置-->
    <aop:config>
        <!--切入点 execution表达式:表达式匹配要执行的方法-->
        <aop:pointcut id="pointcut" expression="execution(* com.qf.service.impl.UserServiceImpl.*(..))"/>
        <!--切入通知; advice-ref执行方法 . pointcut-ref切入点-->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>
  • 测试
@Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.add();
        userService.delete();
        userService.update();
        userService.select();
    }
    /*
        UserServiceImpl的add方法被执行了。。。
        UserServiceImpl.add
        执行了com.qf.service.impl.UserServiceImpl的add方法,返回值:null
    */

2.3.3 自定义切面类实现

  • 自定义的切面类
@Component
public class DiyPointCut {

    public void before() {
        System.out.println("执行方法前。。。");
    }

    public void after() {
        System.out.println("执行方法后。。。");
    }
}
  • 在spring配置中配置切面
<!--方式二:-->
        <!--切面:aop:aspect-->
        <aop:aspect ref="diyPointCut">
            <aop:pointcut id="pointcut" expression="execution(* com.qf.service.impl.UserServiceImpl.*(..))"/>
            <!--
                前置通知:aop:before
                后置通知:aop:after
            -->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>

2.3.4 使用注解实现

  • 需要在spring配置文件中增加支持注解的配置
<!--增加aop注解支持-->
    <!--
        通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。
        当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,
        但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了

        <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,
        表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy  poxy-target-class="true"/>时,
        表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,
        如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
    -->
<aop:aspectj-autoproxy/>
  • 编写切面类
@Component
@Aspect
public class AnnotationPointcut {

    @Before("execution(* com.qf.service.impl.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("执行方法前。。。");
    }

    @After("execution(* com.qf.service.impl.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("执行方法后。。。");
    }

    /**
     * @param jp 连接点对象
     * @throws Throwable
     */
    @Around("execution(* com.qf.service.impl.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:" + jp.getSignature());
        //执行目标方法proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println(proceed);
    }
}
  • 测试
@Test
    public void test1() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.add();
    }
    /*
        环绕前
        签名:void com.qf.service.UserService.add()
        执行方法前。。。
        UserServiceImpl.add
        执行方法后。。。
        环绕后
        null
    */

3 事务

3.1 事务的ACID特性

  • 原子性(atomicity)
    • 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
  • 一致性(consistency)
    • 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
  • 隔离性(isolation)
    • 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
  • 持久性(durability)
    • 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

3.2 事务的隔离级别

  • isolation 隔离级别
名称描述
default(默认值)(采用数据库的默认的设置) (建议)
read-uncommited读未提交
read-commited读提交 (Oracle数据库默认的隔离级别)
repeatable-read可重复读 (MySQL数据库默认的隔离级别)
serialized-read序列化读
  • 隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read
  • 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。
  • 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。

3.3 事务并发问题

问题描述
脏读一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止
不可重复读一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止
幻影读一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止

3.4 事务的传播行为

  • propagation传播行为
  • 当涉及到事务嵌套(Service调用Service)时,可以设置:
  • SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
  • REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)

3.5 事务回滚

  • rollback-for 回滚属性
  • 如果事务中抛出 RuntimeException,则自动回滚
  • 如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务
  • 处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for=“Exception”

3.6 spring配置声明式事务

  • 配置事务管理器
<!-- 1. 引入一个事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑 -->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
- 注意:DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource的Bean,否则事务控制失败!!!
  • 配置事务通知
<tx:advice id="txManager" transaction-manager="tx">
	<tx:attributes>
        <!--<tx:method name="insertUser" rollback-for="Exception" isolation="DEFAULT"    
              	propagation="REQUIRED" read-only="false"/>-->
        <!-- 以User结尾的方法,切入此方法时,采用对应事务实行-->
        <tx:method name="*User" rollback-for="Exception"/>
        <!-- 以query开头的方法,切入此方法时,采用对应事务实行 -->
        <tx:method name="query*" propagation="SUPPORTS"/>
        <!-- 剩余所有方法 -->
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>
  • 切入事务
<aop:config>
	<aop:pointcut expression="execution(* com.qf.spring.service.UserServiceImpl.*(..))" id="pc"/>
    <!-- 组织切面 -->
    <aop:advisor advice-ref="txManager" pointcut-ref="pc"/>
</aop:config>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值