Spring框架详解

目录

什么是Spring框架

Spring的特点

什么是IOC

基于xml的IOC:

基于注解的IOC:

添加包扫描的方式

为应用指定多个 Spring 配置文件

spring配置文件的整合:

基于xml的IOC的项目案例:

基于注解的IOC的项目案例:

补充:基于xml的IOC的引用类型自动注入:

面向切面编程AOP:

手写模拟AOP框架:

Spring的AOP通知类型(了解)

Spring原生AOP:

AOP常用的术语

什么是AspectJ框架:

AspectJ常见通知类型

AspectJ 的切入点表达式(掌握)

AspectJ的前置通知@Before:

AspectJ框架切换JDK动态代理和CGLib动态代理:

切面方法的形参:

AspectJ的后置通知@AfterReturning:

AspectJ的环绕通知@Around:

AspectJ的最终通知@After:

给切入点表达式起别名@Pointcut:

SM整合的步骤

Spring的两种事务处理方式

基于注解式的事务添加步骤(变成动态代理类型)

@Transactional注解参数详解

Spring中事务的五大隔离级别

为什么添加事务管理器

Spring事务的传播特性

基于声明式的事务添加:

补充:在声明式事务的基础上为某一个方法提供单独的事务管理:


什么是Spring框架

        它是一个容器。它是整合其它框架的框架,它的核心是IOC(控制反转)和AOP(面向切面编程)。它由20多个模块构成,在很多领域都提供优秀的解决方案。

Spring的特点

(1)轻量级。
        由20多个模块构成,每个jar包都很小,小于1M,核心包也就3M左右。且对代码无污染。(对代码的约束越多污染越大,maven对代码就有污染,但利大于弊)

(2)面向接口编程。
        使用接口,面向灵活,项目的可扩展性、可维护性都极高。因为接口不关心实现类的类型,使用时接口指向实现类,切换实现类即可切换整个功能。

(3)AOP:面向切面编程。
        就是将公共的、通用的、重复的代码单独开发,在需要的时候调用。底层的原理是动态代理。如输出语句。

(4)整合其它框架。
        它整合了众多优秀的框架,使得其它框架更易用。

什么是IOC

        控制反转IOC(Inversion of Control)是一个概念、思想。即由Spring容器进行对象的创建和依赖注入,程序员在使用时直接取出使用。

正转:由程序员进行对象的创建和依赖注入称为正转。程序员说了算。

  Student stu = new Student();   ===>程序员创建对象
  stu.setName("张三");           ===>程序员进行赋值
  stu.setAge(22);

反转:由Spring容器创建对象和依赖注入称为反转,将控制权从程序员手中夺走,由给Spring容器,称为反转。 容器说了算。

  <bean id="stu" class="com.bjpowernode.pojo.Student">     ===>Spring容器负责对象的创建
    <property name="name" value="张三">                    ===>Spring容器依赖注入值
    <property name="age" value="22"> 
  </bean>

基于xml的IOC:

(1)创建对象:

applicationContext.xml:

  <bean id="stu" class="com.bjpowernode.pojo.Student"></bean>

测试类: 

package com.bjpowernode.test;

import com.bjpowernode.pojo.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 */
public class MyTest1 {

    @Test
    public void testStudent(){
        // 程序员创建对象
//        Student stu = new Student();
//        System.out.println(stu);
    }

    @Test
    public void testStudentSprng(){
        // 由spring容器进行对象的创建

        // 如果想从spring容器中取出对象,则要先创建容器对象,并启动才可以取对象.
        // ClassPathXmlApplicationContext会创建容器对象并自动启动
        // 路径从resources包开始
        ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
        // 取出对象
        // 取对象需要类型强制转换
        // 实参为对象名称
        Student stu = (Student) ac.getBean("stu");
        System.out.println(stu);
    }
}

文件位置:

(2)给创建的对象赋值:

<?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">

    <!--创建学生对象
      等同于 Student stu = new Student();
      id:就是创建的对象的名称
      class:就是创建的对象的类型,底层通过反射构建对象

      当容器一启动,对象创建的代码就会执行,即<bean>标签
    -->
    <bean id="stu" class="com.bjpowernode.pojo.Student">
        <property name="name" value="张三"></property>
        <property name="age" value="21"></property>
<!--        value都是用双引号括起来的,即便成员变量是int类型    -->
    </bean>

</beans>

A、赋值之使用setter注入:

        注入分为简单类型注入和引用类型注入,简单类型注入值使用value属性,引用类型注入值使用ref属性。(String、包装类也用value)

        必须要注意:使用setter注入必须提供无参的构造方法,必须提供setXXX()方法。

<?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">


    <!--创建学校对象-->
    <bean id="school" class="com.bjpowernode.pojo2.School">
        <property name="name" value="清华大学"></property>
        <property name="address" value="海淀区"></property>
    </bean>

    <!--创建学生对象-->    
    <bean id="stu" class="com.bjpowernode.pojo2.Student">
        <property name="age" value="22"></property>
        <property name="school" ref="school"></property>
<!--
        // xml文件中创建了的对象都可以直接被引用(ref="school",school为bean标签的id);
        // 一个bean表示一个对象,具体给school赋值为哪个对象自己选
        // 可以只给部分属性赋值
        // 创建bean的顺序没有要求
-->
    </bean>

</beans>

B.使用构造方法注入:

        同setting注入一样,分为简单类型注入和引用类型注入,简单类型注入值使用value属性,引用类型注入值使用ref属性。(String、包装类也用value)

使用构造方法注入有:根据参数名称注入、根据参数下标注入、根据默认参数顺序注入。

<?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">


    <!--创建学校的对象,使用构造方法参数名称注入值-->
    <bean id="school" class="com.bjpowernode.pojo3.School">
        <constructor-arg name="address1" value="海淀区"></constructor-arg>
        <constructor-arg name="name1" value="清华大学"></constructor-arg>
        <!--
            这里使用的是构造方法的参数名称注入值,所以name标签需与构造方法的形参定义的变量名一致,
            不需要与类中成员变量的名称一致
        -->
    </bean>

    <!--创建学生对象,使用构造方法的参数的下标注入值-->
    <bean id="stu" class="com.bjpowernode.pojo3.Student">
        <constructor-arg index="0" value="钱七"></constructor-arg>
        <constructor-arg index="2" ref="school"></constructor-arg>
        <constructor-arg index="1" value="22"></constructor-arg>
<!--        顺序可乱-->
    </bean>

    <!--创建学生对象,使用默认的构造方法的参数顺序-->
    <bean id="stuSequence" class="com.bjpowernode.pojo3.Student">
        <constructor-arg value="陈十"></constructor-arg>
        <constructor-arg value="22"></constructor-arg>
        <constructor-arg ref="school"></constructor-arg>
<!--        顺序不可乱-->
    </bean>

</beans>

基于注解的IOC:

        也称为DI(Dependency Injection),它是IOC的具体实现的技术。


(1)创建对象的注解:

@Component:可以创建任意对象。创建的对象的默认名称是类名的驼峰命名法(小驼峰,首字母小写),我们在用getBean()方法取对象时必须首字母小写,如果使用大驼峰则会报错。也可以指定对象的名称@Component("指定名称")。

@Controller:专门用来创建控制器的对象(Servlet),这种对象可以接收用户的请求,可以返回处理结果给客户端。

@Service:专门用来创建业务逻辑层的对象,负责向下访问数据访问层,处理完毕后的结果返回给界面层。

@Repository:专门用来创建数据访问层的对象,负责数据库中的增删改查所有操作。

@Component("stu")  //交给Spring去创建对象,就是在容器启动时创建
public class Student {

}

注意,基于注解的IOC,必须要在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"
       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">

    <!--添加包扫描,如果添加了注解但没有被扫描到依旧不会被创建对象-->
    <!--路径从Java文件夹下开始-->
    <context:component-scan base-package="com.bjpowernode.s01"></context:component-scan>
</beans>


(2)依赖注入的注解:

简单类型(8种基本类型+String)的注入:

        @Value:用来给简单类型注入值。

    @Value("张三")
    private String name;
    @Value("22")
    private int age;

