Spring 应用及原理分析

        Spring 是一个轻量级的开发框架,以Ioc和AOP为内核,提供了展现层Spring MVC和业务层事务管理等众多企业级应用技术。还能整合众多的第三方类库,是目前java非常流行的框架。Spring 的核心思想就是IOC和AOP。

一.什么是IoC与DI

         Ioc与DI都是描述的同一件事,但是出发点不同。Ioc的英文全称是Inversion of Contronl,翻译过来是反转控制。DI 英文全称为Depedency Inection ,翻译过来是依赖注入。IOC和DI是一种思想,不是一种技术!!

        IOC和DI描述的都是同一件事情,只是从不同角度出发。IOC是针对对象而言,把对象的控制权交给容器,DI是从容器的角度出发,从容器中把对象的依赖注入给对象。

        传统的开发中,我们有两个对象A和B,A关联B,我们一般会定义如下:

        public class A {

                private B b  = new B();

        }

        而IOC和DI思想则不需要在关联或者依赖中让我们手动去new想对象。而是使用一个容器把对象保存起来,在需要使用对象的地方自动注入该对象。 

        public class A {

                @Autowire

                private B b;     <----------从容器中获取唯一对象注入给b----一对象---容器 《-----创建后保存到容器---- A对象、B对象

        }

二.什么是AOP 

        AOP英文全称为Aspect oriented Programming ,面向切面的编程方式。AOP是相对于OOP面向对象编程来比较的,面向对象的三个特点就是继承、封装和多态,是一种垂直架构。

        OOP面向切面编程就是对方法的增强,在不修改方法的前提下,在方法运行过程中添加通用的逻辑操作,将代码解耦。

三.自定义实现IOC和AOP的思想

        模拟一个银行转账的案例,通过案例我们来深入理解IOC和AOP的思想,以理解Spring框架的核心思想。

       我们首先定义一个传统的MVC工程,模拟银行转账案例。

1)创建一个JAVE EE项目,编写前端转账功能        

<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    转账案例


    
转账账号: 收款账号: 金额:

  2)创建数据库和pojo实体类       

CREATE TABLE USER_ACCOUNT(
    accound int,
    money int
)
public class Account {
    private int money;
    private String account;

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }
}

3)编写Servlet接收转账请求

