Spring学习笔记

1.Spring概述

1.1什么是框架?

框架(Framework) :框(指其约束性)架(指其支撑性),在软件设计中指为解决一个开放性问题而设计的具有一 定约束性的支撑结构。 在此结构上可以根据具体问题扩展、安插更多的组成部分,从而更迅速和方便地构建完整的解决问题的方案。

1、框架本身一般不完整到可以解决特定问题
2、框架天生就是为扩展而设计的
3、框架里面可以为后续扩展的组件提供很多辅助性、支撑性的方便易用的实用工具(utilities) ,也就是说框架时常配套了一些帮助解决某类问题的库(libraries)或工具(tools)。

如何学习框架呢?

1、知道框架能做什么
2、学习框架的语法,-般框架完成-个功能需要一 定的步骤
3、框架的内部实现原理(扩展)
4、尝试实现一个框架(提升)|

1.2 Spring是什么

Spring官网https://spring.io

Spring被称为j2EE的春天,是一个是分层的Java SE/EE full-stack开源的轻量级的Java开发框架,是最受欢迎的企业级Java应用程序开发框架,数以百万的来自世界各地的开发人员使用Spring 框架来创建性能好、易于测试、可重用的代码。

Spring具有控制反转(loC)面向切面(AOP) 两大核心。Java Spring框架通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

Spring框架不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring 框架还是一个超级粘合平台,

除了自己提供功能外,还提供粘合其他技术和框架的能力。

1.3 Spring的优势

1、方便解耦,简化开发
	Spring就是一一个大工厂 ,可以将所有对象的创建和依赖关系的维护交给Spring 管理。
	
2、方便集成各种优秀框架
	Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts2、 Hibernate、 MyBatis等)的直接支持。

3、降低Java EE API 的使用难度
	Spring对Java EE开发中非常难用的一些API (JDBC、JavaMail、 远程调用等)都提供了封装,使这些API 应用的难度大大降低。

4、方便程序的测试
	Spring支持JUnit4,可以通过注解方便地测试Spring 程序。

5、AOP编程的支持
	Spring提供面向切面编程,可以方便地实现对程序进行权限拦截和运行监控等功能。

6、声明式事务的支持T
	只需要通过配置就可以完成对事务的管理,而无须手动编程。

1.4 Spring的体系结构

Spring为我们提供了一站式解决方案, 但Spring 是模块化的,允许咱们挑选和选择适用于项目的模块,不需要把剩余部分也引入。

Spring框架提供约20个模块,可以根据应用程序的要求来选择。

在这里插入图片描述

1.4.1核心容器(Core Container)

2、IoC控制反转

2.1IOC的概念

loc-Inversion of Control,即"控制反转”,不是什么技术,而是一种设计思想。

loC是指在程序开发中,实例的创建不再由调用者管理,而是由Spring容器创建。Spring 容器会负责控制程序之间的关系,而不是由程序代码直接控制,因此,控制权由程序代码转移到了Spring容器中,控制权发生了反转,这就是Spring的loC思想。

创建实例/对象的方式:构造方法new 反射 克隆 序列化 动态代理

上述方式都是由调用者进行操作

javaweb servlet 中—doGet/doPost方法–实例方法,按理说需要通过对象名.doGet调用,实际tomcat服务器(容器,创建了对象servlet),这里就是一种IOC的思想。

2.2 Spring入门案例

  • 创建maven项目

创建完毕的目录结构

在这里插入图片描述

  • pom.xml文件添加依赖和插件
		<!-- 测试依赖-->
		<dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

		<!-- spring核心依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>

		<!-- spring-JDBC依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>

		<!--        mysql数据库连接依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>

		<dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>


    <build>
        <plugins>
            <!--  tomcat插件-->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <!--                    <configuration>-->
                <!--                        <port>8080</port>-->
                <!--                    </configuration>-->
            </plugin>
            
			 <!--  编译插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>

        </plugins>
    </build>
  • 创建一个实体类
public class Team {
    private Integer id;
    @Value("湖人队")//本质是通过set方法赋值,此标记写在set方法上效果也一样
    private String name;
    @Value("洛杉矶")
    private String location;

    public Team(){
        System.out.println("Team - 默认的构造方法 id = " + id+",name  = "+name+",location = "+location);
    }
}
  • 创建Spring的配置文件application.xml
文件夹:src/main/resourcses/ ->new -> XML Configuration File -> Spring Config -> 命名为application.xml

使用Spring容器创建对象

配置文件application.xml内容(一个bean标签就表示创建了一个对象):

<?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="team1" name="team1" class="com.kkb.pojo.Team"></bean>
</beans>

Spring提供两种容器,分别是BeanFactory和ApplicationContext

BeanFactory
BeanFactory是基础类型的IoC容器,是一个管理Bean的工厂,它主要负责初始化各种Bean,并调用它们的生命周期方法。
BeanFactory接口有多个实现类,最常见的是org.Springframwork.beans.factory.xml.XmlBeanFactory,它根据XML配置文件中的定义装配Bean。
语法:
BeanFactory b = new XmlBeanFactory(new FileSystemResource(Spring的配置文件名称—绝对路径));
	@Test
    public void test01_2(){
        //创建工厂类读取文件(绝对路径),此方法已过时 
        String path = "D:/WorkSpace/myCode/MySpring/spring01/src/main/resources/application.xml";
        XmlBeanFactory beanFactory = new XmlBeanFactory( new FileSystemResource(path));
        beanFactory.getBean("team1");//根据ID从IOC容器获取对象
    }