引用类型的注入:

        A、@Autowired:使用类型注入值,从整个Bean工厂中搜索同源类型的对象进行注入。

        什么是同源类型:

                a、被注入的类型(Student中的school)与注入的类型是完全相同的类型(同一个类)

                b、被注入的类型(Student中的school)与注入的类型是父子类(被注入的为父,注入的为子)(比他小才能注入进去)

                c、被注入的类型(Student中的school)与注入的类型是接口和实现类的类型(被注入的为接口,注入的为实现类)

    //引用类型按类型注入,这个关键字会去Spring的bean工厂中找有无school的对象创建,有就注入,没有就报错
    @Autowired
    private School school;

        注意:在有父子类的情况下,使用按类型注入,就意味着有多个可注入的对象。此时按照变量名称进行二次筛选,选中与变量相同名称的对象进行注入。

        但在三层架构中,每一层的(主)对象都只有一个而已,对这一层的所有操作都是通过这个(主)对象完成的。

        B、@Autowired+@Qualifier("名称"):使用名称注入值,从整个Bean工厂中搜索相同名称的对象进行注入。

        如果一个容器有一个以上的匹配的bean,则可以通过@Qualifier注解准确找指定的Bean。

    //引用类型按名称注入,单独@Qualifier无法注入
    @Autowired
    @Qualifier("schoolNew")
    private School school;

        注意:如果有父子类的情况下,按名称进行注入值更准确安全。

添加包扫描的方式

(1)单个包扫描(推荐使用):

    <context:component-scan base-package="com.bjpowernode.controller"></context:component-scan>
    <context:component-scan base-package="com.bjpowernode.service.impl"></context:component-scan>
    <context:component-scan base-package="com.bjpowernode.dao"></context:component-scan>

(2)多个包扫描,多个包之间以逗号或空格或分号分隔:

    <context:component-scan base-package="com.bjpowernode.controller ,com.bjpowernode.service ,com.bjpowernode.dao"></context:component-scan>

(3)扫描根包(不推荐)

    <context:component-scan base-package="com.bjpowernode"></context:component-scan>

        会降低容器启动的速度,导致多做无用功,效率较低。

为应用指定多个 Spring 配置文件

当项目越来越大,需要多人合作开发,一个配置就存在很大隐患。

拆分配置文件的策略:

A、按层拆:

      applicationContext_controller.xml 中:       
        <bean id="uController" class="com.bjpowernode.controller.UsersController">
        <bean id="bController" class="com.bjpowernode.controller.BookController">

      applicationContext_service.xml 中:       
        <bean id="uService" class="com.bjpowernode.controller.UsersService">
        <bean id="bService" class="com.bjpowernode.controller.BookService">

      applicationContext_mapper.xml 中:       
        <bean id="uMapper" class="com.bjpowernode.controller.UsersMapper">
        <bean id="bMapper" class="com.bjpowernode.controller.BookMapper">

B、按功能拆:

      applicationContext_users.xml 中:       
        <bean id="uController" class="com.bjpowernode.controller.UsersController">
        <bean id="uService" class="com.bjpowernode.controller.UsersService">
        <bean id="uMapper" class="com.bjpowernode.controller.UsersMapper">
      applicationContext_book.xml 中:     
        <bean id="bController" class="com.bjpowernode.controller.BookController">
        <bean id="bService" class="com.bjpowernode.controller.BookService">
        <bean id="bMapper" class="com.bjpowernode.controller.BookMapper">

spring配置文件的整合:

因为我们在《基于xml的IOC》中,需要对配置文件进行读取,如果拆分后,那么就需要多次读取代码,为了简便我们的操作,我们可以对配置文件进行一个整合。

<!--    因为我们在创建容器并启动时,需要传一个参数,
    ApplicationContext ac = new ClassPathXmlApplicationContext("total.xml");
    但我们已经把配置文件分为了三个,那么在这里可以做一个整合    -->

   <!--单个导入-->
    <!--<import resource="applicatoinContext_mapper.xml"></import>-->
    <!--<import resource="applicatoinContext_service.xml"></import>-->
    <!--<import resource="applicatoinContext_controller.xml"></import>-->
    
    <!--批量导入-->
    <import resource="applicatoinContext_*.xml"></import>

整合后只要注册用于整合的xml文件,其他文件也会被注册。

基于xml的IOC的项目案例:

        使用三层架构进行用户的插入操作。

项目结构:

  实体类
  com.bjpowernode.pojo  Users

  数据访问层
  com.bjpowernode.dao   UsersMapper.java(接口)
                                          UsersMapperImpl.java(实现类)

  业务逻辑层
  com.bjpowernode.service   UsersService.java(接口)
                                               UsersServiceImpl.java(实现类 )

  界面层
  com.bjpowernode.controller  UsersController.java

实体类:

package com.bjpowernode.pojo;

/**
 *
 */
public class Users {
    private int uid;
    private String uname;
    private int uage;

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }

    public int getUage() {
        return uage;
    }

    public void setUage(int uage) {
        this.uage = uage;
    }

    public Users(int uid, String uname, int uage) {
        this.uid = uid;
        this.uname = uname;
        this.uage = uage;
    }

    public Users() {
    }

    @Override
    public String toString() {
        return "Users{" +
                "uid=" + uid +
                ", uname='" + uname + '\'' +
                ", uage=" + uage +
                '}';
    }
}

UsersController类:

package com.bjpowernode.controller;

import com.bjpowernode.pojo.Users;
import com.bjpowernode.service.UsersService;
import com.bjpowernode.service.impl.UsersServiceImpl;

/**
 *  界面层
 */
public class UsersController {

    //如何去访问业务逻辑层,就是创建对象
    //切记切记:所有的界面层都会有业务逻辑层的对象
    public UsersService usersService ; //= new UsersServiceImpl();

    //交给Spring去注入值,必须提供setXXX()方法

    public void setUsersService(UsersService usersService) {
        this.usersService = usersService;
    }

    //界成层的功能实现,对外提供访问的功能
    public int insert(Users users){
        return usersService.insert(users);
    }
}

 界面层是最上面的一层,没有接口。

UsersService接口

package com.bjpowernode.service;

import com.bjpowernode.pojo.Users;

/**
 *
 */
public interface UsersService {

    //增加用户
    int insert(Users users);
}

UsersServiceImpl类

package com.bjpowernode.service.impl;

import com.bjpowernode.dao.UsersMapperImpl;
import com.bjpowernode.dao.UsersMappr;
import com.bjpowernode.pojo.Users;
import com.bjpowernode.service.UsersService;

/**
 *  业务逻辑层的实现类
 */
public class UsersServiceImpl implements UsersService {

    //切记切记:在所有的业务逻辑层中都必定有数据访问层的对象
    private UsersMappr usersMappr ;//= new UsersMapperImpl();

    //交给Spring去依赖注入值,必须提供setXXX()方法

    public void setUsersMappr(UsersMappr usersMappr) {
        this.usersMappr = usersMappr;
    }

    @Override
    public int insert(Users users) {
        //添加更复杂的业务,但是我们现在没有复杂业务
        return usersMappr.insert(users);
    }
}

UsersMappr接口

package com.bjpowernode.dao;

import com.bjpowernode.pojo.Users;

/**
 *
 */
public interface UsersMappr {
    //增加用户
    int insert(Users u );
}

UsersMapperImpl类

package com.bjpowernode.dao;

import com.bjpowernode.pojo.Users;

/**
 *  数据访问层的实现类
 */
public class UsersMapperImpl implements UsersMappr {
    @Override
    public int insert(Users u) {
        System.out.println(u.getUname()+"用户增加成功!");
        return 1;
    }
}

applicatoinContext_controller.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">

    <!--创建界面层的对象-->
    <bean id="uController" class="com.bjpowernode.controller.UsersController">
        <property name="usersService"  ref="uService"></property>
    </bean>
</beans>

applicatoinContext_service.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">


    <!--创建业务逻辑层的对象-->
    <bean id="uService" class="com.bjpowernode.service.impl.UsersServiceImpl">
        <property name="usersMappr" ref="uMapper"></property>
        <!--ref在这里不能识别uMapper对象,会标红显示错误,但他是存在的,运行时不会报错-->
    </bean>


</beans>

applicatoinContext_mapper.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">

    <!--创建各种对象-->
    <!--创建数据访问层的对象-->
    <bean id="uMapper" class="com.bjpowernode.dao.UsersMapperImpl">
    </bean>

</beans>

total.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">

    <!--批量导入-->
    <import resource="applicatoinContext_*.xml"></import>
</beans>

测试类:

package com.bjpowernode.test;