@WebServlet("/transfer")
public class TransferServlet  extends HttpServlet {
    TransferService transferService = new TransferServiceImpl();
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
       doPost(request, response);
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response){
        String transferAccount = request.getParameter("transferAccount");
        String accessAccount = request.getParameter("accessAccount");
        int money = Integer.parseInt(request.getParameter("money"));

        try {
            int result = transferService.transfer(transferAccount, accessAccount, money);

            if (result == 2) {
                PrintWriter writer = response.getWriter();
                writer.println("成功");
            } else {
                PrintWriter writer = response.getWriter();
                writer.println("失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

4)编写Service类处理转账请求业务

public interface TransferService {
    int transfer(String transferAccount, String accessAccount, int money) throws Exception;
}

public class TransferServiceImpl implements TransferService {
    TransferDao transferDao = new TransferDao();

    @Override
    public int transfer(String transferAccount, String accessAccount, int money) throws Exception {
        Account transferAccountMoney = transferDao.getMoneyByTransferAccount(transferAccount);
        Account accessAccountMoney = transferDao.getMoneyByTransferAccount(accessAccount);
        int result = transferDao.updateMoney(transferAccount, transferAccountMoney.getMoney() - money);
        result += transferDao.updateMoney(accessAccount, transferAccountMoney.getMoney() + money);

        return result;
    }
}

5) 编写转账数据库DAO层

public class TransferDao {
    private final static String url = "";
    private final static String user = "";
    private final static String password = "";
    public Account getMoneyByTransferAccount(String transferAccount) throws ClassNotFoundException, SQLException {
        Account account = new Account();
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection(url, user, password);
        PreparedStatement preparedStatement = connection.prepareStatement("SELECT id, money, account FROM account_money WHERE account = ?");
        preparedStatement.setString(1, transferAccount);
        ResultSet rs = preparedStatement.executeQuery();
        while(rs.next()) {
            int money = rs.getInt("money");
            account.setAccount(transferAccount);
            account.setMoney(money);
        }
        return account;
    }

    public int updateMoney(String transferAccount, int money) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection(url, user, password);
        PreparedStatement preparedStatement = connection.prepareStatement("UPDATE account_money SET money = ? WHERE account = ?");
        int i = preparedStatement.executeUpdate();
        return i;
    }
}

这个案例使用了传统的MVC模式开发的项目,这样子编写存在什么问题?

一、代码耦合,每一层对上一层的调用都是用了new关键字,但是如果上层技术架构改变我们还要需要改变其他层的代码,不符合开闭原则。

二、DAO层中使用的数据库连接对象Connetion不是同一个对象,意味着这里开启了两个事务,而我们希望转账的操作是在同一个事务中,这样才能保证数据的一致性。

对以上的问题我们如何去解决?

问题一:代码耦合,各层级之间存在耦合

        方案一:我们可以使用设计模式的简单工厂模式与反射相结合,通过简单的参数传入反射相应对象,不直接关联该对象。因为Servlet 是单例多线程的,我们频繁的从工厂创建对象会造成性能问题,我们把创建的对象放入容器中,整个应用只存在一个实例,再需要的时候向容器获取,这样可以避免性能问题。

问题二:没有在service开启事务,并且在DAO层中开了两个事务

        方案一:使用JDK代理service层对象,对代码进行拦截增强。使用代理模式和工厂模式的结合,通过工厂获取到service代理对象,对service方法进行增强,在代理对象中开启事务和提交及回滚。

改进代码:

1、编写一个XML,维护我们需要从工程拿到的对象bean



    
        
    

    
        
    

    

2、编写一个FactoryBean,将配置文件中维护的对象放入一个Map集合中管理

public class FactoryBean {
    private static Map beanMap = new HashMap<>();
    static {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            InputStream resourceAsStream = FactoryBean.class.getClassLoader().getResourceAsStream("applicationContent.xml");
            Document xml = builder.parse(resourceAsStream);

            NodeList beans = xml.getElementsByTagName("bean");

            for (int i = 0; i < beans.getLength(); i++) {
                Node item = beans.item(i);
                NamedNodeMap attributes = item.getAttributes();
                String id = attributes.getNamedItem("id").getNodeValue();
                String classPath = attributes.getNamedItem("class").getNodeValue();

                // 处理bean标签
                Class aClass = Class.forName(classPath);
                Object o = aClass.newInstance();
                beanMap.put(id , o);
            }

            NodeList property = xml.getElementsByTagName("property");

            for (int i = 0; i < property.getLength(); i++) {
                Node item = property.item(i);
                NamedNodeMap attributes = item.getAttributes();
                String name = attributes.getNamedItem("name").getNodeValue();
                String ref = attributes.getNamedItem("ref").getNodeValue();

                // 要注入的节点
                Object diObj = beanMap.get(ref);

                // 找到父类节点
                Node parentNode = item.getParentNode();
                NamedNodeMap parentAttributes = parentNode.getAttributes();
                String id = parentAttributes.getNamedItem("id").getNodeValue();
                Object o = beanMap.get(id);

                Method method = o.getClass().getMethod("set" + name, diObj.getClass());

                method.invoke(o, diObj);

                beanMap.put(id , o);

            }

        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    /**
     * 根据id获取bean对象
     * @param name
     * @return: java.lang.Object
     * @author: 壮乡码农
     * @date: 2021/12/21 14:43
     */
    public static Object getBean(String name) {
        return beanMap.get(name);
    }
}

3、修改TransactionService代码,不直接new TransferDao, 而是仅仅声明该变量,提供一个set方法,让FactoryBean启动时注入对象

public class TransferServiceImpl implements TransferService {
    TransferDao transferDao;

    public void setTransferDao(TransferDao transferDao) {
        this.transferDao = transferDao;
    }

    @Override
    public int transfer(String transferAccount, String accessAccount, int money) throws Exception {
        Account transferAccountMoney = transferDao.getMoneyByTransferAccount(transferAccount);
        Account accessAccountMoney = transferDao.getMoneyByTransferAccount(accessAccount);
        int result = transferDao.updateMoney(transferAccount, transferAccountMoney.getMoney() - money);
        result += transferDao.updateMoney(accessAccount, transferAccountMoney.getMoney() + money);

        return result;
    }
}

 4、编写一个ConnectionUtil对象,让DAO层获取到的数据库连接为同一个连接

public class ConnectionUtil {
    private String user = "root";
    private String password = "123456";
    private String driver = "com.mysql.Driver";
    private String url = "jdbc.mysql://127.0.0.1:3306/test";
    ThreadLocal local = new ThreadLocal();

    public Connection getLocalConnect() throws ClassNotFoundException, SQLException {
        Connection connection = local.get();
        if (connection == null) {
              Class.forName(driver);
              connection = DriverManager.getConnection(url, user, password);

        }
        return connection;
    }
}

5、将ConnectionUitl放到容器中管理



    
    
        
    
    
    
    
    

6、修改DAO层TransfernDao , 使用ConnectionUtil工具获取数据库连接

public class TransferDao {
//    private final static String url = "";
//    private final static String user = "";
//    private final static String password = "";
    ConnectionUtil connectionUtil;

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    public Account getMoneyByTransferAccount(String transferAccount) throws ClassNotFoundException, SQLException {
        Account account = new Account();
//        Class.forName("com.mysql.jdbc.Driver");
//        Connection connection = DriverManager.getConnection(url, user, password);
        Connection connection = connectionUtil.getLocalConnect();
        PreparedStatement preparedStatement = connection.prepareStatement("SELECT id, money, account FROM account_money WHERE account = ?");
        preparedStatement.setString(1, transferAccount);
        ResultSet rs = preparedStatement.executeQuery();
        while(rs.next()) {
            int money = rs.getInt("money");
            account.setAccount(transferAccount);
            account.setMoney(money);
        }
        return account;
    }

    public int updateMoney(String transferAccount, int money) throws ClassNotFoundException, SQLException {
//        Class.forName("com.mysql.jdbc.Driver");
//        Connection connection = DriverManager.getConnection(url, user, password);
        Connection connection = connectionUtil.getLocalConnect();
        PreparedStatement preparedStatement = connection.prepareStatement("UPDATE account_money SET money = ? WHERE account = ?");
        int i = preparedStatement.executeUpdate();
        return i;
    }
}

7 、编写一个TrancationManger 对象,定义开启事务、提交事务、回滚事务等行为

public class TransactionManager {
    private ConnectionUtil connectionUtil;

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    public void beginTransaction() throws SQLException, ClassNotFoundException {
        Connection localConnect = connectionUtil.getLocalConnect();
        localConnect.setAutoCommit(false);
    }

    public void commit() throws SQLException, ClassNotFoundException {
        Connection localConnect = connectionUtil.getLocalConnect();
        localConnect.commit();
    }

    public void rollback() throws SQLException, ClassNotFoundException {
        Connection localConnect = connectionUtil.getLocalConnect();
        localConnect.rollback();
    }
}

 8、编写一个代理类工厂,在代理工程中开启事务,提交事务和回滚

public class ProxyFactory {
    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public Object getProxyObject(String beanName) {
        Object target = FactoryBean.getBean(beanName);
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 开启事务
                transactionManager.beginTransaction();
                Object o  = null;
                try {
                    o = invoke(proxy, method, args);
                } catch (Exception e) {
                    e.printStackTrace();
                    // 回滚事务
                    transactionManager.rollback();
                    throw e.getCause();
                } finally {
                    // 提交事务
                    transactionManager.commit();
                }
                return o;
            }
        });
    }
}

        通过改造之后,我们每一层的实例化对象定义在bean中,解耦了代码,通过ProxyFactory 在service开启事务。good! 