ApplicationContext
ApplicationContext是BeanFactory的子接口,也被称为应用上下文,它不仅提供了BeanFactory的所有功能,还添加了对i18n(国际化)、资源访问、事件传播等方面的良好支持。

ApplicationContext接口有两个常用实现类:ClassPathXmlApplicationContext(常用-推荐写法)、FileSystemXmlApplicationContext

1ClassPathXmlApplicationContext 该类从类路径ClassPath中寻找指定的XML配置文件,找到并装载完成ApplicationContext的实例化工作。
    String springConfig = "application.xml";
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(springConfig);
	Team team1 = (Team) applicationContext.getBean("team1");

2FileSystemXmlApplicationContext 它与ClassPathXmlApplicationContext的区别在于:在读取Spring的配置文件时,不再从类路径中读取配置文件,而是通过参数指定配置文件的位置,可以获取类路径之外的资源(支持绝对路径)
    String path = "D:/WorkSpace/myCode/MySpring/spring01/src/main/resources/application.xml";
    ApplicationContext applicationContext = new FileSystemXmlApplicationContext(path);
	Team team1 = (Team) applicationContext.getBean("team1");
ApplicationContext的其它API
1.getBeanDefinitionCount:获取对象个数

		int count = applicationContext.getBeanDefinitionCount();
        System.out.println("容器中对象的个数:"+count);

2.getBeanDefinitionNames:获取所有对象的name

        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        System.out.println("容器中的所有对象的名称:");
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }

创建非自定义对象

<!--    非自定义对象的创建-->
    <bean id="date1" class="java.util.Date"></bean>
//非自定义的对象获取
        Date date1 = (Date) applicationContext.getBean("date1");
        System.out.println("日期:"+date1);

bean标签的属性

属性说明
class完全限定名,指定bean对应类的全路径
name自定义的名称,name是bean对应对象的一个标识
scopescope=“singleton/prototype”,默认值为singleton,执行bean对象创建模式和生命周期。
singleton: 单例,容器在启动完毕的时候单例对象就已经被创建了,并且容器中只有一个单例对象.
prototype: 多例,对象什么时候使用什么时候创建,每次创建都是new一个新的对象.
id对象名,要求唯一,id是bean对象的唯一标识,不能添加特别字符
lazy-initlazy-init=“true/false”,默认值为false。表示是否延时加载(懒加载)。
true: 懒加载,针对单例对象——当对象被调用的时候才会创建对象,多例对象本身就是懒加载。
false:不延迟,无论对象是否被使用,都会立即创建对象只需要加载配置文件即可。注意:测试的时候只留下id,class属性
init-method对象创建时执行的方法-自定义,init-method=“方法名”
destroy-methodspring容器销毁时需要执行的方法-自定义,destroy-method=“方法名”

2.3Spring容器创建对象的方式

(1)使用默认的构造方法

上面的例子调用的就是默认无参构造方法

(2)使用带参数的构造方法

Team添加一个三参构造方法:
	    public Team(Integer id, String name, String location) {
        this.id = id;
        this.name = name;
        this.location = location;
    }
根据参数名赋值
<bean id="team1" name="team1" class="com.kkb.pojo.Team">
    <constructor-arg name="id" value="001"></constructor-arg>
    <constructor-arg name="name" value="勇士队"></constructor-arg>
    <constructor-arg name="location" value="金州"></constructor-arg>
</bean>

或者根据参数下标按顺序赋值
<bean id="team1" name="team1" class="com.kkb.pojo.Team">
    <constructor-arg index="0" value="001"></constructor-arg>
    <constructor-arg index="1" value="勇士队"></constructor-arg>
    <constructor-arg index="2" value="金州"></constructor-arg>
</bean>

(3)使用工厂类

首先创建一个工厂类

public class MyFactory {
    /**
     * 实例方法:必须先创建Team的实例,通过:对象名.instancceFun才能访问此方法
     * @return
     */
    public Team instanceFun(){
        return new Team(1003,"湖人","洛杉矶");
    }
    /**
     * 静态方法 :可以直接通过MyFactory.staticFun访问此方法
     * @return
     */
    public static Team staticFun(){
        return new Team(1004,"小牛","达拉斯");
    }
}
调用工厂类的静态方法:等价于代码:MyFactory.staticFun();
	<bean id="InstanceTeam" class="com.kkb.pojo.MyFactory" factory-method="staticFun"></bean>

调用工厂类的实例方法,要先创建工厂实例:
	先创建工厂的对象
	<bean id="MyFactory1" class="com.kkb.pojo.MyFactory"></bean>
	调用工厂对象中的对应方法
	<bean id="StaticTeam" factory-bean="MyFactory1" factory-method="instanceFun"></bean>

2.4DI

DI—Dependency Injection,即"依赖注入":是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、 可展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

loC是一个概念,是一种思想,其实现方式多种多样。依赖注入就是其中用的比较多的一种方式。

loc和DI是同一个概念的不同角度描述。loC是一 种思想, 概念,DI是 实现它的手段。Spring框架使用依赖注入实现loC.

Spring容器是一个超级大工厂, 负责创建、管理所有的Java对象,这些Java对象被称为Bean。Spring 容器管理着容器中Bean之间的依赖关系,Spring 使用"依赖注入”的方式来管理Bean之间的依赖关系。使用loC实现对象之间的解耦和。

什么叫注入

