Spring5学习

1.IOC容器

控制反转:把对象的创建和对象之间的调用过程都交给Spring进行管理,目的是为了降低耦合度;

在这里插入图片描述

IOC思想基于IOC容器完成,IOC容器底层就是对象工厂

建议放到容器中的对象:dao类,service类,controller类,工具类,spring容器中的对象都是单例,容器中每个对象只存在一个;

不建议放到容器中的对象:

  • 实体类对象,实体类对象的数据来自数据库,应该是在程序运行过程中创建
  • servlet,filter,listener,这些类应该由tomcat负责创建与调用

将对象放到容器中有两种方式

  • 在xml配置中添加bean标签
  • 使用注解

2.Bean标签创建容器对象

通过容器使用对象:

  • 在resources目录下配置mybeans.xml
<?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">

<!--  beans是根标签,spring把java对象称为bean
      spring-beans.xsd是一种约束文件和mybatis的.dtd约束一样
  -->
    <!--  告诉Spring创建对象,声明bean,
      id:对象的自定义名称,唯一值,spring通过这个名称找到对象
      class:类的全限定名称
      spring是把创建好的对象放到map中 springMap.put("WarMovie",new WarMovieInterImpl())
      一个bean标签对应一个对象
      -->
    <bean id="WarMovie" class="com.ywxk.spring5.service.impl.WarMovieInterImpl"></bean>
    <!--  创建一个非自定义类  -->
    <bean id="myDate" class="java.util.Date"></bean>
</beans>
  • 创建spring容器,通过容器获取对象
//从类路径target/classes下加载xml文件,创建容器,ApplicationContext就是spring容器
//在创建完spring容器后,spring会创建配置文件中的所有对象,默认调用的是类的无参构造
        ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
        //从容器中获取某个类
        WarMovieInterImpl warMovie =context.getBean("WarMovie",WarMovieInterImpl.class);
        warMovie.seeMovie();
//获取非自定义类
Date date=(Date)context.getBean("myDate");

3.依赖注入(Dependency Injection)DI

bean实例在调用无参数构造方法创建对象后,就要对bean对象的属性进行初始化,初始化是由容器完成的,称为依赖注入,根据注入的方式分为set注入,构造注入;

set注入(设值注入):spring调用类的set方法,在set方法可以实现属性的赋值,实际开发中80%都是使用该方式;

//简单类型
<bean id="director" class="com.ywxk.spring5.service.T1.Director">
    <!-- 为类注入属性,spring会调用对象的setXXX方法 -->
    <property name="name" value="路阳"></property>
    <property name="age" value="35"></property>
</bean>

//引用类型,property里面用ref="beanid"
<bean id="company" class="com.ywxk.spring5.service.T1.Company">
        <property name="addr" value="北京市"></property>
        <property name="companyName" value="北京文化"></property>
        <property name="registerMoney" value="2000000"></property>
</bean>
<bean id="director" class="com.ywxk.spring5.service.T1.Director">
        <!-- 为类注入属性,spring会调用对象的setXXX方法 -->
        <property name="name" value="路阳"></property>
        <property name="age" value="35"></property>
        <property name="nick" value="烧水水"></property>
        <property name="com" ref="company"></property>
</bean>

构造注入:spring调用类的有参构造方法,创建对象,在构造方法中完成属性的赋值;

<bean class="com.ywxk.spring5.service.T1.Player" id="player">
        <!-- 一个<constructor-arg>表示一个构造方法的一个参数,
            name:构造方法的形参名或者index:构造方法参数的位置,从0开始
            value:构造方法形参的值
            ref:当构造方法的形参类型是引用类型,使用ref
			也可以省略name跟index,但是value的顺序必须是按照形参的顺序
         -->
        <constructor-arg name="pName" value="刘德华"></constructor-arg>
        <constructor-arg index="1" value="53"></constructor-arg>
        <constructor-arg name="sex" value="true"></constructor-arg>
        <constructor-arg name="com" ref="company"></constructor-arg>
    </bean>
