欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring中的IOC和AOP以及Spring中的声明式事务

程序员文章站 2022-03-10 10:53:19
Spring中的IOC和AOP以及Spring中的声明式事务(基于狂神说Java-Spring)Spring中的IOCIOC(Inversion Of Controller)又称控制反转,是一种通过(xml或者注解)并通过第三方生成或获取特定对象的方式,在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入(DI:Dependency Injection)控制:控制对象的创建,传统的Java面向对象编程,对象是由程序本身控制实现创建的。用了Spring之后,对象都是由Spring来进行创建...

Spring中的IOC和AOP以及Spring中的声明式事务(基于狂神说Java-Spring)

Spring中的IOC

IOC(Inversion Of Controller)又称控制反转,是一种通过(xml或者注解)并通过第三方生成或获取特定对象的方式,在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入(DI:Dependency Injection)

  • 控制:控制对象的创建,传统的Java面向对象编程,对象是由程序本身控制实现创建的。用了Spring之后,对象都是由Spring来进行创建的
  • 反转:程序现在不用自己创建对象,而是自己被动的接收一个对象

总结:所谓的控制反转,就是创建对象的方式反转了,总的来说,对象可以交给Spring容器进行创建,管理,装配

Spring 当中的依赖

 <dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.7.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

注解说明

  • @Autowired:Spring注解自动装配,可以通过类型和名字,但是当Bean实例多于两个时无法装配,需要通过@Quefilier注解配合使用指定某个Bean实例(注意:@Qualifier不能单独使用),如果允许对象为null,设置required = false,required属性默认为true,即为**@Autowired(required = false)**
  • @Nullable:如果某个字段标记了这个注解后,说明这个字段可以为null
  • @Resource:此注解是JavaEE原生注解实现,同样可以实现自动装配,在Bean实例多于两个时可以通过name属性指定Bean实例(但是这边有一个小问题,随着JDK版本的提升,JavaEE原生注解就不再支持,所以在高版本的JDK中还需要导入相关的Maven依赖),@Resource会通过name属性进行查找,如果说查找不到才会通过Bean的类型进行查找。
 <dependency>
   <groupId>javax.annotation</groupId>
   <artifactId>javax.annotation-api</artifactId>
   <version>1.3.2</version>
 </dependency>
  • @Component:组件,使用此注解声明当前Java类交由Spring进行管理(在此注解的基础上还增加了三个衍生注解,基于MVC三层架构)

    • @Repository:dao层注解
    • @Service:Service层注解
    • @Controller:Controller层注解

注意事项:翻阅底层源码可以发现这四个注解的代码都是一样的,意味着他们的功能都是一样的,都是代表将某个类交由Spring托管,进行Bean的装配

对于xml和注解:

  • xml配置在有些情况下会较为复杂,但是xml文件配置适用于任何场合
  • 注解,对于注解,它的作用范围最大只能在当前这个Java类中,无法延伸到其他的类其他的包中,并且在后期项目维护极为不便

所以针对这种情况最直接的解决方案就是两种方式进行组合(最佳实现)

  • xml用来对Bean进行统一的管理
  • 注解用来对属性进行注入处理
  • 在使用的过程中,必须要让注解生效,就需要在xml中进行开启注解支持
<!--扫描包,在指定包下的注解即刻生效-->
<context:component-scan base-package="com.hrc"/>
<context:annotation-config/>

回顾代理模式

SpringAOP(Aspect Oriented Programming)的底层就是代理设计模式!!!

代理模式有两种:静态代理和动态代理

静态代理

业务分析:

  • 抽象角色:一般都会用接口(使用较多)或者是抽象类来解决
// 以租房为例实现静态代理
public interface Rent {
    void rent();
}
  • 真实角色:处理真实业务,同时被代理角色进行代理
// 房东
public class Host implements Rent {
    @Override
    public void rent() {
        System.out.println("房东要出租房子(真实业务)");
    }
}
  • 代理角色:代理真实角色,并且实现属于自己的扩展业务
public class MiddleProxy extends Host {
    // 处理真实业务
    public void rent() {
        this.rentBefore();
        super.rent(); // 真实业务
        this.rentAfter();
        this.res();
    }
    // 实现扩展业务
    public void rentBefore() {
        System.out.println("中介接单");
    }
    public void rentAfter() {
        System.out.println("中间商赚差价");
    }
    public void res() {
        System.out.println("扩展业务完成");
    }
}
  • 用户:访问代理对象