import com.bjpowernode.controller.UsersController;
import com.bjpowernode.pojo.Users;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 */
public class MyTest {
    @Test
    public void testInsertUsers(){
        //创建容器并启动
        ApplicationContext ac = new ClassPathXmlApplicationContext("total.xml");
        //取出对象
        UsersController usersController = (UsersController) ac.getBean("uController");
        //测试功能
        int num = usersController.insert(new Users(200,"王五",24));
        System.out.println(num);
    }
}

基于注解的IOC的项目案例:

        使用三层架构进行用户的插入操作。

项目结构:

实体类
  com.bjpowernode.pojo  Users

数据访问层
  com.bjpowernode.dao   UsersMapper.java(接口)
                                          UsersMapperImpl.java(实现类)

业务逻辑层
  com.bjpowernode.service   UsersService.java(接口)
                                               UsersServiceImpl.java(实现类 )

界面层
  com.bjpowernode.controller  UsersController.java

实体类:

package com.bjpowernode.pojo;

/**
 *
 */
public class Users {
    private int uid;
    private String uname;
    private int uage;

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }

    public int getUage() {
        return uage;
    }

    public void setUage(int uage) {
        this.uage = uage;
    }

    public Users(int uid, String uname, int uage) {
        this.uid = uid;
        this.uname = uname;
        this.uage = uage;
    }

    public Users() {
    }

    @Override
    public String toString() {
        return "Users{" +
                "uid=" + uid +
                ", uname='" + uname + '\'' +
                ", uage=" + uage +
                '}';
    }
}

UsersController类:

package com.bjpowernode.controller;

import com.bjpowernode.pojo.Users;
import com.bjpowernode.service.UsersService;
import com.bjpowernode.service.impl.UsersServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

/**
 *  界面层
 */
@Controller  //交给Spring去创建对象
public class UsersController {

    //如何去访问业务逻辑层,就是创建对象
    //切记切记:所有的界面层都会有业务逻辑层的对象
    @Autowired
    public UsersService usersService ;  //= new UsersServiceImpl();

    //界成层的功能实现,对外提供访问的功能
    public int insert(Users users){
        return usersService.insert(users);
    }
}

 界面层是最上面的一层,没有接口。

UsersService接口

package com.bjpowernode.service;

import com.bjpowernode.pojo.Users;

/**
 *
 */
public interface UsersService {

    //增加用户
    int insert(Users users);
}

UsersServiceImpl类

package com.bjpowernode.service.impl;

import com.bjpowernode.dao.UsersMapperImpl;
import com.bjpowernode.dao.UsersMappr;
import com.bjpowernode.pojo.Users;
import com.bjpowernode.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.xml.ws.ServiceMode;

/**
 *  业务逻辑层的实现类
 */
@Service  //交给spring创建业务逻辑层的对象
public class UsersServiceImpl implements UsersService {

    //切记切记:在所有的业务逻辑层中都必定有数据访问层的对象
    @Autowired
    private UsersMappr usersMappr;// = new UsersMapperImpl();

    @Override
    public int insert(Users users) {
        //添加更复杂的业务,但是我们现在没有复杂业务
        return usersMappr.insert(users);
    }
}

UsersMappr接口

package com.bjpowernode.dao;

import com.bjpowernode.pojo.Users;

/**
 *
 */
public interface UsersMappr {
    //增加用户
    int insert(Users u );
}

UsersMapperImpl类

package com.bjpowernode.dao;

import com.bjpowernode.pojo.Users;
import org.springframework.stereotype.Repository;

/**
 *  数据访问层的实现类
 */
@Repository  //就是交给spring框架去创建数据访问层的对象
public class UsersMapperImpl implements UsersMappr {
    @Override
    public int insert(Users u) {
        System.out.println(u.getUname()+"用户增加成功!");
        return 1;
    }
}

applicatoinContext_controller.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"
       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:component-scan base-package="com.bjpowernode.controller"></context:component-scan>
</beans>

applicatoinContext_service.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"
       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:component-scan base-package="com.bjpowernode.service"></context:component-scan>
</beans>

applicatoinContext_mapper.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"
       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:component-scan base-package="com.bjpowernode.dao"></context:component-scan>
</beans>

total.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">

    <!--批量导入-->
    <import resource="applicationContext_*.xml"></import>
</beans>

测试类:

package com.bjpowernode.test;

import com.bjpowernode.controller.UsersController;
import com.bjpowernode.pojo.Users;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 */
public class MyTest {
    @Test
    public void testInsertUsers(){
       //创建并启动容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("total.xml");
        //取出UsersController对象
        UsersController usersController = (UsersController) ac.getBean("usersController");
        usersController.insert(new Users(100,"haha",25));
    }
}

补充:基于xml的IOC的引用类型自动注入:

基于xml的IOC的引用类型也可以自动注入(类似于基于注解的方式)

相当于基于注解的IOC的@Autowired:

        查找同源的对象注入。

    <bean id="stu" class="com.bjpowernode.pojo2.Student" autowire="byType">
        <property name="name" value="李四"></property>
        <property name="age" value="22"></property>
<!--        <property name="school" ref="school"></property>-->
    </bean>

         school属性不用写,自动注入。

相当于基于注解的IOC的中的@Autowired+@Qualifier("名称"):

        默认查找与该类名的驼峰命名法(小驼峰)相同名称的对象,并注入。

    <bean id="stu" class="com.bjpowernode.pojo2.Student" autowire="byName">
        <property name="name" value="李四"></property>
        <property name="age" value="22"></property>
<!--        <property name="school" ref="school"></property>-->
    </bean>

         school属性不用写,自动注入。

面向切面编程AOP:

        AOP(Aspect Orient Programming),面向切面编程。

        切面:公共的,通用的,重复的功能称为切面,面向切面编程就是将切面提取出来,单独开发,在需要调用的方法中通过动态代理的方式进行织入。

手写模拟AOP框架:

业务:图书购买业务。

切面:事务、日志...

(1)第一个版本:业务和切面紧耦合在一起,没有拆分。

package com.proxy1;

import java.sql.SQLOutput;

/**
 *  图书购买业务和事务切面耦合在一起
 */
public class BookServiceImpl {

    public void buy(){

        try {
            System.out.println("事务开启.......");
            System.out.println("图书购买业务功能实现...........");
            System.out.println("事务提交.......");
        } catch (Exception e) {
            System.out.println("事务回滚.......");
        }
    }
}

(2)第二个版本:使用子类代理的方式拆分业务和切面,增强功能。

父类:

package com.proxy2;

/**
 *  使用子类代理的方式进行图书业务和事务切面的拆分
 */
public class BookServiceImpl {
    //在父类中只有干干净净的业务
    public void buy(){
        System.out.println("图书购买功能实现........");
    }
}

子类: 

package com.proxy2;

import java.sql.SQLOutput;

/**
 *  子类就是代理类,将父类的图书购买功能添加事务切面
 */
public class SubBookServiceImpl extends BookServiceImpl {

    @Override
    public void buy() {
        try {
            //事务切面
            System.out.println("事务开启.........");
            //主业务实现
            super.buy();
            //事务切面
            System.out.println("事务提交.........");
        } catch (Exception e) {
            System.out.println("事务回滚.........");
        }
    }
}

(3)第三个版本:使用静态代理实现:功能增强+(Service接口下的)目标对象切换。但此时切面紧耦合在业务中。

接口:

package com.proxy3;

public interface Service {
    //规定业务功能
     void buy();
}

目标类:

package com.proxy3;

/**
 *  目标对象:业务功能的具体实现
 */
public class BookServiceImpl implements Service {
    @Override
    public void buy() {
        System.out.println("图书购买业务功能实现............");
    }
}


package com.proxy3;

/**
 *
 */
public class ProductServiceImpl implements Service {
    @Override
    public void buy() {
        System.out.println("商品购买业务实现.........");
    }
}

中间代理类: 

package com.proxy3;

/**
 *  静态代理已经实现了目标对象的灵活切换
 *  图书购买业务,商品购买业务
 */
public class Agent implements Service {

    //设计成员变量的类型为接口,为了灵活切换目标对象
    public Service target;

    //使用构造方法传入目标对象
    public Agent(Service target){
        this.target = target;
    }
    @Override
    public void buy() {
        try {
            //切面功能
            System.out.println("事务开启......");  //日志 权限验证等就无法插入(改动)
            //业务功能
            target.buy();
            //切面功能
            System.out.println("事务提交.......");
        } catch (Exception e) {
            System.out.println("事务回滚.......");
        }
    }
}