四. Spring框架的使用

1、创建一个工程,引入Spring 相关jar包jar包

jar包下载地址:JFrog

2、Spring 管理bean的几种方式(xml,xml+注解,注解)

在web.xml中配置Spring启动类和启动方式,

 
    
        contentConfigLocation
        classpath:application.xml
    
    
        org.springframework.web.context.ContextLoaderListener
    

    
    
        
        
    
    
        
        
    
    
        
    

3、xml方式配置bean

        引入Spring DTD约束头

         使用bean标签配置Spring 管理的bean对象。

bean标签可使用的标签属性如下:

  •     id: bean的唯一标识
  •     class:bean的全限定类名
  •     scope:singleton、prototype、request、session、application、websocket(六个可选值)
  •     init-method: bean被装在后执行的初始化方法,要求是无参的
  •     lazy-init: 延迟加载,当getBean之后再加载,这个配置只对singleton有效
  •     factory-bean: bean产生的工厂
  •     factory-method: 工厂生产bean的方法,可以配合class属性或则factory使用,使用class属      性的工厂对象啊必须是静态方法。
  •     destory-method: bean对象销毁前执行,scope为singleton有效



    
        
    
    

   DI-依赖注入相关的配置:

DI依赖注入的方式看,有两种方式:

  •  set方法注入
  • 构造器注入