bean实例在调用无参构造器创建对象后,要对bean对象的属性进行初始化。初始化是由容器自动完成的,称为注入。

通过set方法

set注入也叫设值注入是指,通过setter方法传入被调用者的实例。这种注入方式简单、直观,因而在Spring的依赖注入中大量使用。

举例:如下代码,要求:将TeamService的对象属性初始化赋值

public class TeamDao {
    public void add(){
        System.out.println("TeamDao---------add--------");
    }

    public TeamDao() {
        System.out.println("默认的无参构造方法----此对象创建成功");
    }
}

public class TeamService {
	TeamDao teamDao;
        public TeamService(TeamDao teamDao) {
        this.teamDao = teamDao;
    }
    public TeamService() {
        System.out.println("默认的TeamService无参构造方法---------");
    }
    public void add(){
        teamDao.add();
        System.out.println("TeamService---------add----");
    }
    public TeamDao getTeamDao() {
        return teamDao;
    }
    public void setTeamDao(TeamDao teamDao) {
        this.teamDao = teamDao;
    }
}
<!--     使用set方法注入属性值-->
<bean id="teamDao001" class="com.kkb.dao.TeamDao"></bean>

<bean id="teamService" class="com.kkb.service.TeamService">
	<!--name:调用者的属性名(调用此属性的set方法),ref:被调用者的bean id-->
    <property name="teamDao" ref="teamDao001"></property>
</bean>

通过构造方法

构造注入是指,在构造调用者实例的同时,完成被调用者的实例化。使用构造器设置依赖关系。

<!--    使用构造方法注入属性值-->
<bean id="teamService2" class="com.kkb.service.TeamService">
     <constructor-arg name="teamDao" ref="teamDao001"></constructor-arg>
</bean>

自动注入

对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为标签设置autowire属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。根据自动注入判断标准的不同,可以分为两种:

byName:根据名称自动注入

byType:根据类型自动注入

byName

当配置文件中被调用者bean的id值与代码中调用者bean类的属性名相同时,可使用byName 方式,让容器自动将被调用者bean注入给调用者bean。容器是通过调用者的bean类的属性名与配置文件的被调用者bean的id进行比较而实现自动注入的。

<!--   自动注入-->  
	<bean id="teamDao" class="com.kkb.dao.TeamDao"></bean>
<!--        使用byName,在容器中查找 跟class表示的类 所需的属性 名字相同的对象 进行注入,发现上面有个bean的id刚好是teamDao,于是匹配成功-->
    <bean id="teamService3" class="com.kkb.service.TeamService" autowire="byName"></bean>
byType

使用byType方式自动注入,要求:配置文件中被调用者bean的class属性指定的类,要与代码中调用者bean类的某引用类型属性类型同源。即要么相同,要么有is-a关系(子类,或是实现类)。但这样的同源的被调用bean在配置文件中只能有一个。多于一个,容器就不知该匹配哪一个了。

    <bean id="teamDao" class="com.kkb.dao.TeamDao"></bean>
<!--        使用btType,在容器中查找跟class中所需的属性类型相同的对象进行注入,要求此类型的对象只能有一个,否则无法区分要使用哪一个导致报错-->
    <bean id="teamService4" class="com.kkb.service.TeamService" autowire="byType"></bean>

2.5基于注解实现IOC

(1)声明Bean的注解@Component

使用@Component注解标记类。表示这个类的对象由Spring容器创建
@Component(value="")//value属性表示,当只有一个属性时,value可以省略;值也可以省略,默认为类名(首字母小写)
public class Team {}
@Component有三个子注解:使用效果一样
	@Repoitory:通常标识在Dao类,表示持久层对象
	@Service:通常标识在Service类,表示业务层对象,业务层对象可以加入事务功能
	@Controller:通常标识在Controller类,表示控制层对象,控制层对象可以作为处理器接收用户的请求

(2)包扫描

要使得此注解生效,需要让spring进行包扫描,需要在xml中做出如下配置

<!--beans中添加:-->
<beans
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd>
</beans>
<!--告知Spring要扫描的包-->
<!--      这些包以及及子包当中的类上如果添加了@Component注解,这些添加了注解的类就交给spring容器创建对象-->
<context:component-scan base-package="com.kkb.dao"></context:component-scan>
<!--    如果需要扫描多个包,扫描多个包的方式如下:-->
<!--        多个包的扫描:方式1 :使用多个<context:component-scan表明-->
            <context:component-scan base-package= "com.kkb.dao"></context:component-scan>
            <context:component-scan base-package= "com.kkb.service"></context:component-scan>
            <context:component-scan base-package="com.kkb.controller"></context:component-scan>


<!--        多个包的扫描:方式2 : base-package 中直接声明要扫描的多个包,多个值用逗号,分号或者空格分割,但是空格不推荐-->
            <context:component-scan base-package= "com.kkb.dao, com.kkb.service,com.kkb.controller"></context:component-scan>

<!--        多个包的扫描: 方式3: base-package中直接声明要扫描的多个包的父包-->
            <context:component-scan base-package="com.kkb"></context:component-scan>

(3)属性注入

  • 通过注解实现属性注入,有三种方式
    • @Value
    • @Autowired和@Qualifier
    • @Resource
@Value(value="")
在基础类型属性上(或者其set方法上,效果一样)使用该注解,value属性表示要注入的值,类中不需要有set方法,如果有,也可以放在set方法上。
//按类型自动装配
@Autowired
	//@Autowired(required = false):如果类型匹配失败,将该属性值设置为null,继续运行,会出现空指针异常.
	//@Autowired(required = true):默认情况,匹配失败直接报错,终止程序运行.