public class Client {
    public static void main(String[] args) {
        // 代理角色,中介帮房东租房子,但是中介会做一些房东不会做的扩展业务
        MiddleProxy proxy = new MiddleProxy();
        proxy.rent();
    }
}

代理模式的好处:

  • 可以让真实业务操作变得更加纯粹,说白了就是“我”该干嘛就干嘛,不用去关心其他的扩展业务
  • 公共业务就交给了其他的代理角色,实现业务的分工处理
  • 公共业务延伸出其他的扩展业务时,方便集中进行管理

缺点:

  • 一个真实业务就需要一个代理对象进行代理,一旦真实业务多了之后,代码量提升就会造成冗余,开发效率降低

动态代理

业务分析:

  • 抽象角色:与静态代理相同

  • 代理角色:动态生成,不是由开发者自己编写,而是开发者自定义一个模板然后交给程序去生成代理对象

  • 动态代理的实现机制有两种:一种是基于接口的动态代理,还有一种就是基于cglib实现动态代理

    • 基于接口:JDK动态代理
    • 基于类的动态代理,也就是cglib动态代理(需要导入额外的cglib相关依赖)
    • Java字节码实现动态代理:Javassist

首先需要熟悉两个Java类:Proxy:代理;InvocationHandler:调用处理程序

动态代理的好处:

  • 静态代理的优点全部具备
  • 一个动态代理类代理的是一个接口,一般就是对应其某一个类型的业务,而不是单个业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可

优化之前实现的房东买房子的业务操作

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 用此类动态生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
    // 在生成代理类的同时还需要一个被代理的接口
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }
    // 生成代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    /**
     * 处理代理实例,并返回结果
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        // 动态代理的本质就是使用反射机制实现
        Object res = method.invoke(target, args);
        return res;
    }
    public void log(String msg) {
        System.out.println("【INFO】" + msg);
    }
}
public class Client {
    public static void main(String[] args) {
        // 真实业务主题
        Host host = new Host();
        // 生成代理角色
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setTarget(host); // 设置代理对象
        // 动态生成代理类
        Rent proxy = (Rent) pih.getProxy();
        proxy.rent();
    }
}

SpringAOP

AOP(Aspect Oriented Programming),意思是面向切面编程,它是OOP(Object-Oriented Programming 面向对象编程)的一种延续
在不破坏原有代码实现的基础上,利用“织入”的模式来实现一些代码的动态配置
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高代码可重用性,提高开发效率

使用SpringAOP需要额外导入相关依赖

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

实现AOP的方式之一:使用Spring的接口

主要是通过实现Spring的接口来进行切面的处理