测试类: 

package com.bjpowernode.test;

import com.proxy2.BookServiceImpl;
import com.proxy2.SubBookServiceImpl;
import org.junit.Test;

public class MyTest02 {
    @Test
    public void test02(){
        BookServiceImpl service = new SubBookServiceImpl();
        service.buy();
    }
}

(4)第四个版本:使用静态代理实现:功能增强+(Service接口下的)目标对象切换+代理功能切换。

业务接口: 

package com.proxy4;

/**
 *
 */
public interface Service {
    //规定业务功能
     void buy();
}

业务实现类:

package com.proxy4;

/**
 *  目标对象:业务功能的具体实现
 */
public class BookServiceImpl implements Service {
    @Override
    public void buy() {
        System.out.println("图书购买业务功能实现............");
    }
}


package com.proxy4;

/**
 *
 */
public class ProductServiceImpl implements Service {
    @Override
    public void buy() {
        System.out.println("商品购买业务实现.........");
    }
}

 代理功能接口(切面的接口):

package com.proxy4;

/**
 *
 */
public interface AOP {
    default void before(){}
    default void after(){}
    default void exception(){}
    // 给了默认实现,需要哪个用哪个,不需要每个都重写
}

代理功能实现:

package com.proxy4;


/**
 *
 */
public class LogAop implements AOP {
    @Override
    public void before() {
        System.out.println("前置日志输出.......");
    }
}

package com.proxy4;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

/**
 *
 */
public class TransAop implements AOP {
    @Override
    public void before() {
        System.out.println("事务开启........");
    }

    @Override
    public void after() {
        System.out.println("事务提交........");
    }

    @Override
    public void exception() {
        System.out.println("事务回滚........");
    }
}

中间代理类:

package com.proxy4;

/**
 *
 */
public class Agent implements Service {

    //传入目标(业务)对象,切面对象
    Service target;
    AOP aop;

    //使用构造方法初始化业务对象和切面对象
    public Agent(Service target,AOP aop){
        this.target = target;
        this.aop = aop;
    }

    @Override
    public void buy() {
        try {
            //切面
            aop.before();  //事务  日志
            //业务
            target.buy();  //图书  商品
            //切面
            aop.after();   //事务

        } catch (Exception e) {
            //切面
            aop.exception();
        }

    }
}

测试类:

package com.bjpowernode.test;


import com.proxy4.*;
import org.junit.Test;

public class MyTest04 {
    @Test
    public void test02(){
        Service agent = new Agent(new ProductServiceImpl(),new TransAop());
        Service agent1 = new Agent(agent,new LogAop());
        // 这样可以实现代理功能的嵌套(合并)
        agent1.buy();
    }
}

如上,代理功能可以合并!!!

(5)第五个版本:使用动态代理实现:功能增强+(所有)目标对象切换+代理功能切换。

静态代理中,构造方法的参数是Service,这是写死的了,这就意味着只能代理Service接口下的类,可以使用动态代理改进。

在(4)的基础之上,将代理类Agent由静态代理改为动态代理:

ProxyFactory动态代理类:

package com.proxy5;

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

/**
 *
 */
public class ProxyFactory {

    public static Object getAgent(Service target,AOP aop){
        //返回生成的动态代理对象
        return Proxy.newProxyInstance(
                //类加载器
                target.getClass().getClassLoader(),
                //目标对象实现的所有的接口
                target.getClass().getInterfaces(),
                //代理功能实现
                new InvocationHandler() {
                    @Override
                    public Object invoke(
                            //生成的代理对象
                            Object proxy,
                            //正在被调用的目标方法buy(),show()
                            Method method,
                            //目标方法的参数
                            Object[] args) throws Throwable {
                        Object obj = null;
                        try {
                            //切面
                            aop.before();  //事务  日志
                            //业务
                            obj = method.invoke(target,args);
                            //切面
                            aop.after();

                        } catch (Exception e) {
                            //切面
                           aop.exception();
                        }

                        return obj; //目标方法的返回值
                    }
                }
        );
    }
}

测试类:

package com.bjpowernode.test;


import com.proxy5.*;
import org.junit.Test;

/**
 *
 */
public class MyTest05 {
    @Test
    public void test02(){
        //得到动态代理对象
        Service agent = (Service) ProxyFactory.getAgent(new BookServiceImpl(),new TransAop());
        agent.buy();
    }

    @Test
    public void test03(){
        //得到动态代理对象
        Service agent = (Service) ProxyFactory.getAgent(new BookServiceImpl(),new LogAop());
        System.out.println(agent.getClass());
        // 有参数有返回值的被代理方法
        String s = agent.show(22);
        System.out.println(s);
    }
}

Spring的AOP通知类型(了解)

        Spring支持AOP的编程,常用的有以下几种:

  1. Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
  2. After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice;
  3. Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice;
  4. Around通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。

Spring原生的AOP较为麻烦,因此在项目中主要是运用AspectJ框架来实现AOP。

Spring原生AOP:

了解即可。

package com.bjpowernode.advice;

/**
 * 业务切面
 */

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

public class LogAdvice  implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
        System.out.println("\n[系统日志]"+sf.format(new Date())+"---"+method.getName()+ Arrays.toString(objects));
    }
}
package com.bjpowernode.service;

/**
 * 业务接口
 */

public interface BookService {
    public boolean buy(String userName,String bookName,double price);
    public void comment(String userName,String comments);
}
package com.bjpowernode.service.impl;

/**
 * 业务接口的实现
 */

import com.bjpowernode.service.BookService;

public class BookServiceImpl implements BookService {
    @Override
    public boolean buy(String userName, String bookName, double price) {
        System.out.println("购买图书功能实现...........");
        System.out.println(userName+"购买了"+bookName+",花费了"+price+"元.");
        System.out.println(userName+"增加了"+(price/10));
        System.out.println("图书购买业务结束");
        return false;
    }

    @Override
    public void comment(String userName, String comments) {
        System.out.println("评论功能的实现 ...........");
        System.out.println(userName+"发表了"+comments+".");
        System.out.println("评论功能结束 ...........");
    }
}
<?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">

    <!--创建业务对象-->
    <bean id ="bookServiceTarget" class="com.bjpowernode.service.impl.BookServiceImpl"></bean>
    <!--创建切面的对象-->
    <bean id="logAdvice" class="com.bjpowernode.advice.LogAdvice"></bean>

    <!--绑定业务和切面-->
    <!--对象名,用于在测试类中注册创建并调用-->
    <bean id="bookService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--配置业务接口-->
        <property name="interfaces" value="com.bjpowernode.service.BookService"></property>
        <!--配置切面-->
        <property name="interceptorNames">
            <list>
                <value>logAdvice</value>
                <!--为List集合,可配置多个切面-->
            </list>
        </property>
        <!--织入-->
        <property name="target" ref="bookServiceTarget"></property>
    </bean>
</beans>
package com.bjpowernode.test;

/**
 * 测试类
 */

import com.bjpowernode.service.BookService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public  void test01(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService proxy = (BookService) ac.getBean("bookService");
        System.out.println(proxy.getClass());
        proxy.buy("张三","平凡的世界",55);
        proxy.comment("张三","还是很好看,可以看一看.....");
    }
}

AOP常用的术语

(1)、切面:就是那些重复的,公共的,通用的功能称为切面,例如:日志,事务,权限。

(2)、连接点:就是目标方法。因为在目标方法中要实现目标方法的功能和切面功能。

(3)、切入点(Pointcut):指定切入的位置,多个连接点构成切入点。切入点可以是一个目标方法,可以是一个类中的所有方法,可以是某个包下的所有类中的方法。

(4)、目标对象:操作谁,谁就是目标对象。

(5)、通知(Advice):来指定切入的时机。是在目标方法执行前还是执行后还是出错时,还是环绕目标方法切入切面功能。

什么是AspectJ框架:

        AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现。它因为是基于java语言开发的,所以可以无缝扩展。

AspectJ常见通知类型

AspectJ 中常用的通知有四种类型:

(1)、前置通知@Before

(2)、后置通知@AfterReturning

(3)、环绕通知@Around

(4)、最终通知@After

(5)、定义切入点@Pointcut(了解)

AspectJ 的切入点表达式(掌握)

规范的公式:

        execution(访问权限 方法返回值 方法声明(参数) 异常类型)

简化后的公式:

        execution(方法返回值 方法声明(参数) )