在引用类型属性上使用该注解,默认使用按类型自动装配。扫描Spring容器中的类,找到该属性类型的bean,并装配。类中不需要有set方法,如果有,也可以放在set方法上。
如果要实现按name装配,则需要搭配@Qualifier("")一起使用
@Resource			同上:可以在属性上,也可以在set方法上
    Spring中提供了对jdk中的@Resource注解的支持(必须是jdk1.6以上)。
    @Resource注解既可以按照名称匹配Bean,也可以按照类型匹配Bean。默认是按名称。
   		@Resource注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入bean,则会按照类型进行匹配
   		@Resource注解若执行其name属性,则name的值即为按照名称进行匹配的Bean的id。
   		
//举例:
@Controller
public class TeamController {
    @Resource(name="teamService",type = TeamService.class) //JDK中的注解,要求JDK版本高于1.6,无参数时:自动装配默认按名称,但是如果名称没有相符的,就按照类型自动装配.
    private TeamService teamService;
}

4、AOP面向切面编程

4.1AOP概述

AOP:Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP的作用:不修改源码的情况下,在程序运行期间对方法进行功能增强

好处: 1、减少代码的重复,提高开发效率,便于维护

​ 2、专注核心业务的开发

4.2先了解一下静态代理

//代理类接口
public interface AOP {
    void before();
    void after();
    void exception();
    void myFinally();
}
//日志代理类
public class LogAOP implements AOP{
    @Override
    public void before() {
        System.out.println("日志---------before");
    }
    @Override
    public void after() {
        System.out.println("日志---------after");
    }
    @Override
    public void exception() {
        System.out.println("日志---------exception");
    }
    @Override
    public void myFinally() {
        System.out.println("日志---------myFinally");
    }
}
//事务代理类
public class TranAOP implements AOP{
    @Override
    public void before() {
        System.out.println("事务---------before");
    }
    @Override
    public void after() {
        System.out.println("事务---------after");
    }
    @Override
    public void exception() {
        System.out.println("事务---------exception");
    }
    @Override
    public void myFinally() {
        System.out.println("事务---------myFinally");
    }
}
//被代理类,核心业务写在这里
//目的,不改此方法的内容,强化此方法的功能,让其实现日志和事务的功能
public class TeamService implements IService{
    public void add(){
        System.out.println("TeamService-------add-----");
    }
}
//代理封装业务:传入参数1:被代理对象,参数2:代理类,实现代理业务
public class ProxyAOPService implements IService {
    private IService iService;
    private AOP aop;
    public ProxyAOPService(IService iService, AOP aop) {
        this.iService = iService;
        this.aop = aop;
    }
    @Override
    public void add() {
        try{
            aop.before();
            iService.add();//被代理对象干活
            aop.after();
        }catch (Exception e){
            aop.exception();
        }finally {
            aop.myFinally();
        }
    }
}
//测试类:
  @Test
    public void test02(){
        TeamService teamService = new TeamService();//被代理对象-核心内容
        AOP logAOP = new LogAOP();//切面-服务性内容
        AOP tranAOP = new TranAOP();//切面,服务性内容,进行二级代理
        ProxyAOPService logAOPService = new ProxyAOPService(teamService,logAOP);//一级代理
        ProxyAOPService tranAOPService1 = new ProxyAOPService(logAOPService,tranAOP);//二级代理
        tranAOPService1.add();
    }
  • 优点:

    • 静态代理可以实现,在不改变实现核心业务的方法内容的情况下,对该方法的功能进行增强。
  • 缺点:

    • 代理对象需要与目标都需要实现一样的接口,所以每需要实现一个新的代理功能,就需要创建新的代理类。
    • 一旦接口增加方法,目标对象与代理对象都要维护。

4.3动态代理

AOP的实现机制为动态代理

  • 动态代理与静态代理的区别
    • 静态代理要求代理类必须存在
    • 动态代理在程序运行的时候,根据要被代理的对象动态生成代理类
  • 动态代理的类型
    • 基于JDK的动态代理
    • 基于CGLIB的动态代理

基于JDK的动态代理

使用jdk提供的Proxy类实现动态代理,格式:

public static Object newProxyInstance(ClassLoader loader, Class<?> [] interfaces, InvocationHandler h)
    参数:
    	ClassLoader loader 		:被代理类的加载器
   		Class<?> [] interfaces	:被代理类的参数列表
   		InvocationHandler h		:回调函数,编写代理规则
//ProxyFactory类
public class ProxyFactory {
    private IService service;
    private AOP aop;

    public ProxyFactory(IService service, AOP aop) {
        this.service = service;
        this.aop = aop;
    }

