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容器,称为反转。 容器说了算。

       ===>Spring容器负责对象的创建
                        ===>Spring容器依赖注入值
     
  

基于xml的IOC:

(1)创建对象:

applicationContext.xml:

  

测试类: 

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);
    }
}

文件位置:

Spring框架详解_第1张图片

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




    
    
        
        

    

A、赋值之使用setter注入:

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

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





    
    
        
        
    

        
    
        
        

    

B.使用构造方法注入:

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

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





    
    
        
        
        
    

    
    
        
        
        

    

    
    
        
        
        

    

基于注解的IOC:

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


(1)创建对象的注解:

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

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

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

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

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

}

注意,基于注解的IOC,必须要在Spring的核心配置文件中添加包扫描:




    
    
    


(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)单个包扫描(推荐使用):

    
    
    

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

    

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

    

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

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

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

拆分配置文件的策略:

A、按层拆:

      applicationContext_controller.xml 中:       
        
        

      applicationContext_service.xml 中:       
        
        

      applicationContext_mapper.xml 中:       
        
        

B、按功能拆:

      applicationContext_users.xml 中:       
        
        
        
      applicationContext_book.xml 中:     
        
        
        

spring配置文件的整合:

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



   
    
    
    
    
    
    

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

基于xml的IOC的项目案例:

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

项目结构:

Spring框架详解_第2张图片

  实体类
  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




    
    
        
    

applicatoinContext_service.xml





    
    
        
        
    


applicatoinContext_mapper.xml




    
    
    
    

total.xml




    
    

测试类:

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的项目案例:

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

项目结构:

Spring框架详解_第3张图片

实体类
  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




    
    

applicatoinContext_service.xml




    
    

applicatoinContext_mapper.xml




    
    

total.xml




    
    

测试类:

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:

        查找同源的对象注入。

    
        
        

    

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

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

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

    
        
        

    

         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接口下的)目标对象切换+代理功能切换。

Spring框架详解_第4张图片

业务接口: 

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("评论功能结束 ...........");
    }
}



    
    
    
    

    
    
    
        
        
        
        
            
                logAdvice
                
            
        
        
        
    
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)、添加依赖

   
      org.springframework
      spring-aspects
      5.2.5.RELEASE 
    

(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:




    
    
    
    
    

测试类:

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);
    }

}

目录:

Spring框架详解_第5张图片

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

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

        设置为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:





    
    
    
    

测试类:

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:





    
    
    
    

测试类:

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:





    
    
    
    

测试类:

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文件,添加相关的依赖和添加资源文件指定(有模板),模板如下:

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


    
    
      junit
      junit
      4.12
      test
    
    
    
      org.springframework
      spring-aspects
      5.2.5.RELEASE
    
    
    
      org.springframework
      spring-context
      5.2.5.RELEASE
    
    
    
      org.springframework
      spring-tx
      5.2.5.RELEASE
    
    
      org.springframework
      spring-jdbc
      5.2.5.RELEASE
    
    
    
      org.mybatis
      mybatis
      3.5.6
    
    
    
      org.mybatis
      mybatis-spring
      1.3.1
    
    
    
      mysql
      mysql-connector-java
      5.1.32
    
    
    
      com.alibaba
      druid
      1.1.12
    
  

  
    
    
      
        src/main/java
        
          **/*.properties
          **/*.xml
        
        false
      
      
        src/main/resources
        
          **/*.properties
          **/*.xml
        
        false
      
    
  

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

添加模板方法:

Spring框架详解_第6张图片

Mybatis中的SqlMapConfig.xml文件:




 
    
    
 
    
    
    
        
    
 
    
    
    
        
        
        
        
        
    
 
    
    
    
 
        
        
 
            
            
 
            
            
 
                
                
                
                
                
            
 
        
 
    
    
 

   
    

        
        

        
        

        
        
        

        
        

     

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

 


    
    
    
        
    

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

XXXMapper.xml:







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


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

模板的使用:

Spring框架详解_第7张图片

 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表示数据访问层)

Spring框架详解_第8张图片




    
    
    
    
        
        
        
        
    

    
    
    
    
        
        
        
        
        
        
        
        
        
        
        
        
    

    
    
    
        
        
        
    

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

(8)添加applicationContext_service.xml




    
    

    
    


(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);
}
 


    
    
        insert into users values(#{userid},#{username},#{upass})
    

(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文件中添加事务管理器

    
    
        
        
        
     

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

    
    

Spring框架详解_第9张图片

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

@Transactional(propagation = Propagation.REQUIRED)

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

@Transactional注解参数详解

Spring框架详解_第10张图片

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类完成处理

     
        
        
     

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

Spring框架详解_第11张图片

Spring事务的传播特性

Spring框架详解_第12张图片

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

常用:

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

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

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

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

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

不常用:

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

PROPAGATION_NESTED:嵌套事务

示例:

AccountServiceImpl 是出异常的

UserServiceImpl 是没出异常的

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

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

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

Spring框架详解_第13张图片

基于声明式的事务添加:

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

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

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

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

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

示例:

    创建新的xml配置文件:

Spring框架详解_第14张图片




    

    
    
    
    
    
    
        
    

    
    
    
        
            
            
            

                    
            
            
            
            
            

                    
            
            
            
            

            
            
            
            
            

            
            
        
    

    
    
    
        
        
        
        
        
    

测试文件中:    

    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);
    }

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

    
    

    
    
        
            ......
        
    
    
    
        
        
        
        
        
        
    

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

你可能感兴趣的:(JAVA,spring,java)