用到的符号:

  *  代表任意个任意的字符(通配符)
  ..  如果出现在方法的参数中,则代表任意参数
  ..  如果出现在路径中,则代表本路径及其所有的子路径

示例:

execution(public * *(..)):任意的公共方法。

execution(* set*(..)):表示所有以set开头的任意返回值类型的任意参数的方法。

set*(..)跟括号连在一起表示一个方法名,所有前一个*只能是方法返回值了(访问权限可省略)

execution(* com.xyz.service.impl.*.*(..)):任意的返回值类型,在com.xyz.service.impl包下的任意类(impl后的第一个*)的任意方法(impl后的第二个*)的任意参数

execution(* com.xyz.service..*.*(..)):任意的返回值类型 ,在com.xyz.service及其子包下(service后的..)的任意类的任意方法的任意参数  

execution(* *..service.*.*(..)):任意返回值类型的任意包下的所有路径的service文件夹下的任意类的任意方法。

execution(* *.service.*.*(..)):任意返回值类型的任意包下的service文件夹下的任意类的任意方法。

execution(* *.ISomeService.*(..)):指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点

execution(* *..ISomeService.*(..)):指定所有包下的 ISomeSerivce 接口中所有方法为切入点

execution(* com.xyz.service.IAccountService.*(..)):指定切入点为:IAccountService  接口中的任意方法。

execution(* com.xyz.service.IAccountService+.*(..)):指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。

execution(* joke(String,int))):指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。

execution(* joke(String,*))):指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String s3)不是。

execution(* joke(String,..))):指定切入点为:所有的 joke()方法,该方法第一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3) 都是。

execution(* joke(Object)):指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。

execution(* joke(Object+))):指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。

execution(String com.bjpowernode.s01.SomeServiceImpl.doSome(String,int)):精准到doSome方法

AspectJ的前置通知@Before:

        在目标方法执行前切入切面功能。在切面方法中不可以获得目标方法的返回值,只能得到目标方法的签名。

前置通知的切面方法的规范
        (1)、访问权限是public
        (2)、方法的返回值是void
        (3)、方法名称自定义
        (4)、方法没有参数,如果有也只能是JoinPoint类型
        (5)、必须使用@Before注解来声明切入的时机是前切功能和切入点
        (6)、参数:value  指定切入点表达式

实现的步骤:

(1)、添加依赖

   <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version> 
    </dependency>

(2)、创建业务接口。

(3)、创建业务实现。

(4)、创建切面类,实现切面方法,并用@Aspect注解切面类。

(5)、在applicationContext.xml文件中进行切面绑定。

实现案例:

切面类MyAspect:

package com.bjpowernode.s01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 *  此类为切面类,包含各种切面方法
 */
@Aspect  //交给AspectJ的框架去识别切面类
@Component
public class MyAspect {
    /**
     * 所有切面的功能都是由切面方法来实现的
     * 可以将各种切面都在此类中进行开发
     *
     * 业务方法
     * public String doSome(String name, int age)
     */
    @Before(value = "execution(public String com.bjpowernode.s01.SomeServiceImpl.doSome(String,int))")
    public void myBefore(){
        System.out.println("切面方法中的前置通知功能实现............");
    }

}

@Aspect交给AspectJ框架识别切面类。

@Component:可以创建任意对象。

@Controller:专门用来创建控制器的对象(Servlet)。

@Service:专门用来创建业务逻辑层的对象。

@Repository:专门用来创建数据访问层的对象。

业务接口SomeService: 

package com.bjpowernode.s01;

/**
 *  业务接口
 */
public interface SomeService {
    String doSome(String name,int age);

}

业务接口实现类SomeServiceImpl: 

package com.bjpowernode.s01;

import org.springframework.stereotype.Service;

/**
 *  业务实现类
 */
@Service
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome的业务功能实现................");
        return "abcd";
    }

}

applicationContext.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" xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--这里使用注解的方式创建对象-->
    <!--基于注解的访问要添加包扫描-->
    <context:component-scan base-package="com.bjpowernode.s01"></context:component-scan>
    <!--绑定-->
    <aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
</beans>

测试类:

package com.bjpowernode.test;

import com.bjpowernode.s01.SomeService;
import com.bjpowernode.s01.SomeServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 */
public class MyTest01 {

    @Test
    public void test01(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");
        //取出代理对象
        SomeService someService = (SomeService) ac.getBean("someServiceImpl");
        System.out.println(someService.getClass());
        String s = someService.doSome("张三",22);
        System.out.println(s);
    }

}

目录:

AspectJ框架切换JDK动态代理和CGLib动态代理:

<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>

        默认是JDK动态代理。在测试类中必须使用接口类型的变量来接收getBean方法返回值。(用实现类会报错,因为动态代理已经在原接口的基础上进行了功能的增强,而实现类实现的仅仅只是原来的接口,匹配不上)

<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

        设置为CGLib子类代理。使用子类代理时在测试类中使用实现类的类型变量来接收getBean方法返回值。(因为子类代理就是在接口的实现类的基础上进行功能的拓展,所以用实现类去接就不会报错)(接口也可以)

        记住:使用接口来接,永远不出错。

注意:查看一个对象是否是动态代理对象,直接输出是看不出来的,需要调用对象的getClass()方法。

切面方法的形参:

        在切面方法中不可以获得目标方法的返回值,但可以得到目标方法的签名。

使用JoinPoint形参的变量就可以获得签名的信息。

实参由AspectJ自己传。

    @Before(value = "execution( * com.bjpowernode.s01.*.*(..))")
    public void myBefore(JoinPoint jp){
        System.out.println("切面方法中的前置通知功能实现............");
        // 获得方法的签名
        System.out.println("目标方法的签名:"+jp.getSignature());
        // 获得方法的参数,返回的是一个Object类型的数组,可以用Array.toString方法输出打印
        System.out.println("目标方法的参数:"+ Arrays.toString(jp.getArgs()));
    }

AspectJ的后置通知@AfterReturning:

        后置通知是在目标方法执行后切入切面功能,可以得到目标方法的返回值。如果目标方法的返回值是简单类型(8种基本类型+String)则不可改变。如果目标方法的返回值是引用类型则可以将其改变。

后置通知的方法的规范:

(1)访问权限是public

(2)方法没有返回值void

(3)方法名称自定义

(4)方法要求有参数,推荐使用Object,参数名自定义,用于接收目标方法的返回值

(也可以没有参数,如果目标方法没有返回值,则可以写无参的方法,但一般会写有参,这样既可以处理无参的目标方法也可以处理有参的目标方法)

(5)使用@AfterReturning注解表明是后置通知

(6)参数:

        value:指定切入点表达式

        returning:指定目标方法的返回值的名称,则名称必须与切面方法的参数名称一致。

代码示例:

业务接口: 

package com.bjpowernode.s02;

/**
 *
 */
public interface SomeService {
    String doSome(String name,int age);
    Student change();
}

业务实现类:

package com.bjpowernode.s02;

import org.springframework.stereotype.Service;

/**
 *
 */
@Service
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome()业务方法被执行............");
        return "abcd";
    }

    @Override
    public Student change() {
        System.out.println("change()方法被执行............");
       // System.out.println(1/0);
        return new Student("张三");
    }
}

切面类:

package com.bjpowernode.s02;

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 *
 */
@Aspect
@Component
public class MyAspect {

    @AfterReturning(value = "execution(* com.bjpowernode.s02.*.*(..))",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知功能实现..............");

        // 以下,改变方法的返回值
        if(obj != null){
            if(obj instanceof String){
                obj = obj.toString().toUpperCase();
                System.out.println("在切面方法中目标方法的返回值:"+obj);
            }
            if(obj instanceof Student){
                Student stu = (Student) obj;
                stu.setName("李四");
                System.out.println("在切面方法中目标方法的返回值:"+stu);
            }
        }
    }
}

学生实体类:

package com.bjpowernode.s02;

/**
 *
 */
public class Student {
    private String name;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }
}

applicationContext.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" xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">


    <!--基于注解的访问要添加包扫描-->
    <context:component-scan base-package="com.bjpowernode.s02"></context:component-scan>
    <!--绑定-->
    <aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
</beans>

测试类:

package com.bjpowernode.test;

import com.bjpowernode.s02.SomeService;
import com.bjpowernode.s02.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 */
public class MyTest02 {