从注入的类型来看:

  • Java 基本类型或是Stirng类型
  • 除了Java基本类型及String对象之外的其他对象(如List、Map、Set、Propertity、自定义对象)

相关标签:

1)set方法注入可以使用propety

property: bean对象中的依赖(关联)的属性,

         name: 关联的属性名称         

         value:  属性值,这列的值一般为Java 9种变量类型

        ref:   属性为一个对象,使用ref引用Spring bean 中的对象

2)构造器注入使用constructor-arg标签

constructor-arg:

        name : 构造器参数名称

        index:参数位置

        value: 构造器参数值

        ref: 属性为一个对象,使用ref引用Spring bean 中的对象

复杂对象的注入,再property中使用ref 引用其他对象,再property内使用map、set、list、props标签

3)注入一个Map集合

       

               

                

        

4)注入一个Set集合

       

                张三

                18

        

5)注入一个List对象

       

                张三

                李四

        

6)注入一个Properties对象

       

                张三

                李四

        

        上面几种集合类型中,properties、set、list类型都是使用value标签,value标签除了注入基本的数据类型之外还可以使用ref属性引用一个Spring bean管理的对象,map的类型使用entry标签向map中注入数据,也可以使用value-ref 向map中注入Spring bean管理的对象。

7)、注解方式使用bean

        

xml标签 注解

@Conpoment

也可以使用

@Service

@Controller

@Repository

 这几个标签和@Conpoment 效果一致,只是在程序上更好读

五. Spring框架的三个高级使用

1、延迟加载Lazy_init

        延迟加载的使用,只需要在 标签中将lazy_inti 属性设置为true,设置了延迟加载的bean会再第一次getBean的时候才会进行创建。如果其他bean中依赖(关联)了lazy_init 为true的对象,也会调用getBean方法,这时候就会对bean进行创建。

2、FactoryBean 工厂bean

        FactoryBean定义了bean定义了bean的生产细节。

public interface FactoryBean {
	
	String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";


	/**
	 * 获取FactoryBean对象产生的对象,如果 isSingleton() 返回true,则bean是单例的,放入容器Map中维护
	 * @return: T
	 * @author: 壮乡码农
	 * @date: 2021/12/21 15:20
	 */
	@Nullable
	T getObject() throws Exception;

	/**
	 * 获取FactoryBean创建的bena类型 
	 * @return: Class
	 * @author: 壮乡码农
	 * @date: 2021/12/21 15:21
	 */
	@Nullable
	Class getObjectType();

	/**
	 * 是否是单例的
	 * @return: boolean
	 * @author: 壮乡码农
	 * @date: 2021/12/21 15:20
	 */
	default boolean isSingleton() {
		return true;
	}

}