public Player(String pName, int age, boolean sex,Company com) {
    this.pName = pName;
    this.age = age;
    this.sex = sex;
    this.com= com;
}

用构造注入获取file对象

<bean name="myFile" class="java.io.File">
    <constructor-arg name="parent" value="C:\Users\Administrator\Desktop\临时"></constructor-arg>
    <constructor-arg name="child" value="aa.txt"></constructor-arg>
</bean>

4.引用类型的自动注入

实际开发中可能会碰到一个类里面有多个引用属性,如果逐个配置标签会导致代码冗余,所以spring提供了一种自动注入引用类型的功能,该功能只能为引用属性自动赋值;

  • byName(按名称)方式:如果java类中引用类型的属性名与bean中的类的id一致,且数据类型一致,那么spring就能对该属性进行自动注入

    <bean id="WarMovie" class="com.ywxk.spring5.service.impl.WarMovieInterImpl" autowire="byName"></bean>
    
    public class WarMovieInterImpl implements MovieInter {
    
        private Player player;
        private Director director;
        ...
    }
    
    <bean id="director" class="com.ywxk.spring5.service.T1.Director">
        <property name="name" value="路阳"></property>
        <property name="age" value="35"></property>
        <property name="nick" value="烧水水"></property>
        <property name="com" ref="company"></property>
    </bean>
    <bean id="player" class="com.ywxk.spring5.service.T1.Player" >
        <constructor-arg  value="刘德华"></constructor-arg>
        <constructor-arg index="1" value="53"></constructor-arg>
        <constructor-arg  value="true"></constructor-arg>
        <constructor-arg  ref="company"></constructor-arg>
    </bean>
    
  • byType(按类型)方式:如果java类中引用类型的属性类型与bean中的类的class是同源的,那么spring就能对该属性进行自动注入

    这里的同源指的是:

    • java类中引用类型的属性类型与bean中的类的class同一个类;
    • java类中引用类型的属性类型是bean中的类的class的父类;
    • java类中引用类型的属性类型与bean中的类的class是接口与实现类的关系;
<bean id="WarMovie" class="com.ywxk.spring5.service.impl.WarMovieInterImpl" autowire="byType"></bean>
<bean id="playerSon" class="com.ywxk.spring5.service.T1.PlayerSon" >
    <constructor-arg  value="刘德华"></constructor-arg>
    <constructor-arg index="1" value="53"></constructor-arg>
    <constructor-arg  value="true"></constructor-arg>
    <constructor-arg  ref="company"></constructor-arg>
</bean>
public class WarMovieInterImpl implements MovieInter {

    private Player player;
    ...
}
public class PlayerSon extends Player{

    public PlayerSon(String pName, int age, boolean sex, Company com) {
        super(pName, age, sex, com);
        System.out.println("player子类");
    }
}

5.多配置文件

如果项目较为庞大,可以使用多个配置文件

  • 可以按功能模块,一个模块一个配置文件
  • 按类的功能,数据库相关一个配置文件,事物功能一个配置文件,service功能一个配置文件…
<!--  主配置文件,引入其他模块配置文件  -->
    <import resource="classpath:package1/moviePackage1.xml"></import>
    <import resource="companyPackage1.xml"></import>
<!--  也可以用通配符方式,注意不要匹配到主配置文件,否则会造成死循环,通配符只能匹配当前目录的文件,不能匹配当前目录的子目录的文件  -->
    <import resource="classpath:package1/package-*.xml"></import>
<!--companyPackage1.xml,公司模块所有bean-->
<bean id="companyPackage1" class="com.ywxk.spring5.package1.CompanyPackage1"></bean>
<!--moviePackage1.xml  movie模块所有bean  -->
    <bean id="moviePackage1" class="com.ywxk.spring5.package1.MoviePackage1"></bean>

6.注解方式的依赖注入

6.注解方式创建容器对象与属性赋值

注解方式创建容器对象的步骤

  • 在类上添加注解
/**
 * @Component创建对象,等同于bean功能
 * 等同于<bean id="movieP2" class="com.ywxk.spring5.package2.MovieP2" ></bean>
 * value是对象的名称,相当于bean的id,在整个spring容器中值唯一
 */
