Spring中的IOC和AOP以及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的方式进行处理

猜你喜欢

转载自blog.csdn.net/weixin_46468474/article/details/107647359