使用案例:

1)定义一个类,这个类有FacoryBean工厂对象生产

/**
 * TODO
 *
 * @author 壮乡码农
 * @version 1.0
 * @date 2021/12/21 15:30
 */
public class Product {
    private String name;
    private String describle;


    public String getName() {
        return name;
    }

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

    public String getDescrible() {
        return describle;
    }

    public void setDescrible(String describle) {
        this.describle = describle;
    }
}

2)定义一个工厂方法实现FactoryBean

public class ProductFactoryBean implements FactoryBean {
    String productName;
    @Override
    public Product getObject() throws Exception {
        Product product = new Product();
        product.setDescrible("我是产品,通通过ProductFactoryBean生产");
        product.setName(productName);
        return product;
    }

    @Override
    public Class getObjectType() {
        return Product.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

3) 修改配置文件


        

 3)测试使用

public class FactoryBeanTest {

    public static void main(String[] arg) {
        ConfigurableApplicationContext configurableApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 获取实例对象
        Product product = (Product) configurableApplicationContext.getBean("product");

        System.out.println(product.getName());

        // 获取对象类型
        Object roduct = configurableApplicationContext.getBean("&product");

        System.out.println(roduct.getClass().getTypeName());
    }

}

3、后置处理器 BeanPostProcessor 和 BeanFactoryPostProcessor

1)BeanPostProcessor 

        BeanPostProcessor 针对bean进行处理,可以在两个方法中判断beanName对单个bean做处理。

public interface BeanPostProcessor {

	/**
	 * bean容器加载了bean,在bean执行init-method之前
	 * @param bean
	 * @param beanName
	 * @return: Object
	 * @author: 壮乡码农
	 * @date: 2021/12/21 16:10
	 */
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	/**
	 * bean容器加载了bean,执行了init-method方法之后执行
	 * @param bean
	 * @param beanName
	 * @return: Object
	 * @author: 壮乡码农
	 * @date: 2021/12/21 16:11
	 */
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

2)BeanFactoryPostProcessor

        BeanFactoryPostProcessor针对整个bean工厂进行处理。

public interface BeanFactoryPostProcessor {

	/**
	 * bean实例化之后调用,这里可以修改bean的属性也可以初始化bean
     * @param beanFactory
     * @return: void
     * @author: 壮乡码农
     * @date: 2021/12/21 16:23
	*/
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

六.Spring 循环依赖的问题及处理

        Spring 循环依赖就是有两个bean对象相互依赖(关联)对方。BeanA 中依赖BeanB,BeanB中依赖BeanC,BeanC中依赖BeanA最终形成了闭环。

        为什么会产生循环依赖?我们有两个对象A和B,A中依赖了B,B中也依赖了A。这时候我们new A对象的时候,A依赖了B也把B实例化了,B实例化后B里面又依赖了A,又把A实例化了,如此反复,直到内存溢出。

public class A{
    public B = new B();
}

public class B{
    public B = new A();
}

Spring 应用及原理分析_第1张图片

Spring 中循环依赖场景有:
  • 构造器的循环依赖(构造器注⼊)
  • Field 属性的循环依赖(set注⼊)
注意,构造器的循环依赖问题⽆法解决、bean的singleton为protoytpe的循环依赖无法法解决,会拋出 BeanCurrentlyInCreationException 异常。Spring只能解决set注入的依赖,并且是单例的,在解决属性循环依赖时,Spring采⽤的是提前暴露对象的⽅法。Sping 再对bean进行实例化的时候,通过ObjectFactory提前暴露了实例化的对象。Java再实例化对象的时候,可以先获得引用,之后再对属性进行赋值。
构造构成可以认为:
1)实例化A对象,发现A中依赖了B对象,将A通过ObjectFactory提前暴露,尝试从容器中获取B对象,B对象不存在,不存在就去实例化啊
2)实例化B对象实例化,发现B依赖了A对象,先看看能不能从容器中拿到A对象,发现A对象已经存在容器中,从容器中拿到A对象,对B对象进行注入
3)A对象从容器中获取B对象,注入。
源码分析:
Spring 应用及原理分析_第2张图片

 先不分析了,头疼,忘记了,以后再分析一次。