@Component(value = "movieP2")
//@Component  注解如果没有指定bean的名字,默认为小写开头的类名
public class MovieP2 {

    private String mName;
    private int mYear;
    private  String mInfo;
    ...
}
  • 在配置文件中添加组件扫描器
<!--  声明组件扫描器,java中的组件就是类
    base-package:组件所在的包名,组件扫描器会去这个包以及这个包下面的所有子包中查找注解类,再根据注解创建对象或者给属性赋值
	指定多个包可以写多个component-scan标签,也可以在一个标签中用分号或者逗号分隔
	或者指定一个父包也可以,因为扫描器扫的是指定包及子包下的所有类,尽量精确,否则影响程序运行效率
  -->
    <context:component-scan base-package="com.ywxk.spring5.package2"></context:component-scan>
<!--    <context:component-scan base-package="com.ywxk.spring5.package1"></context:component-scan>-->
<!--    <context:component-scan base-package="com.ywxk.spring5.package2;com.ywxk.spring5.package1"></context:component-scan>-->
<!--    <context:component-scan base-package="com.ywxk.spring5.package2,com.ywxk.spring5.package1"></context:component-scan>-->
<!--    <context:component-scan base-package="com.ywxk.spring5"></context:component-scan>-->

spring中和@Component功能类似的,创建对象的注解还有:

  • @Repository(用在持久层的类上面):放在dao的实现类上面,表示创建dao对象,dao对象是能访问数据库的;
  • @Service(用在业务层类的上面):放在service的实现类上面,创建service对象,service对象是做业务处理的,可以有事物等功能的;
  • @Controller(用在控制器层的上面):放在controller类的上面,创建控制器对象的,控制器对象能够接受用户提交的数据,显示请求的处理结果;

以上3个注解的使用语法、功能和@Component一样,但是他们有额外的含义,是给项目对象分层的;

当一个类同时不属于持久层、业务层、控制器层的时候,这个类可以用@Component注解

注解方式给属性赋值

可以省略set方法

@Component
public class PlayerP2 {

    //在属性上方加@Value,推荐使用
    @Value("黄渤")
    private String pName;
    @Value("23")
    private int pAge;
    @Value("ggg")
    private String pNick;
    
    /**
     * @Autowired是给引用类型赋值的注解,使用的是自动注入原理,有byName与byType两种方式
     * 默认使用的是byType
     * @Autowired的属性required,默认为true,表示如果引用类型赋值失败,则程序报错,终止运行
     * required=false表示如果引用类型赋值失败,则将引用类型赋值为null
     * 推荐使用required=true
     */
    @Autowired
    private PlayerP2 playerP2;
    
    /**
     * @Autowired 的byName方式
     * 添加@Qualifier,value=“bean的id”
     */
    @Autowired
    @Qualifier("myPlayer")
    private PlayerP2 playerP2;
    
    /**
     * @Resource 给引用类型添加注解,用法与@Autowired相似,但是它是JDK中的注解,JDK11以上使用此注解需添加依赖,默认是byName方式
     */
    @Resource(name="bean的id")
    private PlayerP2 playerP2;
    
    //也可以在set方法上设置,很少用这个方式
	@Value("ggg")
    public void setpNick(String pNick) {
        this.pNick = pNick;
    }
}

注解与配置文件相结合的方式:

//配置文件配置key=value
player=myPlayer

//容器配置文件添引入配置文件
<context:property-placeholder location="classpath:annoTest1.properties"></context:property-placeholder>

//在使用注解的地方使用${key}方式引入
@Component("${player}")
public class PlayerP2 {...}

bean标签方式与注解方式的对比:

  • bean标签方式更加符合OCP原则,耦合度更低,代码繁琐
  • 注解方式代码更简洁,耦合度高,直接在类或者属性上方,方便阅读代码

7.动态代理

创建接口

public interface MovieProxyInter {

    public void  seeMovie();

    public void buyTicket();
}

实现接口

public class MovieProxy implements MovieProxyInter{