    @Test
    public void test01(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml");
        //取出代理对象
        SomeService someService = (SomeService) ac.getBean("someServiceImpl");
        String s = someService.doSome("张三",22);
        System.out.println("在测试方法中目标方法的返回值:"+s);
        // 测试可得,原返回值并未被改变
    }

    @Test
    public void test02(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("s02/applicationContext.xml");
        //取出代理对象
        SomeService someService = (SomeService) ac.getBean("someServiceImpl");
        Student stu = someService.change();
        System.out.println("在测试为中目标方法的返回值是:"+stu);
        // 测试可得,原返回值已经被改变
    }

}

AspectJ的环绕通知@Around:

        它是通过拦截目标方法的方式 ,在目标方法前后增强功能的通知。它是功能最强大的通知,一般事务使用此通知。它可以轻易的改变目标方法的返回值。

环绕通知方法的规范
(1)访问权限是public
(2)切面方法有返回值,此返回值将作为目标方法的返回值
(3)方法名称自定义
(4)方法有参数,此参数就是目标方法
(5)回避异常Throwable
(6)使用@Around注解声明是环绕通知

参数:
        value:指定切入点表达式

        如果在spring中的环绕通知中修改了目标方法的返回值,那么实际的返回值也会改变,即使你改变的是形参的指向,返回值也会改变(可修改String类型的返回值)。

案例实现:

 业务接口:

package com.bjpowernode.s03;

/**
 *
 */
public interface SomeService {
    String doSome(String name,int age);
}

业务实现类: 

package com.bjpowernode.s03;

import org.springframework.stereotype.Service;

/**
 *
 */
@Service
public class SomeServiceImpl implements SomeService{
    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome业务方法被执行...."+name);
        return "abcd";
    }
}

切面类:

package com.bjpowernode.s03;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 *
 */
@Aspect
@Component
public class MyAspect {

    @Around(value = "execution(* com.bjpowernode.s03.*.*(..))")
    // 这里须使用ProceedingJoinPoint类型来作为形参接收方法的签名
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        //前切功能实现
        System.out.println("环绕通知中的前置功能实现............");
        //目标方法调用。使用proceed()对方法进行调用,getArgs获得要调用方法的签名中的参数
        Object obj = pjp.proceed(pjp.getArgs());
        //后切功能实现
        System.out.println("环绕通知中的后置功能实现............");
        return obj.toString().toUpperCase();  //改变了目标方法的返回值
    }
}

 applicationContext.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" xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">


    <!--基于注解的访问要添加包扫描-->
    <context:component-scan base-package="com.bjpowernode.s03"></context:component-scan>
    <!--绑定-->
    <aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
</beans>

测试类:

package com.bjpowernode.test;


import com.bjpowernode.s03.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 */
public class MyTest03 {

    @Test
    public void test01(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("s03/applicationContext.xml");
        //取出代理对象
        SomeService someService = (SomeService) ac.getBean("someServiceImpl");
        String s = someService.doSome("张三",22);
        System.out.println("在测试方法中目标方法的返回值:"+s);

    }

}

AspectJ的最终通知@After:

        对于后置通知和环绕通知的后切通知来说,如果所调用的方法发生了异常那么将不好被执行,为了代码无论如何都能被执行,我们可以设置最终通知,无论目标方法是否正常执行,最终通知的代码都会被执行。

最终通知方法的规范

(1)访问权限是public

(2)方法没有返回值

(3)方法名称自定义

(4)方法没有参数,如果有也只能是JoinPoint

(5)使用@After注解表明是最终通知

        参数:value:指定切入点表达式

案例示例:

业务接口: 

package com.bjpowernode.s04;

/**
 *
 */
public interface SomeService {
    String doSome(String name, int age);
}

业务实现类:

package com.bjpowernode.s04;

import org.springframework.stereotype.Service;

/**
 *
 */
@Service
public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, int age) {
        System.out.println("doSome业务方法被执行...."+name);
        //System.out.println(1/0);
        return "abcd";
    }
}

切面类:

package com.bjpowernode.s04;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 *
 */
@Aspect
@Component
public class MyAspect {

    @After(value = "execution(* com.bjpowernode.s04.*.*(..))")
    public void myAfter(){
        System.out.println("最终通知的功能........");
    }

}

applicationContext.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" xmlns:aop="http://www.springframework.org/schema/aop"
       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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">


    <!--基于注解的访问要添加包扫描-->
    <context:component-scan base-package="com.bjpowernode.s04"></context:component-scan>
    <!--绑定-->
    <aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
</beans>

测试类:

package com.bjpowernode.test;


import com.bjpowernode.s04.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 */
public class MyTest04 {

    @Test
    public void test01(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("s04/applicationContext.xml");
        //取出代理对象
        SomeService someService = (SomeService) ac.getBean("someServiceImpl");
        String s = someService.doSome("张三",22);
        System.out.println("在测试方法中目标方法的返回值:"+s);

    }
}

给切入点表达式起别名@Pointcut:

如果多个切面切入到同一个切入点,可以使用别名简化开发。

使用@Pointcut注解,创建一个空方法,此方法的名称就是别名。

package com.bjpowernode.s04;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 *
 */
@Aspect
@Component
public class MyAspect {

    @After(value = "mycut()")
    public void myAfter(){
        System.out.println("最终通知的功能........");
    }
    @Before(value = "mycut()")
    public void myBefore(){
        System.out.println("前置通知的功能........");
    }
    @AfterReturning(value = "mycut()",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知的功能........");
    }
    @Around(value = "mycut()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知中的前置通知的功能........");
        Object obj = pjp.proceed(pjp.getArgs());
        System.out.println("环绕通知中的后置通知的功能........");
        return obj;
    }
    @Pointcut(value = "execution(* com.bjpowernode.s04.*.*(..))")
    public void mycut(){}
}

SM整合的步骤

(1)建表
(2)新建项目,选择quickstart模板
(3)修改目录

(4)修改pom.xml文件,添加相关的依赖和添加资源文件指定(有模板)

(5)添加MyBatis相应的模板(只是为了以后创建项目的便利,为SqlMapConfig.xml和XXXMapper.xml文件设定基本的代码)

(6)添加SqlMapConfig.xml文件(MyBatis核心配置文件,注册Mapper文件等作用),并拷贝jdbc.propertiest属性文件到resources目录下(jdbc文件用于配置数据库账户密码)

(7)添加applicationContext_mapper.xml
(8)添加applicationContext_service.xml
(9)添加Users实体类
(10)添加mapper包,添加UsersMapper接口和UsersMapper.xml文件并开发
(11)添加service包,添加UsersService接口和UsersServiceImpl实现类
(12)添加测试类进行功能测试

(1)建表
(2)新建项目,选择quickstart模板
(3)修改目录

(4)修改pom.xml文件,添加相关的依赖和添加资源文件指定(有模板),模板如下:

........省略.........

<dependencies>
    <!--单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--aspectj依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--spring核心ioc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--做spring事务用到的-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>
    <!--mybatis和spring集成的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.32</version>
    </dependency>
    <!--阿里公司的数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
  </dependencies>

  <build>
    <!--目的是把src/main/java目录中的xml文件包含到输出结果中。输出到classes目录中-->
    <resources>
      <resource>
        <directory>src/main/java</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
      <resource>
        <directory>src/main/resources</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>

(5)添加MyBatis相应的模板(只是为了以后创建项目的便利,为SqlMapConfig.xml和XXXMapper.xml文件设定基本的代码)

添加模板方法:

Mybatis中的SqlMapConfig.xml文件:

<?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>
 
    <!--读取属性文件(jdbc.properties)
      属性:
         <properties resource="xxx" 或 url="xxx"></properties>
         resources:从resources目录下找指定名称的文件加载
         url:使用绝对路径加载属性文件
    -->
    <properties resource="jdbc.properties"></properties>
 
    <!--设置日志输出底层执行的代码-->
    <!--name和value是固定的,STDOUT_LOGGING 表示控制台日志-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
 
    <!--注册实体类的别名,注册完后XXXMapper.xml文件就可以使用别名代替完整类名-->
    <!--别名大小写不敏感-->
    <typeAliases>
        <!--单个实体类别名注册-->
        <!--<typeAlias type="com.bjpowernode.pojo.Student" alias="student"></typeAlias>-->
        
        <!--批量注册别名。因为在公司的项目中一个包下可能有几百个类,如果都注册别名需要写几百行的注册别名,不实用,可以批量注册。
          别名是类名的驼峰命名法(规范)
        -->
        <package name="com.bjpowernode.pojo"></package>
    </typeAliases>
 
    <!--配置数据库的环境变量(数据库连接配置)
       default:使用下面的environment标签的id属性进行指定配置
    -->
    <environments default="development">
    <!-- environment的id是提供给environments的default属性进行快速的配置指定的-->
 
        <!--(开发时)在公司使用的数据库配置-->
        <environment id="development">
 
            <!--配置事务管理器
               type:指定事务管理的方式
                  JDBC:事务的控制交给程序员处理
                  MANAGED:由容器(Spring)来管理事务
            -->
            <transactionManager type="JDBC"></transactionManager>
 
            <!--配置数据源
               type:指定不同的配置方式
                  JNDI:java命名目录接口,在服务器端进行数据库连接池的管理
                  POOLED:使用数据库连接池
                  UNPOOLED:不使用数据库连接池
            -->
            <dataSource type="POOLED">
 
                <!--配置数据库连接的基本参数
                    private String driver;
                    private String url;
                    private String username;
                    private String password;
                -->
                <property name="driver" value="${jdbc.driverClassName}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>
 
        </environment>
 
    <!--
        在家的数据库配置
        <environment id="home">
            <transactionManager type=""></transactionManager>
            <dataSource type=""></dataSource>
        </environment>
        上线后的数据库配置&ndash;&gt;
        <environment id="online">
            <transactionManager type=""></transactionManager>
            <dataSource type=""></dataSource>
        </environment>
    -->
    </environments>
 

   <!--注册mapper.xml文件-->
    <mappers>

        <!--绝对路径注册-->
        <mapper url="/"></mapper>

        <!--非动态代理方式下的注册(StudentMapper.xml文件在resources文件夹下)-->
        <mapper resource="StudentMapper.xml"></mapper>

        <!--动态代理方式下的单个mapper.xml文件注册-->
        <!--主要!!重要!!-->
        <mapper class="com.bjpowernode.mapper.UsersMapper"></mapper>

        <!--批量注册-->
        <package name="com.bjpowernode.mapper"></package>

    </mappers> 
</configuration>

Spring接管后的SqlMapConfig.xml模板文件(大部分功能都可以被Spring接管,大部分代码都可以删除,后续介绍)(但缓存处理等Spring接管不了,依旧得写入这个文件中):

<?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>

    <!--设置日志输出底层执行的代码-->
    <!--name和value是固定的,STDOUT_LOGGING 表示控制台日志-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    ......未被Spring接管的设置......

</configuration>

XXXMapper.xml:

<?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">

<!--使用Mybatis动态代理需要将namespace更名为该文件的完全限定名-->
<mapper namespace="com.bjpowernode.mapper.UsersMapper">

    ......增删改查mapper代码.......

</mapper>

(6)添加SqlMapConfig.xml文件(MyBatis核心配置文件,注册Mapper文件等作用),并拷贝jdbc.propertiest属性文件到resources目录下(jdbc文件用于配置数据库账户密码)

模板的使用:

 jdbc.propertiest文件:

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
// com.mysql.jdbc.Driver是mysql-connector-java 5版本的驱动名称,5版本之后替换为com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root

(7)添加applicationContext_mapper.xml(mapper表示数据访问层)

<?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">

    <!--读取属性文件jdbc.properties-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>
    <!--创建数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--配置SqlSessionFactoryBean类-->
    <!--这个bean不会被我们调用,所以不用配id也可以-->
    <!--对Spring的SqlSessionFactoryBean类进行配置-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--配置数据源-->
        <!--替代SqlMapConfig.xml中读取属性文件(jdbc.properties)的操作-->
        <!--此处引用上面创建的数据源对象,然后对其进行配置-->
        <property name="dataSource" ref="dataSource"></property>
        <!--配置MyBatis的核心配置文件-->
        <!--SqlMapConfig有一些Spring无法接管的功能,所以也需要配置MyBatis核心配置文件-->
        <!--配置SqlMapConfig.xml的位置,从resources包下开始-->
        <property name="configLocation" value="SqlMapConfig.xml"></property>
        <!--替代SqlMapConfig.xml中注册实体类别名的功能-->
        <!--注册实体类的别名,注册完后XXXMapper.xml文件就可以使用别名代替完整类名-->
        <!--用包的形式注册实体类的别名-->
        <property name="typeAliasesPackage" value="com.bjpowernode.pojo"></property>
    </bean>

    <!--注册mapper.xml文件-->
    <!--调用org.mybatis.spring.mapper.MapperScannerConfigurer这个类对mapper文件进行注册-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--name为basePackage表示用包名批量注册,value中为包名-->
        <!--在Mybatis中如果使用动态代理那么要用class来注册mapper,但这里不是Mybatis-->
        <property name="basePackage" value="com.bjpowernode.mapper"></property>
    </bean>
</beans>

        当添加applicationContext_mapper.xml后,原Mybatis的SqlMapConfig.xml配置文件要删掉相应的功能不然会冲突,对于上述模板来说,就只留下日志的设置而已。

(8)添加applicationContext_service.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"
       xmlns:context="http://www.springframework.org/schema/context" 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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--导入applicationContext_mapper.xml文件-->
    <import resource="applicationContext_mapper.xml"></import>

    <!--SM是基于注解的开发,所以添加包扫描-->
    <context:component-scan base-package="com.bjpowernode.service.impl"></context:component-scan>


</beans>

(9)添加Users实体类

package com.bjpowernode.pojo;

/**
 *
 */
public class Users {
    private Integer userid;
    private String username;
    private String upass;

    @Override
    public String toString() {
        return "Users{" +
                "userid=" + userid +
                ", username='" + username + '\'' +
                ", upass='" + upass + '\'' +
                '}';
    }

    public Integer getUserid() {
        return userid;
    }

    public void setUserid(Integer userid) {
        this.userid = userid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUpass() {
        return upass;
    }

    public void setUpass(String upass) {
        this.upass = upass;
    }

    public Users(Integer userid, String username, String upass) {
        this.userid = userid;
        this.username = username;
        this.upass = upass;
    }

    public Users() {
    }
}

(10)添加mapper包,添加UsersMapper接口和UsersMapper.xml文件并开发

package com.bjpowernode.mapper;

import com.bjpowernode.pojo.Users;

public interface UsersMapper {
    int insert(Users users);
}
<?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="com.bjpowernode.mapper.UsersMapper">