七.Spring AOP

 1、面向切面变成的基本概

概念 白话解释
连接点 能够增强的方法都可以是连接点
切入点 已经接入的增强方法的连接点就成为了切入点
切面 定义增强行为的类,就是切面
通知/增强 增强的方法执行的时机,如前置通知、后置通知、环绕通知、异常通知
目标对象 被代理的对象,只有被代理了才能够进行横切逻辑
代理 代理的对象
织入 把增强代码和目标对象连接起来的过程,就是织入

2、Spring 面向切面编程开启方式

1)引入jar包,Spring 是模块化开发,用什么就引入什么


     org.springframework
     spring-aop
     5.1.12.RELEASE
 
     org.aspectj
     aspectjweaver
     1.9.4

2) 在Spring 配置文件中添加约束头

xmlns:aop="http://www.springframework.org/schema/aop"
 http://www.springframework.org/schema/aop 
https://www.springframework.org/schema/aop/spring-aop.xsd

3)将切面对象交给Spring管理

id = "logUtil" class = "com.lagou.utils.LogUtil" >
4)配置AOP相关的配置,如切面、接入点、通知等,使用
标签 说明

定义切面

id:  唯一的全局切面标识
ref: 引用的bean对象

前置通知

正常执⾏时通知,执行完毕后执行

returning>

后置通知

异常通知

after-throwing>

环绕通知

配置通通知

methods: 增强的方法

pointcut: 切入点表达式,定义哪些类的方法可以被拦截

切入点表达式说明:

public * ....(..)

从左到右,分别为

访问权限 返回值  包.类名.方法名(参数)

1)访问权限可以忽略

2)返回值可以用*表示,*代表任意返回值都可以

3)包名,包名可能是多级的如com.sql这样的形式,可以用.表示任意包

4)类名,类型可以使用.来代替,表示任意类

5)方法名,方法名可以使用.来代替,表示任意方法

6)参数,参数可以用..来代替,..代表任意参数都可以,也可以指定参数的全限定名称

开启Spring 对AOP注解的支持

4)面向切面使用案例

  • 再XML开启对注解的支持
  • 编写切面类
@Component // 交给Spring 为互这个类
@Aspect // 定义切面
public class LogAspect {

    // 拦截切入点,拦截访问修饰符为public 任意返回值 service.Transfer
    @Pointcut("execution(public * service.TransferService..(..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置通知");
    }

    @After("pointcut()")
    public void after() {
        System.out.println("后置通知");
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) {
        System.out.println("环绕通知");
        //定义返回值
        Object rtValue = null;
        try{
            //前置通知
            System.out.println("前置通知");
            System.out.println("环绕通知");
            //1.获取参数
            Object[] args = pjp.getArgs();
            //2.执⾏切⼊点⽅法
            rtValue = pjp.proceed(args);
            //后置通知
            System.out.println("后置通知");
            System.out.println("环绕通知");
        }catch (Throwable t){
            //异常通知
            System.out.println("异常通知");
            t.printStackTrace();
        }finally {
            //最终通知
            System.out.println("最终通知");
        }
        return rtValue;
    }

    @AfterThrowing
    public void exception(Throwable e) {
        System.out.println("异常通知");
    }

    @AfterReturning
    public void afterReturning(Object rtValue) {
        System.out.println("执行完毕后执行");
    }
}

八、Spring 声明式事务

1、什么是声明事事务?

        在业务代码中编写事务的代码,这样的事务是编程式事务。通过配置文件或则注解达到事务控制的目的,这个就是声明式事务

2、事务的概念

        事务的四大特征