    @Override
    public void  seeMovie(){
        System.out.println("看电影");
    }

    @Override
    public void buyTicket(){
        System.out.println("买电影票");
    }
}

创建代理类

public class MyinvocationHandle implements InvocationHandler {

    private Object target;

    public MyinvocationHandle(Object target){
        this.target=target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object res=null;
        System.out.println("执行了invoke方法");
        if("seeMovie".equals(method.getName())){

            //在执行目标方法之前调用记录日志方法
            Util.doLog();
            //target是目标方法所属的对象,args是对象的各个方法
            res=method.invoke(target,args);

            //在执行目标方法之后执行其他方法
            Util.doCommit();
        }else{
            res=method.invoke(target,args);
        }
        return res;
    }
}

动态代理调用方法

@Test
public void test7(){
    MovieProxyInter mp=new MovieProxy();
    InvocationHandler handle=new MyinvocationHandle(mp);
    MovieProxyInter proxy=(MovieProxyInter)Proxy.newProxyInstance(mp.getClass().getClassLoader(),mp.getClass().getInterfaces(),handle);
    proxy.seeMovie();
    proxy.buyTicket();
}

动态代理的实现方式

  • JDK动态代理:使用JDK中的Proxy,Method,InvocationHandler创建代理对象,动态代理要求目标类必须实现接口
  • cglib动态代理:第三方的工具库,创建代理对象,原理是继承,通过继承目标类,创建子类,子类就是代理对象,要求目标类不能是final的,子类也不能是final的

当类实现了接口spring就会自动使用jdk动态代理,当类没有实现接口,则spring会自动使用cglib动态代理
动态代理的作用:

  • 在目标类源代码不改变的情况下,增加功能;
  • 减少代码的重复;
  • 让目标类专注业务逻辑代码
  • 让业务功能与日志、事物等非业务功能分离开来,解耦合

8.AOP面向切面编程

实际开发中很多方法可能都要执行相同的部分功能,例如记录日志、判断权限等,如果每个业务方法都写上这些功能,那么代码将会变得冗余,此时我们可以将这些业务方法中相同功能的代码切出来,形成块,例如日志功能一块,权限判断功能一块,在这些功能块中匹配业务方法,被匹配到的业务方法在执行的时候就会自动注入这些块功能,业务方法只需要编写业务代码,而不用每个业务功能都写一遍相同功能的代码,达到解耦的目的,这种方式即是面向切面编程;

aop就是动态代理的规范化,把动态代理的实现步骤,方式都定义好,让开发人员统一使用一种方式实现动态代理;
在这里插入图片描述

术语:

  • Aspect:切面,切面就是指把通知应用到切入点的过程;
  • JoinPoint:连接点,表示允许使用增强的地方、位置,比如切面的前、后、异常位置;
  • Pointcut:切入点,被execution表达式匹配到的目标方法,就是实际被增强的方法;
  • 目标对象:被代理的类就目标对象;
  • Advice:通知,切面在某个具体的连接点采取的行为或行动,称为通知,例如日志记录、权限判断功能等,切面的核心逻辑代码都写在通知中,通知是切面功能的具体实现,通常是业务代码以外的需求,如日志、验证等,这些被模块化的特殊对象;

切面的功能代码、被增强的方法、切面的执行时机是切面的三要素;

使用场景:如果要批量的给一些方法添加一些功能、例如日志、权限判断功能,可以使用aop;

9.AspectJ

spring在内部实现了aop规范,主要是在事物处理时使用aop,我们在项目开发中很少使用spring的aop实现,因为spring的aop比较笨重、繁琐;

实际开发中通常使用aspectJ实现aop,aspectJ是一个开源的专门做aop的框架;

spring框架中集成了aspectj框架;

aspjectJ框架实现aop的两种方式:

  • xml配置文件,配置全局事务的时候使用
  • 注解,一般使用此方式

切面的执行时机Advice在aspectJ框架中使用注解表示:

  • @Before
  • @AfterReturning
  • @Around
  • @AfterThrowing
  • @After

也可以使用xml标签表示