    <!--
       int insert(Users users);
       实体类
        private Integer userid;
        private String username;
        private String upass;
    -->
    <insert id="insert" parameterType="users">
        insert into users values(#{userid},#{username},#{upass})
    </insert>
</mapper>

(11)添加service包,添加UsersService接口和UsersServiceImpl实现类

package com.bjpowernode.service;

import com.bjpowernode.pojo.Users;

public interface UsersService {
    //增加用户
    int insert(Users users);
}
package com.bjpowernode.service.impl;

import com.bjpowernode.mapper.UsersMapper;
import com.bjpowernode.pojo.Users;
import com.bjpowernode.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 *
 */
@Service  //交给Spring去创建对象
//@Transactional(propagation = Propagation.REQUIRED)
public class UsersServiceImpl implements UsersService {

    /**
     *         使用MyBatis时的访问方法:
     *         //1.读取核心配置文件
     *         InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
     *         //2.创建工厂对象
     *         SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
     *         //3.取出sqlSession
     *         sqlSession = factory.openSession(true);//自动提交事务
     *         //4.取出动态代理的对象,完成接口中方法的调用,实则是调用xml文件中相的标签的功能
     *         uMapper = sqlSession.getMapper(UsersMapper.class);
     */
    //切记切记:在所有的业务逻辑层中一定会有数据访问层的对象
    @Autowired
    UsersMapper usersMapper;

    @Override
    public int insert(Users users) {
        int num = usersMapper.insert(users);
        System.out.println("用户增加成功!num="+num);
        return num;
    }
}

(12)添加测试类进行功能测试

package com.bjpowernode.test;

import com.bjpowernode.pojo.Users;
import com.bjpowernode.service.UsersService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 *
 */
public class MyTest {
    @Test
    public void testUsers(){
        //创建容器并启动(所有的对象都创建好了)
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_service.xml");
        //取出UsersServiceImpl对象,用接口类型去接,并调用其实现UsersService接口的方法
        UsersService uService = (UsersService) ac.getBean("usersServiceImpl");
        int num = uService.insert(new Users(311,"张三","123"));
        // UsersServiceImpl是普通的对象,不是动态代理的增强对象
        System.out.println(uService);

        System.out.println(num);
    }


}

包扫描只用进行一次即可。可以在mapper.xml文件中配置Spring,然后在service.xml中进行包扫描(基于注解的方式)

Spring的两种事务处理方式

(1)注解式的事务

        使用@Transactional注解完成事务控制,此注解可添加到类上,则对类中所有方法执行事务的设定。此注解可添加到方法上,只是对此方法执行事务的处理。

(2)声明式事务(必须掌握)

        在配置文件中添加一次,整个项目遵循事务的设定。

基于注解式的事务添加步骤(变成动态代理类型)

(1)在applicationContext_service.xml文件中添加事务管理器

    <!--1.添加事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--因为事务必须关联数据库处理,所以要配置数据源-->
        <!--这个dataSource指向了applicationContext_mapper.xml文件中配置好了的数据源-->
        <property name="dataSource" ref="dataSource"></property>
     </bean>

(2)在applicationContext_service.xml文件中添加事务的注解驱动

    <!--2.添加事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

(3)在业务逻辑的实现类上添加注解

@Transactional(propagation = Propagation.REQUIRED)

        REQUIRED表示增删改操作时必须添加的事务传播特性

@Transactional注解参数详解

rollbackForClassName = "异常名称"        指定发生什么异常必须回滚

rollbackFor = ArithmeticException.class        指定发生什么异常必须回滚

readOnly 如果是查询操作,必须设置为true。

isolation = Isolation.DEFAULT        使用数据库本身(本身)的隔离级别        

@Transactional有几点需要注意

  1. 只能声明在publicmethod原因是spring是通过JDK代理或者CGLIB代理的,生成的代理类,只能处理public方法,注解放在类名称上面,这样你配置的这个@Transactional 对这个类中的所有public方法都起作用,@Transactional 在方法名上,只对这个方法有作用,同样必须是public的方法。
  2. 不能被类内部方法调用。还是因为代理的原因,类内部自调用,不会经过代理类,所以@Transactional不会生效

Spring中事务的五大隔离级别

(1)未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。

(2)提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)。

(3)可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读

(4)串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

(5)使用数据库默认的隔离级别isolation = Isolation.DEFAULT
        MySQL:mysql默认的事务处理级别是'REPEATABLE-READ',也就是可重复读。
        Oracle:oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别。默认系统事务隔离级别是READ COMMITTED,也就是读已提交。

  

为什么添加事务管理器

        JDBC:  Connection   con.commit();   con.rollback();

        MyBatis:  SqlSession   sqlSession.commit();  sqlSession.rollback();

        Hibernate:  Session    session.commit();   session.rollback();

事务管理器用来生成相应技术的连接+执行语句的对象.

如果使用MyBatis框架,必须使用DataSourceTransactionManager类完成处理

     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--因为事务必须关联数据库处理,所以要配置数据源-->
        <property name="dataSource" ref="dataSource"></property>
     </bean>

项目中的所有事务,必须添加到业务逻辑层上.

Spring事务的传播特性

多个事务之间的合并,互斥等都可以通过设置事务的传播特性来解决。

常用:

PROPAGATION_REQUIRED:必被包含事务(增删改必用)

PROPAGATION_REQUIRES_NEW:自己新开事务,不管之前是否有事务

PROPAGATION_SUPPORTS:支持事务,如果加入的方法有事务,则支持事务,如果没有,不单开事务

PROPAGATION_NEVER:不能运行中事务中,如果包在事务中,抛异常

PROPAGATION_NOT_SUPPORTED:不支持事务,运行在非事务的环境

不常用:

PROPAGATION_MANDATORY:必须包在事务中,没有事务则抛异常

PROPAGATION_NESTED:嵌套事务

示例:

AccountServiceImpl 是出异常的

UserServiceImpl 是没出异常的

UserServiceImpl 内包含了 AccountServiceImpl (前者的方法中调用了后者的方法)

事务是按照范围来的,大范围的事务影响小范围的事务。

下面示例了不同情况下对数据库表的操作,OK表示成功更改数据库,NO则表示未成功更改。

基于声明式的事务添加:

声明式事务要求项目中的方法命名有规范:

        (1)完成增加操作包含    add  save  insert  set

        (2)更新操作包含   update   change  modify  

        (3)删除操作包含   delete   drop    remove  clear

        (4)查询操作包含   select   find    search  get 

示例:

    创建新的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"
       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 http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--此配置文件与applicationContext_service.xml的功能一样,只是事务配置不同-->

    <!--导入applicationContext_mapper.xml-->
    <import resource="applicationContext_mapper.xml"></import>
    <!--添加包扫描-->
    <context:component-scan base-package="com.bjpowernode.service.impl"></context:component-scan>
    <!--添加事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置事务切面,指定什么方法要添加事务-->
    <!--配置事务切面时可以使用通配符*来匹配所有方法-->
    <tx:advice id="myadvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--增-->
            <tx:method name="*insert*" propagation="REQUIRED" no-rollback-for="ArithmeticException"/>
            <tx:method name="*add*" propagation="REQUIRED"/>

            <!--删-->        <!--增删改都必须使用REQUIRED-->
            <tx:method name="*modify*" propagation="REQUIRED"/>
            <tx:method name="*delete*" propagation="REQUIRED"/>
            <tx:method name="*remove*" propagation="REQUIRED"/>
            <tx:method name="*drop*" propagation="REQUIRED"/>
            <tx:method name="*clear*" propagation="REQUIRED"/>

            <!--改-->        <!--no-rollback-for指定不回滚的异常类型-->
            <tx:method name="*save*" propagation="REQUIRED" no-rollback-for="ArithmeticException"/>
            <tx:method name="*set*" propagation="REQUIRED"/>
            <tx:method name="*update*" propagation="REQUIRED"/>
            <tx:method name="*change*" propagation="REQUIRED"/>

            <!--查-->
            <tx:method name="*select*" read-only="true"/>
            <tx:method name="*find*" read-only="true"/>
            <tx:method name="*search*" read-only="true"/>
            <tx:method name="*get*" read-only="true"/>

            <!--其余的跟随外层事务(支持事务),外层有事务就开启事务,没有事务则不开启-->
            <tx:method name="*" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>

    <!--绑定切面和切入点-->
    <!--上面所配置的方法名称有可能在多个层里面都存在,那么在这里指定那个文件夹下的方法被事务控制-->
    <aop:config>
        <!--指定切入点-->
        <aop:pointcut id="mycut" expression="execution(* com.bjpowernode.service.impl.*.*(..))"></aop:pointcut>
        <!--将切面和切入点绑定起来-->
        <!--advice-ref指向上面事务切面的id,pointcut-ref指向切入点id-->
        <aop:advisor  advice-ref="myadvice" pointcut-ref="mycut"></aop:advisor>
    </aop:config>
</beans>

测试文件中:    

    public void testTrans(){
        //创建容器并启动
// 注意这里的配置文件的选择!!!!!!!
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_trans.xml");    
        //取出UsersServiceImpl
        UsersService uService = (UsersService) ac.getBean("usersServiceImpl");
        int num = uService.insert(new Users(100,"张三","123"));
        System.out.println(num);
    }

补充:在声明式事务的基础上为某一个方法提供单独的事务管理:

    <!--添加注解事务-->
    <tx:annotation-driven order="100"></tx:annotation-driven>

    <!--配置事务切面,指定切面-->
    <tx:advice id="myadvice" transaction-manager="transactionManager">
        <tx:attributes>
            ......
        </tx:attributes>
    </tx:advice>
    <!--绑定切面和切入点-->
    <aop:config>
        <!--指定切入点-->
        <aop:pointcut id="mycut" expression="execution(* com.bjpowernode.service.impl.*.*(..))"></aop:pointcut>
        <!--将切面和切入点绑定起来-->
        <!--advice-ref指向切面-->
        <!--pointcut-ref指向切入点-->
        <aop:advisor order="1" advice-ref="myadvice" pointcut-ref="mycut"></aop:advisor>
    </aop:config>

        在配置事务切面同级目录中添加注解事务,并用order设置优先级为100,再将<aop:advisor>的优先级设置为1,这样就可以在声明式事务中使用注解式事务,并且如果有注解式事务则忽略声明式事务的指定。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

秦矜

对你有帮助的话,请我吃颗糖吧~

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

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

打赏作者

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

抵扣说明:

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

余额充值