特征 说明
原子性   一个事务里面的操作,要么全部完成,要么都不做
一致性 一个状态改变到另外一个状态,前后的总量应该是一致的。如转账1000元,转账后两个账户的金额总额是一致的,不会凭空多出也不会平凭空减少
隔离性 一个事务不会被另外一个事务所干扰
持久性 数据库中的数据改变是永久性的

        事务的隔离级别:

                         数据库事务在没有隔离级别的情况下可能发生的情况!!

脏读 一个事务读取了另外一个事务未提交的数据,当另外一个事务回滚后,这个事务读到的就是脏数据
不可重复读 一个事务读取了数据,数据未结束,此时另外一个事务修改了数据,这个事务在此读取该数据,得到了前后不一致的数据
幻读 一个数据读取了数据库中id<10的数据,此时事务还没有结束,另外一个数据又插入了或则删除了id<10的数据,这个事务再去读取,得到的结果集数量不一致

        针对这几种情况,数据库中提供了集中事务的隔离级别:

读未提交 脏读、不可重复读、幻读都有可能发送
读已提交        可以避免脏读。但是在读事务的时候,还允许另外一个事务的进行写操作和更新操作,所以不能避免不可重复读和幻读。大部分的数据默认的事务隔离级别。
可重复读 可以避免脏读、不可可重复读的问题,一个事务中没有提交,会对update操作进行锁定,不能进行更新操作,但是不能避免幻读。MySQL默认的事务级别。
序列化 事务一个一个的执行,不能并行运行,可避免脏读、不可重复读与幻读

3、事务的传播

        事务的隔离级别是数据库级别的,我们在编写的代码的时候,往往会存在着事务的传播问题。比如我们在service开启了事务,serviceA 调用了serviceB ,这时候就存在了事务的传播。

        我们站在serviceB的角度,来看事务的传播行为:

PROPATATION_REQUIRE 如果当前没有事务,就创建一个事务,如果当前有事务,那就加入该事务
PROPATATION_SUPPORTS 如果当前有事务,就加入该事务,没有就以非事务的方式执行
PROPATATION_MANDRTOR 如果有事务就加入事务,没有就抛出异常
PROPATATION_NEW_REQURIRE 新建一个事务,如果当前有事务,就挂起当前事务
PROPATATION_NOT_SUPPORTS 以非事务的方式执行,如果当前存在事务,就挂起当前事务
PROPATATION_NEVER 以非事务的方式执行,如果存在当前事务,就抛出异常
PROPATATION_NESTED 如果当前存在事务,就嵌套在当前事务中执行,没有就新建一个事务

4、Spring 事务的API

        Spring本身不支持事务的实现,但是定义了事务的标准。

public interface PlatformTransactionManager extends TransactionManager {

	/**
	 * 获取当前事务装填信息
	 * @param definition
	 * @return: TransactionStatus
	 * @author: 壮乡码农
	 * @date: 2021/12/22 14:40
	 */
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException;

	/**
	 * 提交事务
	 * @param status
	 * @return: void
	 * @author: 壮乡码农
	 * @date: 2021/12/22 14:40
	 */
	void commit(TransactionStatus status) throws TransactionException;

	/**
	 * 回滚事务
	 * @param status
	 * @return: void
	 * @author: 壮乡码农
	 * @date: 2021/12/22 14:40
	 */
	void rollback(TransactionStatus status) throws TransactionException;

}

        需要特别说明的是,这个类是一个策略类,Spring使用了策略模式,不用的框架有着不同的实现。

5、使用XML+注解的方式开启事务

1)导入jar包


 org.springframework
 spring-context
 5.1.12.RELEASE
 
 org.aspectj
 aspectjweaver
 1.9.4
 
 org.springframework
 spring-jdbc
 5.1.12.RELEASE
 
 org.springframework
 spring-tx
 5.1.12.RELEASE

2)将事务管理器交由Spring管理

       

               

                name="dataSource" ref="dataSource">

        

3)开启Spring 对事务注解的支持

transaction-manager="transactionManager"/>

4)再需要启动事务的service添加@Transactional注解

@Transactional(readonly=Boolean.True, propagation= Propagation.REQUIRE)

readonly : 只读事务

propagation: 事务的传播行为

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