AspectJ匹配切入点的表达式:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throw-pattern?)

modifiers-pattern:访问权限类型,可省略

ret-type-pattern:返回值类型,不可省略

declaring-type-pattern:包名类名,可省略

name-pattern(param-pattern):方法名(参数类型和参数个数),不可省略

throw-pattern:抛出异常类型,可省略

?表示可选

以上表达式共4个部分 execution(访问权限 方法返回值 方法的声明(参数) 异常类型)

各部分之间可以使用以下符号

*表示0至多个任意字符

…用在方法参数中表示任意多个参数,用在包名后表示当前包及其子包路径

+用在类名后表示当前类及其子类,用在接口后表示当前接口及其实现类

execution(public * *(..))  表示匹配任意公开方法
execution(* set*(..)) 表示匹配所有方法名以set开头的方法
execution(* com.xyz.service.*.*(..)) 表示匹配在com.xyz.service包中的所有类的所有方法
execution(* com.xyz.service..*.*(..)) 表示匹配在com.xyz.service包及其子包中的所有类的所有方法
execution(* *..service.*.*(..)) 表示匹配所有包下的service子包下的所有类的所有方法
execution(* *.service.*.*(..)) 表示匹配所有一级包下的service子包下的所有类中的所有方法

使用aspectJ实现aop的步骤

  • 添加aspectJ依赖

  • <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.13</version>
    </dependency>
    
  • 创建目标类,目标类实现的接口

  • public interface MovieJ1 {
    
        public void seeMovie(String name,String addr);
    }
    
    public class MovieJ1Impl implements MovieJ1 {
        @Override
        public void seeMovie(String name,String addr) {
            System.out.println(name+"在"+addr+"看电影了哦");
        }
    }
    
  • 编写目标要增强的功能

    • 创建切面类,在类的上面加入@Aspect

    • 在类中定义要增强的方法,在方法的上方加入通知注解,例如@Before

    • 指定切入点的匹配表达式

    • /**
       * @Aspect表示当前类是切面类
       */
      @Aspect
      public class AspectJ1 {
      
          /**
           * 定义实现切面功能的方法
           * 方法的定义要求:
           * 1.必须是公共方法
           * 2.不能有返回值
           * 3.如果方法有参数,参数类型不能是自定义的,有几个类型可以使用
           *
           * @Before:前置通知的注解
           * 属性value:切入点execution表达式
           * 在目标方法执行前执行
           * 不会改变目标方法的执行结果
           * 不会影响目标方法的执行
           */
          //@Before("execution(public void com.ywxk.spring5.aspectj.j1.MovieJ1Impl.seeMovie(String,String))")
          @Before("execution( * *..MovieJ1Impl.see*(..))")
          public void doLogBefore(){
              System.out.println("切面方法记录日志,在切入点执行之前执行");
          }
      }
      
  • 在spring中通过注解或者bean标签声明切面类,目标类,aspectj的自动代理生成器(用来完成代理对象的自动创建功能)

<!--声明目标类-->
<bean id="movieJ1" class="com.ywxk.spring5.aspectj.j1.MovieJ1Impl"></bean>

<!--  声明切面对象  -->
<bean id="aspectJ1" class="com.ywxk.spring5.aspectj.j1.AspectJ1"></bean>

<!--  声明自动代理生成器
    使用aspect框架内部的功能,创建目标对象的代理对象
    返回的是目标对象被修改后的代理对象
    aspectj-autoproxy会把容器中的所有对象,一次性都生成代理对象
    当目标类已经实现了接口,想要使用cglib动态代理,可以设置参数proxy-target-class="true"
  -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>-->

调用