public interface UserService {
    void insert();
    void delete();
    void update();
    void select();
}
/*--------------两个类分开写----------------*/
public class UserServiceImpl implements UserService {
    @Override
    public void insert() {
        System.out.println("【INSERT】增加了一个用户");
    }
    @Override
    public void delete() {
        System.out.println("【DELETE】删除了一个用户");
    }
    @Override
    public void update() {
        System.out.println("【UPDATE】修改了一个用户");
    }
    @Override
    public void select() {
        System.out.println("【SELECT】查询了一个用户");
    }
}
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
    /**
     * 在实现操作业务之前首先要先做一些日志信息
     * @param method 要执行的目标对象的方法
     * @param objects 参数,对象数组
     * @param target 目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
    }
}
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
    // 后置通知,并且接收返回值
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + method.getName() + "方法,返回结果为" + returnValue);
    }
}
<!--注册Bean-->
<bean id="userService" class="com.hrc.service.UserServiceImpl"/>
<bean id="log" class="com.hrc.log.Log"/>
<bean id="afterLog" class="com.hrc.log.AfterLog"/>
<!--配置aop-->
<aop:config>
    <!--首先需要设置切入点pointcut,expression,是一个AspectJ表达式-->
    <aop:pointcut id="pointcut" expression="execution(public * com.hrc.service..*(..))"/>
    <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
    <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>

这里有一个小东西叫做AspectJ表达式,Spring容器中支持此表达式,用来进行切入点的定义

其语法格式为:方法注解 权限修饰符 返回值 全限定类名 方法名 (参数);这里有几个地方需要注意

  • 方法注解:可选操作,即一个方法上的注解,例如@Override
  • 权限修饰符:public,protected,default(权限修饰符为可选操作,即可有可无)
  • 返回值:有多种,这里可以用 * 来表示这个表达式的返回值为任意返回值
  • 全限定类名:其名称格式为,包.类名称;如果这个包下面还有子包,可以使用“…”来代替所有的子包
  • 方法名:普通普通的方法名称,这边可以和全限定类名进行组合,所以总的名称格式可以为包.类名称.方法名(参数)
  • (参数):括号里可以使用“…”来表示任意的参数
import com.hrc.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 动态代理代理的是接口,实现类不需要被代理
        UserService service = context.getBean("userService", UserService.class);
        service.insert();
    }
}

SpringAOP的实现方式二:自定义类实现 AOP

自定义类实现AOP只需要编写自定义类,无需实现Spring自带的接口,通过配置文件进行切面的处理配置

public class MyselfClass {
    public void before() {
        System.out.println("【INFO---BEFORE】业务处理之前");
    }
    public void after() {
        System.out.println("【INFO---AFTER】业务处理之后");
    }
}
<bean id="myselfClass" class="com.hrc.myClass.MyselfClass"/>
<aop:config>
    <!--aop:aspect标签,自定义切面,ref,引用要切入的类-->
    <aop:aspect ref="myselfClass">
        <!--定义切入点-->
        <aop:pointcut id="pointcut" expression="execution(public * com.hrc.service..*(..))"/>
        <!--通知,这个概念指的是实现切入点的扩展业务-->
        <aop:before method="before" pointcut-ref="pointcut"/>
        <aop:after method="after" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

SpringAOP 实现注解配置

// 使用注解方式实现AOP
@Aspect // 标注此类为一个切面
public class AnnotationPointcut {
    @Before("execution(public * com.hrc.service..*(..))")
    public void before() {
        System.out.println("【INFO】方法执行前");
    }
    @After("execution(public * com.hrc.service..*(..))")
    public void after() {
        System.out.println("【INFO】方法执行后");
    }
    // 在环绕增强中,可以给定一个参数,代表我们要获取的切入的点
    @Around("execution(public * com.hrc.service..*(..))")
    public void around(ProceedingJoinPoint pj) throws Throwable {
        System.out.println("【INFO】使用环绕之前");
        System.out.println("【INFO】获得签名:" + pj.getSignature());
        // 执行此方法
        Object proceed = pj.proceed();
        System.out.println("【INFO】获得执行对象:" + proceed);
        System.out.println("【INFO】使用环绕之后");
    }
}
<bean id="annotationPointcut" class="com.hrc.myClass.AnnotationPointcut"/>
<!--开启注解支持 在开启注解支持的时候,Spring容器有两种实现AOP的方式
    1.JDK动态代理(Spring默认)
    2.cglib动态代理
    设置两种实现AOP的方式有一个属性标签proxy-target-class="false"
    如果属性值为false,那么就是默认JDK动态代理
    如果为true,就会以cglib动态代理实现AOP,两种实现方式的运行结果都是相同的
    实现机制不一样
-->
<aop:aspectj-autoproxy/>

Spring整合MyBatis

实现步骤

  • 导入相关依赖
    • junit 单元测试
    • MyBatis
    • MySQL数据库
    • Spring相关
    • SpringAOP的相关依赖
    • MyBatis-Spring(一个新的依赖,可以和Spring完美整合)
  • 编写配置文件
  • 测试

相关依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>
<!--要想使用Spring操作数据库的话,还需要一个相关依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.5</version>
</dependency>

回顾MyBatis

  1. 编写实体类
  2. 编写核心配置文件
  3. 编写接口
  4. 编写mapper.xml
  5. 测试

MyBatis-Spring

  • 编写数据源配置
<!--DataSource:使用Spring的数据源替换掉MyBatis的配置,除此之外还有 c3p0 dbcp druid
    这里使用的是Spring当中提供的JDBC:org.springframework.jdbc.datasource.*
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
</bean>
  • SqlSessionFactory
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!--绑定MyBatis配置文件-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath*:com.hrc.mapper/UserMapper.xml"/>
</bean>
  • SqlSessionTemplate
<!--SqlSessionTemplate:他就是我们使用的SqlSession
    翻阅底层源码,发现这个类,没有相应的set方法,所以在使用Spring容器注入时,
    需要使用构造器注入的方式进行资源注入
-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
  • Mapper接口加实现类
import com.hrc.pojo.User;
import java.util.List;
public interface UserMapper {
    List<User> getUsers();
}
import com.hrc.mapper.UserMapper;
import com.hrc.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper {
    // 在以前都使用SqlSession来执行所有操作,现在只需要使用SqlSessionTemplate
    private SqlSessionTemplate sqlSession;
    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }
    @Override
    public List<User> getUsers() {
        return sqlSession.getMapper(UserMapper.class).getUsers();
    }
}
  • 测试
import com.hrc.mapper.UserMapper;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper mapper = context.getBean("userMapper", UserMapper.class);
        mapper.getUsers().forEach(System.out::println);
    }
}

Spring-MyBatis还有一种整合方式,就是直接实现SqlSessionDaoSupport类

<!--这是整合MyBatis的第二种方式,继承SqlSessionDaoSupport类
    它是一个抽象类,翻阅源码可以发现,此类中内置SqlSessionTemplate,
    但是需要一个SqlSessionFactory,所以在交由Spring容器进行管理时,
    需要注入资源的属性是SqlSessionFactory
-->
<bean id="mapper" class="com.hrc.mapper.impl.UserMapperImplement">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
import com.hrc.mapper.UserMapper;
import com.hrc.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImplement extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> getUsers() {
        // 由于继承了SqlSessionDaoSupport类,可以直接使用getSqlSession方法得到进一步简化
        return getSqlSession().getMapper(UserMapper.class).getUsers();
    }
}

Spring中的声明式事务

回顾事务的基础知识

  • 要么同时成功,要么同时失败
  • 事务在项目开发中占据非常重要的位置,因为涉及到事务的一致性问题,所以必须认真对待
  • 确保完整性和一致性

事务的ACID原则

  • 原子性(Atomicity):确保一个业务当中的所有操作,要么同时成功,要么同时失败
  • 一致性(Consistency):事务的运行不改变数据当中的一致性,举个例子,对于银行转账来说,a用户有1000元,b用户有1000元,不管这个事务最后是成功还是失败,a+b的和始终都是2000
  • 隔离性(Isolation):多个事务可能操作同一个资源,防止资源损坏。事务与事务之间是处于隔离的状态,一个事务不应该影响其他事务的运行效果
  • 永久性(Durability):事务一旦提交,无论系统发生什么问题,都不会再被影响,被持久化的写到存储器中

Spring中的事务管理

  • 声明式事务:AOP
<!--在Spring中配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!--结合AOP实现事务的“织入”-->
<!--配置事务通知-->
<tx:advice id="interceptor" transaction-manager="transactionManager">
    <!--给哪些方法配置事务-->
    <!--配置事务的传播属性-->
    <tx:attributes>
        <tx:method name="addUser" propagation="REQUIRED" isolation="DEFAULT"/>
        <tx:method name="selectUsers"/>
        <tx:method name="delUser" propagation="REQUIRED"/>
        <tx:method name="*" propagation="REQUIRED"/> <!-- * 代表所有方法-->
    </tx:attributes>
</tx:advice>
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(public * com.hrc.mapper..*.*(..))"/>
    <aop:advisor advice-ref="interceptor" pointcut-ref="txPointcut"/>
</aop:config>
  • 编程式事务:需要在代码中进行事务的管理

为什么需要事务

  • 如果不配置事务,可能存在数据提交不一致的情况
  • 如果不在Spring中去配置事务,那么就需要在代码中手动配置事务
  • 事务在项目的开发中十分重要,涉及到数据的一致性和完整性

最后再多聊一嘴Spring中事务的传播属性

传播属性

刚刚在使用xml进行事务的织入时有一个标签叫做propagation,propagation在xml中可以进行事务的传播属性的处理配置,它主要有以下几种属性
(面试可能会问到,建议最好还是记一下)

  • REQUIRED:这个属性表示当前业务必须存在事务的处理,如果没有,就会自动创建一个新的事务(这个属性值在开发中属于常用,并且也是Spring默认处理的传播属性)
  • SUPPORTS:如果现在有事务,那么当前业务就直接使用事务进行处理,如果没有,就以非事务的方式运行
  • MANDATORY:如果当前开启了一个事务,就会以事务的方式运行,如果没有事务,就会抛出相应的异常
  • REQUIRES_NEW:这个属性总是会开启新的事务,如果在开启新事务之前已经有了一个事务,就会将这个事务挂起
  • NOT_SUPPORTED:总是以非事务的方式处理业务,如果当前有事务,就会将当前事务挂起
  • NEVER:总是以非事务的方式处理业务,如果当前有事务,则会抛出相应的异常
  • NESTED:如果当前有一个事务正在进行业务处理,那么这个属性值就会再次开启一个事务,内嵌进已经存在的事务之中,如果当前没有事务,则会以REQUIRED的方式进行处理

本文地址:https://blog.csdn.net/weixin_46468474/article/details/107647359