    public Object getProxyInstance(){
        return Proxy.newProxyInstance(
                service.getClass().getClassLoader(), //参数1,被代理对象类加载器
                service.getClass().getInterfaces(),  //参数2,被代理对象的接口列表
                new InvocationHandler(){ //参数3,回调函数,编写代理规则
                    /**
                     * @param proxy 代理类对象
                     * @param method 代理方法
                     * @param args 代理方法的参数
                     *
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        try{
                            aop.before();
                            Object invoke1 = method.invoke(service, args);//代表核心业务的位置
                            aop.after();
                            return invoke1;
                        }catch (Exception e){
                            aop.exception();
                            e.printStackTrace();
                            throw e;
                        }finally {
                            aop.myFinally();
                        }
                    }
                }
        );
    }
}

//测试类
public static void main(String[] args) {
        //目标对象-被代理对象
        TeamService teamService = new TeamService();
        //切面
        AOP tranAOP = new TranAOP();
        LogAOP logAOP = new LogAOP();
        //根据目标对象.返回代理对象--基于JDK的动态代理
        IService proxyService = (IService) new ProxyFactory(teamService,tranAOP).getProxyInstance();//一级代理,增强事务功能
        IService proxyService1 = (IService) new ProxyFactory(proxyService, logAOP).getProxyInstance();//二级代理,增强日志功能

        //此时proxyService1就是返回的二级代理对象,它的add方法就是功能被增强之后的add方法。
        //代理对象干活
        proxyService1.add();
        System.out.println(teamService.getClass());
        System.out.println(proxyService.getClass()+"------");
        System.out.println(proxyService1.getClass()+"------");
    }

使用Proxy进行动态代理,代理对象不需要实现接口,但是目标对象(被代理对象必须要实现接口,否则不能使用JDK动态代理。

那么,如果想要进行功能扩展,但是目标对象(被代理对象)没有实现接口,怎么进行功能扩展呢?

基于CGLIB的动态代理

用过子类的方式实现代理,CGLIB

Cglib代理,也叫做子类代理。在内存中构建一个子类对象从而实现对目标对象功能的扩展。

  • JDK的动态代理有一个限制, 就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。
  • CGLIB是 一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口,它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception (b) 。
  • CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不建议直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

使用前引入依赖

<dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.2.2</version>
</dependency>
//定义一个没有实现接口的被代理对象
public class NBAService {
    public int add(String name,int id){
        System.out.println("NBAService----add--id="+id+",name="+name);
        return id;
    }
}

cglib中使用Enhancer.create方法进行动态代理,该方法的参数(该方法有重载,如果是实现了接口的类要实现多级代理,也可以传入接口列表)
	Class type			:被代理的类
	Callback callback	:回调函数
其中Callback是个接口,其实现类intercept
//实例:
public class CglibProxyFactory {
    private NBAService nbaService;
    private AOP aop;
    public CglibProxyFactory(NBAService nbaService, AOP aop) {
        this.nbaService = nbaService;
        this.aop = aop;
    }
    public Object getProxyInstance(){
        return  Enhancer.create(nbaService.getClass(),
                new MethodInterceptor() {//回调函数编写代理规则
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        try{
                            aop.before();
                            Object invoke = methodProxy.invokeSuper(o,objects);//核心代码
                            aop.after();
                            return invoke;
                        }catch (Exception e){
                            aop.exception();
                            throw e;
                        }finally {
                            System.out.println("finally---------");
                        }
                    }
                });
    }
}

//测试类
public static void main(String[] args) {
        NBAService nbaService = new NBAService();
        AOP tranaop = new TranAOP();
        NBAService proxyService = (NBAService) new CglibProxyFactory(nbaService,tranaop).getProxyInstance();
        proxyService.add("huren",1001);
    }

4.4SpringAOP介绍

Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。

SpringAOP基础术语

我们先来介绍AOP的相关术语:

Target(目标对象)

​ 要被增强的对象,一般是业务逻辑类的对象。

Proxy (代理)

​ 一个类被AOP织入增强后,就产生一个结果代理类。

Aspect(切面)

​ 表示增强的功能,就是一些代码完成的某个功能, 非业务功能。是切入点和通知的结合。

Joinpoint(连接点)

​ 所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法(一般是类中的业务方法) ,因为Spring只支持方法类型的连接点。

Pointcut(切入点)

​ 切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。

​ 被标记为final的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

Advice(通知/增强)

​ 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。

​ 通知的类型:前置通知后置通知,异常通知,最终通知环绕通知。

Weaving(织入)

​ 是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。

AspectJ对AOP的实现

​ 对于AOP这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。 Aspectl 也实现了AOP的功能,且其实现方式更为简捷而且还支持注解式开发。所以,Spring 又将Aspectl的对于AOP的实现也引入到了自己的框架中。

​ 在Spring中使用AOP开发时,一般使用Aspectl的实现方式

Aspectl是一个优秀面向切面的框架, 它扩展了Java语言,提供了强大的切面实现。
(1)AspectJ的通知类型

AspectJ中承勇的通知有5中类型:

  1. 前置通知
  2. 后置通知
  3. 环绕通知
  4. 异常通知
  5. 最终通知
(2)AspectJ的切入点表达式
AspectJ定义了专门的表达式用于指定切入点。
表达式的原型如下:
execution(modifiers-pattern? ret-type-pattern 
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
说明:
    modifiers-pattern]访问权限类型
    ret-type-pattern近回值类型
    declaring-type-pattern包名类名
    name-pattern(param-pattern)方法名(参数类型和参数个数)
    throws -pattern抛出异常类型
    ?表示可选的部分

以上表达式共4个部分。

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

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中就是方法的签名。

PS:表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可以使用以下符号:

符号意义
*0-多个任意字符
用在方法参数中,表示任意个参数;用在包名后,表示当前及其子包路径
+用在类名后,表示当前及其子类;用在接口后,表示当前接口及其实现类
示例:
	execution(* com.zx.service.*.*(..))
	指定切入点为:定义在service包里的任眼泪的任意方法。
	
	execution(* com.zx.service..*.*(..))
	指定切入点为:定义在service包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。
	
	execution(* com.zx.IUserService+.*(..))
	指定切入点为:IUserService如果是接口,则为接口中的任意方法及其所有实现类中的任意方法;如果是类,则为该类及其子类中的任意方法

注解方式实现AOP

  • 开发阶段需要关注:核心业务和AOP(切面)代码
  • 运行阶段需要关注:Spring框架会在运行的时候将核心业务和AOP代码通过动态代理的方式进行编织(代理)
  • 代理方式的选择:根据被代理对象是否实现了接口
    • 如果实现接口,就选jdk的动态代理
    • 没有实现接口,则选择CGLIB动态代理
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.13.RELEASE</version>
</dependency>
package com.kkb.service;
//核心业务接口
public interface IService {
    void add(String name,int id);
    boolean update(int num);
}
/**
 * 核心业务实现类
 */
@Service//表示此类可以被扫描
public class TeamService implements IService{//该类实现接口
    @Override
    public void add(String name, int id) {
//      int num = 10/0;//写个异常做测试
        System.out.println("TeamService------add--------");
    }

    @Override
    public boolean update(int num) {
        System.out.println("TeamService------update--------");
        if(num>666) {
            return true;
        }
        return false;
    }
}


/**
 * 切面类
 */
package com.kkb.aop;

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


@Component//切面对象的创建权限依然交给spring容器,使用此注解标注将会被Spring容器扫描
@Aspect//声明为切面(AspectJ框架中的注解,标识当前类是一个切面类 )
public class MyAspect {

    /**
     * 当较多的通知增强方法使用相同的execution切入点表达式时,编写、维护均较为麻烦。
     * 于是AspectJ 提供了@Pointcut 注解,用于定义execution 切入点表达式。
     *      其用法是,将@Pointcut 注解在一个方法之上, 以后所有的execution 的value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。
     * 这个使用@Pointcut 注解方法一般使用private 的标识方法,即没有实际作用的方法。
     */
    @Pointcut("execution(* com.kkb.service..*.*(..))")
    private void pointCut(){

    }
    @Pointcut("execution(* com.kkb.service..*.add*(..))")
    private void pointCut2(){

    }
    /**
     * @Before:声明这是一个前置通知
     * @param jp
     */
    @Before("pointCut()")
    public void before(JoinPoint jp){
        System.out.println("前置通知:在目标方法执行之前被调用的通知");
        String name = jp.getSignature().getName();
        System.out.println("拦截的方法名:"+name);

        Object[] args = jp.getArgs();
        System.out.println("方法的参数格式:"+args.length);
        System.out.println("方法的参数列表");
        for (Object arg : args) {
            System.out.println("\t"+arg);
        }
    }

    /**
     * @AfterReturning 注解声明后置通知
     * returning属性表示返回的结果,如果需要的话可以在后置通知的方法中修改结果
     * value:表示切入点表达式,只有一个属性时默认value,“value:”可以省略
     * @param result
     */
    //下面的execution表示此切入点会拦截service包下以update开头的方法,如果此方法有返回值,可以通过returning获取到
    @AfterReturning(value = "execution(* com.kkb.service..*.update*(..))",returning = "result")
    public void afterReturn(Object result) {
        System.out.println(result);
        if(result!=null){
            Boolean res=(boolean)result;
            if(res=true){
                result=false;
            }
        }
        //以上操作可以通过改动result,强行改变后置通知的执行结果,一般不会这样操作
        System.out.println("后置通知:在目标方法执行之后被调用的通知,result="+result);
    }

    /**
     * @Around 注解声明环绕通知:目标方法执行之前和之后都会执行的通知(没有特殊情况下的话,可以替代前置和后置通知)
     * ProceedingJoinPoint 中的proceed方法:表示目标方法被执行(时机)
     * @return
     * @throws Throwable
     */
    @Around("execution(* com.kkb.service..*.add*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知:目标方法执行之前");
        Object proceed = proceedingJoinPoint.proceed();//目标方法有返回值的话接受一下
        System.out.println("环绕通知:目标方法执行之后");
        return proceed;

    }

    /**
     * @AfterThrowing 注解声明异常通知方法
     * value: 表示切入点表达式
     * returning属性 表示返回的结果,如果需要的话可以在后置通知的方法中修改结果
     */
    //参数中有Throwable,注解中就必须有throwing参数
    @AfterThrowing(value = "execution(* com.kkb.service..*.*(..))",throwing = "ex")
    public void exception(JoinPoint jp,Throwable ex){
        //一般会把异常发生的时间、位置、都记录下来
        System.out.println("异常通知:在目标方法执行出现异常时被调用的通知,否则不调用");
        System.out.println(jp.getSignature()+"方法出现异常,异常信息是:"+ex.getMessage());//记录异常
    }

    /**
     * @After 注解声明最终通知
     */
    @After("execution(* com.kkb.service..*.*(..))")
    public void MyFinally(){
        System.out.println("最终通知:无论是否出现异常都会最后被调用的通知");
    }
}

<!--    在beans标签中引入AOP约束和context的约束-->
<beans 
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
">
    <!--    包扫描  -->
        <context:component-scan base-package="com.kkb.service,com.kkb.aop"></context:component-scan>

<!--    开启自动代理:aspectj-autoproxy,   proxy-target-class:开启对目标类的代理,使用aspectJ的固定设置  -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
//测试类
@Test
    public void test01(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
        TeamService teamService = (TeamService) ac.getBean("teamService");
        teamService.add("湖人队",1001);
        System.out.println("-----------------------------------\n\n");
        boolean update = teamService.update(888);
        System.out.println("update 结果="+update);
    }

XML方式实现AOP

/**
 * 切面类,注意这里就不用注解aspectJ来通知Spring容器了
 */
@Component//切面对象的创建权限依然交给spring容器
public class MyAOP {
    public void before(JoinPoint jp){
        System.out.println("AOP前置通知:在目标方法执行之前被调用的通知");

    }
    public void afterReturn(Object result){

        System.out.println("AOP后置通知:在目标方法执行之后被调用的通知,result="+result);
    }
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("AOP环绕通知:目标方法执行之前");
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("AOP环绕通知:目标方法执行之后");
        return proceed;
    }
    public void exception(JoinPoint jp,Throwable ex){
        //一般会把异常发生的时间、位置、都记录下来
        System.out.println("AOP异常通知:在目标方法执行出现异常时被调用的通知,否则不调用");
        System.out.println(jp.getSignature()+"方法出现异常,异常信息是:"+ex.getMessage());
    }
    public void MyFinally(){
        System.out.println("AOP最终通知:无论是否出现异常都会最后被调用的通知");
    }
}
配置文件中进行如下配置
<!--    xml方式实现aop-->
    <aop:config>
<!--        声明切入点的表达式,可以声明多个-->
        <aop:pointcut id="pt1" expression="execution(* com.kkb.service..*.*(..))"/>
        <aop:pointcut id="pt2" expression="execution(* com.kkb.service..*.add*(..))"/>

        <aop:aspect ref="myAOP">
            <aop:before method="before" pointcut="execution(* com.kkb.service..*.*(..))"></aop:before>
            <aop:after-returning method="afterReturn" pointcut-ref="pt2" returning="result"></aop:after-returning>
            <aop:after-throwing method="exception" pointcut-ref="pt1" throwing="ex"></aop:after-throwing>
            <aop:after method="MyFinally" pointcut-ref="pt1"></aop:after>
            <aop:around method="around" pointcut-ref="pt2"></aop:around>
        </aop:aspect>
    </aop:config>
   //测试类
   @Test
    public void test01(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
        NBAService nbaService = (NBAService) ac.getBean("NBAService");
        nbaService.add("湖人队",1001);
        System.out.println("------------------------------\n\n\n");
        boolean update1 = nbaService.update(888);
        System.out.println("update 结果="+update1);
    }

5、Spring整合JDBC

5.1 使用Spring-jdbc操作数据库

学习使用JdbcTemplate API 和 如何使用Spring管理JdbcTemplate

引入依赖
<!--        spring-JDBC依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.13.RELEASE</version>
        </dependency>
<!--        mysql数据库连接依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>
<!--        数据源依赖-->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>

数据表:

在这里插入图片描述

/**
 * 球队的实体类
 */
public class Team {
    private Integer id;
    private String name;
    private String location;
    public Team(String name, String location) {
        this.name = name;
        this.location = location;
    }
}

/**
 *	数据库操作类TeamDao继承JdbcDaoSupport
 */
public class TeamDao extends JdbcDaoSupport {
    
    public int insert(Team team){
        String sql = "insert into team(tname,location) value (?,?)";
        return this.getJdbcTemplate().update(sql,team.getName(),team.getLocation());
    }
    public int update(Team team){
        String sql = "update  team set tname=?,location=? where tid=?";
        return this.getJdbcTemplate().update(sql,team.getName(),team.getLocation(),team.getId());
    }
    public int del(int id){
        String sql = "delete from team where tid=?";
        return this.getJdbcTemplate().update(sql,id);
    }
    /**
     * 查询单个数据:使用queryForObject
     * @param id
     * @return
     */
    public Team findById(int id){
        String sql = "select * from team where tid=?";
        return this.getJdbcTemplate().queryForObject(sql,new Object[]{id},new RowMapper<Team>(){
            @Override
            public Team mapRow(ResultSet resultSet, int i) throws SQLException {
                Team team = new Team();
                team.setId(resultSet.getInt("tid"));
                team.setName(resultSet.getString("tname"));
                team.setLocation(resultSet.getString("location"));
                return team;
            }
        });
    }
    /**
     * 查询多个数据
     * @return
     */
    public List<Team> findAll(){
        String sql = "select * from team";
        return this.getJdbcTemplate().query(sql, new RowMapper<Team>() {
            @Override
            public Team mapRow(ResultSet resultSet, int i) throws SQLException {
                Team team = new Team();
                team.setId(resultSet.getInt("tid"));
                team.setName(resultSet.getString("tname"));
                team.setLocation(resultSet.getString("location"));
                return team;
            }
        });

    }
    /**
     * 查询行数:单个数据
     * @return
     */
    public int getCount(){
        String sql = "select count(tid) from team";
        return this.getJdbcTemplate().queryForObject(sql,Integer.class);
    }
    /**
     * 查多个
     * @return
     */
    public Map<String, Object> getMany(){
        String sql = "select max(tid),min(tid) from team";
        return this.getJdbcTemplate().queryForMap(sql);
    }
}

6、Spring事务管理

事务原本是数据库中的概念,在Dao层。但在实际开发中,一般将事务提升到业务层,即Service层。这样做是为了能够使用事务的特性来管理具体的业务。

6.1 Spring事务管理API

Spring的事务管理,主要用到两个事务相关的接口。

6.1.1事务管理器接口

事务管理器是PlatformTransactionManager接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。

在这里插入图片描述

PlatformTransactionManager接口常用的实现类:
DataSourceTransactionManager:使用JDBC或MyBatis进行数据库操作时使用。

Spring的回滚方式:
Spring事务的默认回滚方式是:发生运行时异常和error时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。

6.1.2事务定义接口

事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。

在这里插入图片描述

6.1.2.1事务隔离级别常量

这些常量均是以ISOLATION开头。即形如ISOLATION_XXX.

  • ➢DEFAULT: 采用DB默认的事务隔离级别。MySql的默认为REPEATABLE _READ; Oracle默认为 READ_COMMITTED。
  • ➢READ_UNCOMMITTED: 读未提交。未解决任何并发问题。
  • ➢READ_COMMITTED: 读已提交。解决脏读、存在不可重复读、幻读。
  • ➢REPEATABLE_READ: 可重复读。解决脏读、不可重复读,存在幻读
  • ➢SERIALIZABLE: 串行化。不存在并发问题。
6.1.2.2事务传播行为常量

所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A事务中的方法doSome()调用B事务中的方法doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。

事务传播行为常量都是以PROPAGATION_开头,形如PROPAGATION_XXX。

Propagation.REQUIRED

​ 当前没有事务的时候,就会创建一个新的事务; 如果当前有事务,就直接加入该事务,比较常用的设置Propagation.SUPPORTS
​ 支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就以非事务方式执行

Propagation.MANDATORY

​ 支持当前事务,如果当前有事务,就直接加入该事务;当前没有事务的时候,就抛出异常

Propagation.REQUIRES NEW

​ 创建新事务,无论当前是否有事务都会创建新的

PROPAGATION_NESTED

PROPAGATION_NEVER

PROPAGATION_NOT_SUPPORTED

6.1.2.3默认事务超时时限

常量TIMEOUT_DEFAULT定义了事务底层默认的超时时限,sql 语句的执行时长。

注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。

6.2声明式事务控制

Spring提供的对事务的管理,就叫做声明式事务管理。

如果用户需要使用spring的声明式事务管理,在配置文件中配置即可:不想使用的时候直接移除配置。这种方式实现了对事务控制的最大程度的解耦。

声明式事务管理,核心实现就是基于AOP.

Spring中提供了对事务的管理。开发者只需要按照spring的方式去做就行。
事务必须在service层统一控制

事务的粗细粒度:
	细粒度:对方法中的某几行的代码进行开启提交回滚;
	粗粒度:对整个方法进行开启提交回滚;
	
Spring中的aop只能对方法进行拦截,所有我们也就针对方法进行事务的控制。

如果只有单条的查询语句,可以省略事务;如果一次执行的是多条查询语句,例如统计结果、报表查询。必须开启事务。

注解实现事务

<!-- 引入事务的约束-->
<beans
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"   >

<!--创建事务管理器DataSourceTransactionManager的实例,使用之前JDBC的数据源dataSource-->
    <context:component-scan base-package="com.kkb"/><!-- 包扫描-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"   ></property>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>
//Spring中提供了对事务的管理。开发者只需要按照spring的方式去做就行。
事务必须在service层统一控制
@Service
public class TeamService {
    //自动注入属性值,Spring容器中已经创建了id为teamDao的Bean对象
    @Autowired
    private TeamDao teamDao;
    //事务控制的注解:增删改一般使用Propagation.REQUIRED,rollbackFor表示碰到什么异常会回滚(Spring中事务控制的粒度只支持到方法层面)
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = {Exception.class})
    public int insert(Team team){
        //测试事务:这里插入两次相同的数据,观察数据库,只有都成功才会成功,如果有异常就回回滚失败
        int num1 = teamDao.insert(team);
        //System.out.println("第一条执行结果: num1 = "+num1+"\n第一条执行成功,在这里插入一个错误,使下一条无法执行成功,看第一条是否会回滚");
        //System.out.println(10/0);
        int num2 = teamDao.insert(team);
        //System.out.println("第二条执行结果: num2 = "+num2);
        return num2+num1;
    }
}
//测试类
    @Test
    public void test01(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        TeamService teamService = (TeamService) applicationContext.getBean("teamService");
        int num = teamService.insert(new Team("凯尔特人","波士顿"));
    }

xml实现事务

由于是以切面形式进行添加的,所以需要引入aop的约束和aspectJ的依赖,前面有,不重复
<!--声明事务的通知:-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
<!--            增删改用REQUIRED-->
            <tx:method name="insert*" propagation="REQUIRED"/>
            <tx:method name="add*" propagation="REQUIRED"/>
<!--            查询一般用SUPPORTS,read-only(查询一般默认只读)="true"-->
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <!--方法拦截,并将事务功能切入(添加)(到拦截到的方法)-->
    <aop:config>
        <aop:pointcut id="pt"  expression="execution(* com.kkb.service..*.insert*(..))"/>
        <aop:advisor pointcut-ref="pt" advice-ref="txAdvice"/>
    </aop:config>

将TeamService类中的测试方法insert上的@Transactional注解去掉,然后再次测试。发现也有了事务管理的功能。

//测试类(和上面的一样)
    @Test
    public void test01(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
        TeamService teamService = (TeamService) applicationContext.getBean("teamService");
        int num = teamService.insert(new Team("凯尔特人","波士顿"));
    }
  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值