```java
@Test
public void test8(){
    ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
    MovieJ1 mj1Proxy=(MovieJ1) context.getBean("movieJ1");
    mj1Proxy.seeMovie("某某某","大地影院");
}

10.通知(Advice)

通知的参数:JoinPoint,如果传入这个参数,这个参数必须放在参数列表的第一个位置;

@Before("execution(* *..MovieJ1Impl.*(..))")
public void doLogBefore2(JoinPoint joinPoint){
    //获取方法的标签(定义)
    System.out.println("方法的定义--"+joinPoint.getSignature());
    //获取方法的名称
    System.out.println("方法的名称--"+joinPoint.getSignature().getName());
    //获取方法的所有参数
    Object[] pointArgs=joinPoint.getArgs();
    for (Object arg:pointArgs) {
        System.out.println(arg);
    }
}

后置通知,有JoinPoint参数

/**
 * @AfterReturning:后置通知的注解,有两个属性值value,returning
 * value属性:execution表达式
 * returning属性:目标方法的返回值变量,变量名与通知方法的形参名相同
 * 方法必须是public void
 * 方法有参数 参数类型推荐是Object
 * 在目标方法执行之后执行此方法
 * 能够获取到目标方法的返回值 Object res,并进行修改,如果返回值是对象,对该返回值的修改会同步到目标方法的返回值,因为对象是引用传递
 * @param res
 */
@AfterReturning(value = "execution(* *..MovieJ1Impl.getDirector(..))",returning = "res")
public void doAfterReturning2(Object res){
    //res=(Director)res;
    ((Director) res).setName("叶问");
    System.out.println("x修改后director"+res);
}

@Override
public Director getDirector() {
	ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");
        Director d =(Director)context.getBean("director");
        return d;
}

环绕通知

@Override
public Object buyTicket(String name1,String name2) {
    System.out.println("目标方法执行了");
    return "买电影票";
}

    /**
     * @Around 环绕通知,在目标方法前后都能增强,能控制目标方法是否被执行,修改原来目标方法的执行结果,影响目标方法的调用结果,是功能最强的通知
     * 等同于JDK动态代理的InvocationHandler接口,ProceedingJoinPointc参数相当于InvocationHandler的Method参数
     * 必须是public
     * 必须有返回值,建议是Object
     * 方法有固定参数ProceedingJoinPoint,继承了JoinPoint
     * 环绕通知通常用来做事务操作,在目标方法执行前开启事务,在目标方法执行后提交事务
     */
    @Around("execution(* *..buyTicket(..))")
    public Object doArround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println(pjp.getSignature().getName());
        Object[] args= pjp.getArgs();
        for (Object s: args) {
            System.out.println(s);
        }
        System.out.println("在目标方法执行前执行某某功能");
        //控制目标方法的调用,相当于method.invoke()
        Object res=null;
        if(1==1){
            res=pjp.proceed();
        }
        System.out.println("在目标方法执行后执行某某功能");
        return  "修改了目标方法的返回值"+res;
    }

	@Test
    public void test10(){
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        MovieJ1 mj1Proxy=(MovieJ1) context.getBean("movieJ1");

        Object d =mj1Proxy.buyTicket("路人甲","路人乙");
        System.out.println(d);
    }

异常通知,有JoinPoint属性

@Override
public void exceptionTest() {
    //int a=10/0;
    System.out.println("目标方法异常通知演示");
}

	/**
     * @AfterThrowing异常通知
     * 必须是public void
     * 参数Exception,可选参数JoinPoint
     * 属性:
     *  value:execution表达式
     *  throwing:目标方法抛出的异常对象,变量名自定义,必须与方法中的接收参数名一致
     * 在目标方法抛出异常时执行
     * 可以用来做异常的监控程序,如果目标方法有异常,发送短信、邮件等
     * 原理是 try...catch(){myAfterThrowing()},在catch语句里面执行该方法
     * 实际开发中很少使用
     * @param ex
     */
    @AfterThrowing(value = "execution(* *..exceptionTest(..))",throwing = "ex")
    public void myAfterThrowing(Exception ex){

        System.out.println("目标方法抛出了异常"+ex);
    }

	@Test
    public void test11(){
        ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
        MovieJ1 mj1Proxy=(MovieJ1) context.getBean("movieJ1");
        mj1Proxy.exceptionTest();
    }

最终通知,有JoinPoint属性

@Override
public void finnalTest() {
    int a=10/0;
    System.out.println("目标方法演示最终通知");
}

	/**
     * @After:最终通知,总会在目标方法执行后执行,相当于try...catch...finnaly(myAfter()),在finnaly语句里面执行该方法
     * 一般做资源清除工作,实际开发中很少使用
     * 必须是public void
     */
    @After(value = "execution(* *..finnalTest(..))")
    public Object myAfter(){
        System.out.println("myafter执行了吗");
        return  123;
    }
	//测试方法参考上面代码

@PointCut定义切入点

@After(value = "myPointCut()")
public void myAfter(){
    System.out.println("myafter执行了吗");
}

@Before(value = "myPointCut()")
public void myBefore(){
    System.out.println("myBefore执行了吗");
}

/**
 * 当多个切入点表达式都指向同一个目标方法时,可以用@PointCut的切入点表达式指向这个目标方法,其他表达式引用@Pointcut注解的方法名即可
 * 这个方法一般是私有的,因为这个方法不需要被外部调用
 */
@Pointcut("execution(* *..finnalTest(..))")
private void myPointCut(){
    //无需代码
}

11.Spring整合Mybatis

步骤:

  • 添加spring依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.18</version>
</dependency>
  • 添加mybatis依赖
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>

  • 添加mysql驱动
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
</dependency>
  • 添加spring事务依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.3.18</version>
</dependency>
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.19</version>
</dependency>
  • 添加mybatis和spring的集成依赖,mybatis官方提供的,用来在spring项目中创建mybatis的SqlSessionFactory、dao对象
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
</dependency>
  • 创建实体类
public interface MovieDao {

    int insertMovie(Movie movie);
    List<Movie> selectMovie();
}
  • 创建dao接口和mapper文件
<mapper namespace="com.ywxk.spring.dao.MovieDao">
    <select id="selectMovie" resultType="com.ywxk.spring.domain.Movie">
        select * from y_mobie
    </select>

    <insert id="insertMovie">
        insert into y_movie(m_name,m_up_year,m_director,m_adddate)
        values (#{m_name},#{m_up_year},#{m_director},#{m_adddate})
    </insert>
</mapper>
  • 创建mybatis主配置文件
<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--  设置别名  -->
    <typeAliases>
        <package name="com.ywxk.spring.domain"/>
    </typeAliases>

    <mappers>
        <package name="com.ywxk.spring.dao"/>
    </mappers>

</configuration>
  • 创建Service接口和实现类
public class MovieServiceImpl implements MovieService {

    @Autowired
    private MovieDao movieDao;

    @Override
    public int addMovie(Movie movie) {
       return movieDao.insertMovie(movie);
    }

    @Override
    public List<Movie> selectMovie() {

        List<Movie> movies=movieDao.selectMovie();
        return movies;
    }
}
  • 创建srping配置文件,声明mybatis对象交给spring容器

    • 数据源对象
    • SqlSessionFactory
    • Dao对象
    • 声明自定义的service
    <!--  声明数据库配置文件路径  -->
        <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    
        <!--  声明数据源  -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            <property name="url" value="${jdbc.url}"></property>
            <property name="username" value="${jdbc.username}"></property>
            <property name="password" value="${jdbc.password}"></property>
            <property name="maxActive" value="${jdbc.maxActive}"></property>
        </bean>
    
    <!--  声明mybatis的SqlSessionFactoryBean,spring用这个类创建SqlSessionFactory对象  -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--    引用类型set注入连接池    -->
        <property name="dataSource" ref="dataSource"></property>
        <!--    configLocation属性比较特殊,用value指定值    -->
        <property name="configLocation" value="classpath:mybatis.xml"></property>
    </bean>
    
    <!--  mybatis提供的,他会在内部调用getMapper,生成每个dao接口的代理对象放入spring容器中使用
            需要传入两个值,sqlSessionFactory的id,dao接口的包路径
      -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
        <property name="basePackage" value="com.ywxk.spring.dao"></property>
    </bean>
    
    <!--  声明service  -->
        <bean id="movieService" class="com.ywxk.spring.service.impl.MovieServiceImpl"></bean>
    
  • 创建测试类,获取Service对象,通过service调用dao完成数据库访问

@Test
public void test1(){
    ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");

    Movie movie=new Movie();
    movie.setM_name("星际穿越2");
    movie.setM_up_year(2014);
    movie.setM_director("克里斯托弗·诺兰2");
    movie.setM_adddate(new Date());
    MovieService movieService=(MovieService) context.getBean("movieService");
    //spring和mybatis整合之后,事务是自动提交的,无需手动commit
    int c=movieService.addMovie(movie);
    System.out.println("数据库影响条数"+c);
    List<Movie> movies=movieService.selectMovie();
        for (Movie m:movies) {
            System.out.println(m);
        }
}

12.spring框架处理事务

事务的操作需要执行多个dao方法,所以事务应该放在业务方法上执行;

不同的数据库、框架执行事务的方式不同,每个项目用不同的事务操作方法导致代码看起来混乱,且程序员的学习成本提高,不利于项目的管理;

所以spring提供了一种事务处理的统一模型,使用统一的步骤整合了多个数据库框架的事务访问;

spring采用了声明式事务,把事务相关的资源和内容提供给spring,spring就能处理事务提交与回滚等操作,几乎不需要写代码;

spring使用事务管理器来进行在内部进行事务的提交、回滚操作,事务管理器由一个接口和接口的众多实现类组成;

事务管理器接口:PlateformTransactionManager,定义了事务的重要方法,commit,rollback等

事务管理器接口实现类:

  • mybatis实现类:DataSourceTransactionManager
  • hibernate实现类:HibernateTransactionManager

使用方法:在applicationContext.xml中配置即可

<bean id="xxx” class="...DataSourceTransactionManager">

当业务方法执行成功,没有异常抛出,spring会在方法执行后提交事务,事务管理器自动commit;

当业务方法抛出运行时异常或error,spring会执行回滚,调用事务管理器的rollback;

当业务方法抛出非运行时异常,主要是受检异常时,提交事物;

@Transactional注解是Spring框架提供的注解,放在public方法上方,表示当前方法具有事物,事务的隔离级别、传播行为、异常信息等可以在属性中配置,适用于中小型项目;

步骤:

  • <!--  使用spring处理事物,声明事物管理器  -->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--    连接数据库,指定数据源    -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
     <!--    开启事务注解驱动,使用事务注解管理事务,transaction-manager="事务管理器id"    -->
        <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
    
  • @Override
    //sping默认遇到运行时异常才会回滚
    @Transactional(//如果存在rollbackFor属性指定的异常,一定会回滚)
    public void buy(int goodsId, int amount) {//事务代码}
    

13.aspectJ框架处理事务

在spring配置文件中声明类、方法需要的事务,使得业务方法和事务配置完全分离, 此方案适合大型项目,该方案的使用步骤全都在配置文件中完成;

步骤:

  • pom文件中添加aspectj框架依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.13</version>
</dependency>
  • applicationContext.xml中声明事务管理器
<!--  使用spring处理事物,声明事物管理器  -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--    连接数据库,指定数据源    -->
    <property name="dataSource" ref="dataSource"></property>
</bean>
  • 配置事务方法
<!--  声明需要事务的业务方法并配置属性
    id:该配置的id,唯一标识
  -->
<tx:advice id="myAdvice" transaction-manager="dataSourceTransactionManager">
<!--    配置事务属性    -->
    <tx:attributes>
        <!--      name:配置事务的完整方法名,不带包和类,也可以使用通配符      -->
        <tx:method name="buy*" />
        <tx:method name="add*"></tx:method>
        <tx:method name="update*"></tx:method>
    </tx:attributes>
</tx:advice>
  • 配置需要AOP代理类,关联事务方法
<!--  配置切入点表达式规则,指定需要切面的类和方法,然后关联事务方法  -->
<aop:config>
    <!--   id:指定切入点(实际被增强的方法)的id,expression:excution表达式 ,指定需要进行事务的类    -->
    <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>

    <!--  关联事务方法与切入点  -->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"></aop:advisor>
</aop:config>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

y_w_x_k

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值