Spring基础——快速入门

Spring基础

一. IOC(控制反转)

  • Spring中的IOC是通过工厂模式进行解耦。先把对象都使用配置文件配置起来,当启动服务器应用加载的时候, 让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。Spring采用的是Map这种数据结构存储对象。

1. 基于xml的IOC配置

1.1 基本配置
1.1.1 SpringIOC所要使用的maven坐标:
<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
</dependencies>
1.1.2 Spring的xml的配置
<!--把对象的创建交给spring来管理-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--把对象的创建交给spring来管理 Map结构:key为id,value为class-->
    <bean id="accountService" class="com.iteima.service.implAccountServiceImpl"></bean>
    <bean id="accountDao" class="com.iteima.dao.impl.AccountDaoImpl"></bean>
</beans>
1.1.3 Java代码的使用

在这里插入图片描述

  • BeanFactory才是Application的顶层接口。
  • ApplicationContext是它的子接口。
  • BeanFactory 和 ApplicationContext 的区别:创建对象的时间点不一样
    • ApplicationContext:它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取配置文件马上就创建配置文件中配置的对象。适用单例对象
    • BeanFactory:它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。适用多例对象
/**
* ApplicationContext的三个常用实现类:
*       ClassPathXmlApplicationContext: 它可以加载类路径的配置文件,要求配置文件必须在类路径下。不在的话,加载不了(更常用)
*       FileSystemXmlApplicationContext: 它可以加载磁盘意路径下的配置文件(必须有访问权限)
*       AnnotationConfigApplicationContext: 他是用于读取注解创建容器的。
*/
public static void main(String[] args){
    //1. 获取核心容器对象
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
    //2. 根据id获取Bean对象: 两种方式
    IAccountService accountService = (IAccountService) applicationContext.getBean("accountService");
    IAccountDao accountDao = applicationContext.getBean("accountDao", IAccountDao.class);
}
1.2 IOC中bean标签
1.2.1 创建bean的三种方式
  • 第一种方式: 使用默认构造函数创建
    • 在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
    • 采用的就是默认构造创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="com.iteima.service.impl.AccountServiceImpl"></bean>
  • 第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="com.iteima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
/*
* 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认的构造函数)
* */
public class InstanceFactory {
    public IAccountService getAccountService() {
        return new AccountServiceImpl();
    }
}
  • 第三种方式: 使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<bean id="accountService" class="com.iteima.factory.StaticFactory" factory-method="getAccountService"></bean>
public class StaticFactory {
    public static IAccountService getAccountService() {
        return new AccountServiceImpl();
    }
}
1.2.2 bean的作用范围
  • bean标签的scope属性: 作用:用于指定bean的范围,scope的取值:
    • singleton: 单例的(默认值)
    • prototype: 多例的
    • request:作用于web应用的请求范围
    • session:作用于web应用的会话范围
    • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
  • init-method: 指定类中的初始化方法名称。
  • destroy-method: 指定类中销毁方法名称。
1.2.3 bean对象的生命周期
  • 单例对象:
    • 出生:当容器创建时对象出生
    • 活着:只要容器还在,对象一直活着
    • 死亡:容器销毁,对象消亡
    • 总结:单例对象的生命周期和容器相同
  • 多例对象:
    • 出生:当我们使用对象时spring框架为我们创建
    • 活着:对象只要是在使用过程中就一直活着
    • 死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收,Spring框架不负责销毁
1.3 DI(依赖注入)
  • 依赖注入能注入的数据:有三类
    • 基本类型和String
    • 其他bean类型(在配置文件中或者注解配置过的bean)
    • 复杂类型/集合类型
  • 注入的方式:有三种
    • 第一种:使用构造函数提供
    • 第二种:使用set方法提供
    • 第三种:使用注解提供
1.3.1 构造函数注入
<!-- 构造函数注入:
        使用的标签:constructor-arg
        标签出现的位置:bean标签的内部
        标签中的属性
            type: 用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
            index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始
            name:用于指定给构造函数中指定名称的参数赋值   常用的
            ==============以上三个用于指定给构造函数中哪个参数赋值================
            value: 用于提供基本类型和String类型的数据
            ref:用于指定其他的bean类型数据。它指的就是在spring的IOC核心容器中出现过的bean对象
        优势:
            在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
        弊端:
            改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供
-->
<bean id="accountService" class="com.iteima.service.impl.AccountServiceImpl">
    <constructor-arg name="name" value="test"></constructor-arg>
    <constructor-arg name="age" value="18"></constructor-arg>
    <constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
public class AccountServiceImpl implements IAccountService {
    //如果是经常变化的数据,并不适用与注入的方式
    private String name;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }
}
1.3.2 set方法注入(更常用)
<!-- set方法注入
        涉及的标签:property
        出现的位置:bean标签的内部
        标签的属性:
            name:用于指定注入时所调用的set方法名称
            value: 用于提供基本类型和String类型的数据
            ref:用于指定其他的bean类型数据。它指的就是在spring的IOC核心容器中出现过的bean对象
        优势:
            创建对象时没有明确的限制,可以直接使用默认构造函数
        弊端:
            如果某个成员必须有值,则获取对象是有可能set方法没有执行
-->
<bean id="accountService2" class="com.iteima.service.impl.AccountServiceImpl2">
    <property name="name" value="TEST"></property>
    <property name="age" value="21"></property>
    <property name="birthday" ref="now"></property>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
public class AccountServiceImpl2 implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}
1.3.3 使用p名称空间注入数据(本质还是调用set方法)
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    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="accountService"
        class="com.itheima.service.impl.AccountServiceImpl4"
        p:name="test" p:age="21" p:birthday-ref="now"/>
    <!-- 配置一个日期对象 -->
    <bean id="now" class="java.util.Date"></bean>
</beans>
1.3.4 注入集合属性
  • 用于给List结构集合注入的标签:
    • list array set: 这三个标签可以互换使用
  • 用于给Map结构集合注入的标签:
    • map props:这两个标签可以互换使用
<bean id="accountService3" class="com.iteima.service.impl.AccountService>
    <property name="myStrs">
        <array>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </array>
    </property>
    <property name="myList">
        <list>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </list>
    </property>
    <property name="mySet">
        <set>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </set>
    </property>
    <property name="myMap">
        <map>
           <entry key="testA" value="aaa"></entry>
            <entry key="testB">
                <value>BBB</value>
            </entry>
        </map>
    </property>
    <property name="myProps">
        <props>
            <prop key="testC">ccc</prop>
            <prop key="testD">ddd</prop>
        </props>
    </property>
</bean>
public class AccountServiceImpl3 implements IAccountService {
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProps;

    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }
    public void setMyList(List<String> myList) {
        this.myList = myList;
    }
    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }
    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }
    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }
}

2. 基于注解的IOC配置

2.1 用于创建对象的注解
  • 他们的作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的。
  • @Component: 用于把当前类对象存入spring容器中
    • 属性:value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
  • @Controller: 一般用在表现层
  • @Service: 一般用在业务层
  • @Repository: 一般用在持久层

小总结:
以上三个注解他们的作用和属性与component属性是一模一样的。
他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰

2.2 用于注入数据的注解
  • 他们的作用就和在xml配置文件中的bean标签中写一个标签的作用是一样的。
  • @Autowired: 自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
    • 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错
    • 如果ioc容器中有多个类型匹配时:按照变量名找到与bean中匹配的id
    • 在使用注解时,set方法就不是必须的了
  • @Qualifier: 在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以
@Autowired
@Qualifier("accountDao1")
private IAccountDao accountDao;
  • @Resource: 直接bean的id注入。它可以独立使用
    • 属性:name: 用于指定bean的id
@Resource(name = "accountDao1")
private IAccountDao accountDao;
  • @Value: 用于注入基本类型和String类型的数据
    • 属性:value:用于指定数据的值。它可以使用spring中SpEL(spring中的el表达式),SpEL的写法:${表达式}

小总结:
@Autowired,@Qualifier,@Resource: 三个注解只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现
另外集合类型的注入只能通过XML来实现

2.3 用于改变作用范围的
  • 他们的作用就和在bean标签中使用scope属性实现的功能是一样的
  • @Scope: 用于指定bean的作用范围。
    • 属性:value:指定范围的取值。常用取值:singleton prototype
@Component("accountService")
@Scope("prototype")
public class AccountServiceImpl{}
2.4 生命周期相关的注解(了解)
  • 他们的作用就和在bean标签中init-method和destroy-method的作用是一样的。
  • @PreDestroy: 用于指定销毁方法
  • @PostConstruct: 用于指定初始化方法
2.5 配置相关的注解
  • @Configuration: 指定当前类是一个配置类

    • 当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写
  • @ComponentScan: 指定通过注解指定spring在创建容器时要扫描的包

    • 属性:value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包
    • 与下面xml配置作用一样:
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为context名称空间和约束中-->
        <context:component-scan base-package="com.iteima"></context:component-scan>
    </beans>
    
  • @Bean: 用于把当前方法的返回值作为bean对象存入spring的IOC容器中

    • 属性:name:用于指定bean的id,默认值是当前方法的名称
    • 细节:当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。查找的方式和Autowired注解的作用是一样的。
@Bean("runner")
public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource dataSource) {
    return new QueryRunner(dataSource);
}
  • @Import: 用于导入其他的配置类

    • 属性:value:用于指定其他配置类的字节码
    • 当我们使用Import的注解之后,有Import注解的类就是父配置类,而导入的都是子配置类
  • @PropertySource: 作用:用于指定properties文件的位置

    • 属性:value:指定文件名称和路径
    • 关键字:classpath:表示类路径下
  • 小总结:

@Configuration
@ComponentScan("com.iteima")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {}

3. Spring整合Junit

  • 1.导入相应的maven坐标
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
  • 2.编写测试类
    • @ContextConfiguration:
      • locations: 指定xml文件的位置,加上classpath关键字,表示在类路径下
      • classes:指定注解类所在地位置
    • 当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
    • 我们测试时便不用写实例化ApplicationContext了。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class) //没有xml文件的情况,使用@Configuration注解的情况
//@ContextConfiguration(locations = "classpath:bean.xml") 有xml文件的情况
public class AccountServiceTest {
    @Autowired
    private IAccountService accountService;
}

二. AOP(面向切面编程)

  • AOP是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

1. 动态代理

1.1 基于接口的动态代理
public class Client {
    public static void main(String[] args){
        //Producer是实体类
        final Producer producer = new Producer();
        /*
        * 动态代理:
        *   特点:字节码随用随加载
        *   作用:不修改源码的基础上对方法增强
        *   分类:
        *       基于接口的动态代理
        *       基于子类的动态代理
        *   基于接口的动态代理:
        *       涉及的类:Proxy
        *       提供者:JDK官方
        *   如何创建代理对象:
        *       使用Proxy类中的newProxyInstance方法
        *   创建代理对象的要求:
        *       被代理类最少实现一个接口,如果没有则不能使用
        *    newProxyInstance方法的参数:
        *       ClassLoader:类加载器
        *           他是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法
        *       Class[]:字节码数组
        *           它是用于让代理对象和被代理对象有相同方法。固定写法
        *       InvocationHandler:用于提供增强的代码
        *           他是让我们写如何代理。我们一般都是写一些该接口的实现类。通常情况下都是匿名内部类,但不是必须的。
        *           此接口的实现类都是谁用谁写。
        * */

        //此时不实现接口不能使用
        //IProducer是接口
        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(), new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何借口方法都会经过该方法
                     * 方法参数的含义
                     * @param proxy 代理对象的引用
                     * @param method 当前执行的方法
                     * @param args 当前执行方法所需的参数
                     * @return 和被代理对象有相同的返回值
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的代码
                        Object returnValue = null;
                        //1. 获取方法执行的参数
                        Float money = (Float)args[0];
                        //2. 判断当前方法是不是销售
                        if("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money * 0.8f);
                        }
                            return returnValue;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }
}
1.2 基于子类的动态代理
  • 1.导入相应的jar包
<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>2.1_3</version>
    </dependency>
</dependencies>
  • 2.java代码:
public class Client {
    public static void main(String[] args){
        final Producer producer = new Producer();
        /*
        * 动态代理:
        *   特点:字节码随用随加载
        *   作用:不修改源码的基础上对方法增强
        *   分类:
        *       基于接口的动态代理
        *       基于子类的动态代理
        *   基于子类的动态代理:
        *       涉及的类:Enhancer
        *       提供者:第三方cglib库
        *   如何创建代理对象:
        *       使用Enhancer类中的create方法
        *   创建代理对象的要求:
        *       被代理类不能是最终类
        *    create方法的参数:
        *       Class:字节码
        *           它是用于指定被代理对象的字节码
        *       Callback:用于提供增强的代码
        *           他是让我们写如何代理。我们一般都是写一些该接口的实现类。通常情况下都是匿名内部类,但不是必须的。
        *           此接口的实现类都是谁用谁写。
        *           我们一般写的都是该接口的子接口实现类:MethodInterceptor
        * */
        //没有实现任何接口
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             *  以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param methodProxy:当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //提供增强的代码
                Object returnValue = null;
                //1. 获取方法执行的参数
                Float money = (Float)args[0];
                //2. 判断当前方法是不是销售
                if("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money * 0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(12000f);
    }
}

2. AOP相关术语

  • Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的
    连接点。
  • Pointcut(切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
  • Advice(通知/增强): 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。
  • Introduction(引介): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方
    法或 Field。
  • Target(目标对象): 代理的目标对象。
  • Weaving(织入): 是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
  • Proxy(代理): 一个类被 AOP 织入增强后,就产生一个结果代理类。
  • Aspect(切面): 是切入点和通知(引介)的结合。

3. AOP的xml配置

3.1 导入相应的maven依赖
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.7</version>
</dependency>
3.2 xml文件的编写
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置spring的IOC,把service对象配置进来-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
    <!--spring中基于XML的AOP配置步骤
        1. 把通知Bean也交给spring来管理
        2. 使用aop:config标签表明开始AOP的配置
        3. 使用aop:aspect标签表明配置切面
                id属性:是给切面提供一个唯一标识
                ref属性:是指定通知类bean的Id
        4. 在aop:aspect标签的内部使用对应标签来配置通知的类型
                我们现在示例是让printLog方法在切入点方法执行之前:所以是前置通知
                aop:before:表示配置前置通知
                    method属性:用于指定Logger类中哪个方法是前置通知
                    pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务中哪些方法增强

                切入点表达式的写法:
                    关键字:execution(表达式)
                    表达式:
                        访问修饰符 返回值 包名.包名.包名.....类名.方法名(参数列表)
                    标准的表达式写法:
                        public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
                    访问修饰符可以省略
                        void com.itheima.service.impl.AccountServiceImpl.saveAccount()
                    返回值可以使用通配符,表示任意返回值
                        * com.itheima.service.impl.AccountServiceImpl.saveAccount()
                    包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
                        * *.*.*.*.AccountServiceImpl.saveAccount()
                    包名可以使用..表示当前包及其子包
                        * *..AccountServiceImpl.saveAccount()
                    类名和方法名都可以使用*来实现通配
                    参数列表:
                        可以直接写数据类型:
                            基本数据类型直接名称
                                引用类型写包名.类名的方式 java.lang.String
                        可以使用通配符表示任意类型,但是必须有参数
                        可以使用..表示有无参数均可,有参数可以是任意类型
                    全通配写法:
                        * *..*.*(..)

                    实际开发中切入点表达式的通常写法:
                        切到业务层实现类下的所有写法
                            * com.itheima.service.impl.*.*(..)
    -->
    <!--配置Logger类 -->
    <bean id="logger" class="com.itheima.utils.Logger"></bean>
    <!--配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>
<!--配置AOP-->
<aop:config>
    <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
             此标签写在aop: aspect标签内部只能当前切面使用。
             它还可以写在aop: aspect外面,此时就变成了所有切面可用
        -->
    <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
    <!--配置切面-->
    <aop:aspect id="logAdvice" ref="logger">
        <!--配置前置通知: 在切入点方法执行之前执行-->
        <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
        <!--配置后置通知:在切入点方法正常执行之后执行。它和异常通知只能执行一个。-->
        <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
        <!--配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知只能执行一个。-->
        <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
        <!--配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
        <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
        <!--配置环绕通知, 详细的注释请看Logger类中-->
        <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
    </aop:aspect>
</aop:config>
public class Logger {
    /*
    * 环绕通知
    * 问题:
    *       当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
    * 分析:
    *       通过对比动态代理中的环绕通知代码:发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
    * 解决:
    *       Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就想相当于明确调用切入点方法。
    *       该接口可以作为环绕通知的方法参数:在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
    * spring中的环绕通知:
    *       它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
    * */
    public Object  aroundPrintLog(ProceedingJoinPoint joinPoint) {
        Object rtValue = null;
        try {
            Object[] args = joinPoint.getArgs(); //得到方法执行所需的参数
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。前置");
            rtValue = joinPoint.proceed(); //明确调用业务层方法(切入点方法)
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。后置");
            return rtValue;
        } catch (Throwable throwable) {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。异常");
            throw new RuntimeException(throwable);
        } finally {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。最终");
        }
    }
}

4. AOP的注解配置

4.1 java代码示例
/*
* 使用注解配置AOP时,后置通知或者异常通知会在最终通知之后执行。
*
* */
@Component("logger")
@Aspect //表示当前类是一个切面类
public class Logger {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}

    /*
    * 前置通知
    * */
    @Before("pt1()")
    public void beforePrintLog() {
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了");
    }

    /*
     * 后置通知
     * */
    @AfterReturning("pt1()")
    public void afterReturningPrintLog() {
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了");
    }

    /*
     * 异常通知
     * */
    @AfterThrowing("pt1()")
    public void afterThrowingPrintLog() {
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了");
    }

    /*
     * 最终通知
     * */
    @After("pt1()")
    public void afterPrintLog() {
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了");
    }

    /*
    * 环绕通知
    * 问题:
    *       当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
    * 分析:
    *       通过对比动态代理中的环绕通知代码:发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
    * 解决:
    *       Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就想相当于明确调用切入点方法。
    *       该接口可以作为环绕通知的方法参数:在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
    * spring中的环绕通知:
    *       它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
    * */
    @Around("pt1()")
    public Object  aroundPrintLog(ProceedingJoinPoint joinPoint) {
        Object rtValue = null;
        try {
            Object[] args = joinPoint.getArgs(); //得到方法执行所需的参数
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。前置");
            rtValue = joinPoint.proceed(); //明确调用业务层方法(切入点方法)
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。后置");
            return rtValue;
        } catch (Throwable throwable) {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。异常");
            throw new RuntimeException(throwable);
        } finally {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。最终");
        }
    }
}
  • 注意:当使用注解aop时,最终通知会发生在后置通知或者异常通知之前。环绕通知则不会出现这种情况。
4.2 xml开启注解AOP支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!--配置spring开启注解AOP的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • @EnableAspectJAutoProxy:该注解可以开启AOP的支持,与<aop:aspectj-autoproxy></aop:aspectj-autoproxy>作用一样。

三. Spring的事务控制

1. 事务控制的API

1.1 PlatformTransactionManager接口
  • 该接口提供事务操作的方法:

    • 获取事务状态信息:TransactionStatus getTransaction(TransactionDefinition definition)
    • 提交事务:void commit(TransactionStatus status)
    • 回滚事务:void rollback(TransactionStatus status)
  • 该接口的实现类:

    • org.springframework.jdbc.datasource.DataSourceTransactionManager:使用 SpringJDBC 或 iBatis 进行持久化数据时使用
    • org.springframework.orm.hibernate5.HibernateTransactionManager:使用Hibernate 版本进行持久化数据时使用
1.2 TransactionDefinition
  • 它是事务的定义信息对象:
    • String getName():获取事务对象名称
    • int getIsolationLevel():获取事务隔离级别
    • int getPropagationBehavior():获取事务传播行为
    • int getTimeout():获取事务超时时间
    • boolean isReadOnly():获取事务是否只读
1.3 TransactionStatus
  • 此接口提供的是事务具体的运行状态:
    • void flush():刷新事务
    • boolean hasSavepoint():获取是否存在存储点(可以设置事务回滚到的位置)
    • boolean isCompleted():获取事务是否完成
    • boolean isNewTransaction():获取事务是否为新事务
    • boolean isRollbackOnly():获取事务是否回滚

2. 事务的相关概述

2.1 事务的隔离级别
  • 事务隔离级反映事务提交并发访问时的处理态度
  • ISOLATION_DEFAULT:默认级别:ISOLATION_REPEATABLE_READ
  • ISOLATION_READ_UNCOMMITTED:可以读取未提交的数据
  • ISOLATION_READ_COMMITTED:只能读取已提交数据,解决脏读问题(Oracle默认级别)
  • ISOLATION_REPEATABLE_READ:是否读取其他事务提交修改后的数据,解决不可重复读问题(MySql默认级别)
  • ISOLATION_SERIALIZABLE:是否读取其他事务提交添加后的数据,解决幻读问题
2.2 事务的传播行为
  • REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
  • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
  • MANDATORY: 使用当前的事务,如果当前没有事务,就抛出异常
  • REQUERS_NEW: 新建事务,如果当前在事务中,把当前事务挂起
  • NOT_SUPPORTED: 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  • NEVER: 以非事务方式运行,如果当前存在事务,抛出异常
  • NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作

3. 事务的xml配置

3.1 导入相应的maven依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>
3.2 xml的配置编写
  • spring中基于xml的声明式事务控制配置步骤;
    1. 配置事务管理器
    2. 配置事务的通知
    3. 配置AOP中的通用切入点表达式
    4. 建立事务通知和切入点表达式的对应关系
    5. 配置事务的属性
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置账户的持久层-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置业务层 -->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="username" value="root"></property>
        <property name="password" value=""></property>
    </bean>

    <!--spring中基于xml的声明式事务控制配置步骤
        1. 配置事务管理器
        2. 配置事务的通知
            此时我们需要导入事务的约束 tx名称空间约束,同时也需要aop
            使用tx:advice标签配置事务通知
                属性:
                    id: 给事务通知起一个唯一标识
                    transaction-manager:给事务通知提供一个事务管理器引用
        3. 配置AOP中的通用切入点表达式
        4. 建立事务通知和切入点表达式的对应关系
        5. 配置事务的属性
                是在事务的通知tx:advice标签的内部
    -->
    <!--1. 配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--2. 配置事务的通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--5. 配置事务的属性
            isolation=""  用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别
            no-rollback-for="" 用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。不写表示任何异常都回滚。
            propagation="" 用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS.
            read-only="" 用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写
            rollback-for="" 用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
            timeout="" 用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位
        -->
        <tx:attributes>
            <!-- *(通配符)表示:所有方法 -->
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <!-- find*表示:以find开头的所有方法 -->
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>

    <!--配置AOP-->
    <aop:config>
        <!--3. 配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--4. 建立切入点表达式和事务通知的对应关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
</beans>

4. 事务的注解配置

4.1 java类上的注解配置
  • @Transactional: 该注解可以出现在接口上,类上和方法上
    • 出现接口上,表示该接口的所有实现类都有事务支持
    • 出现在类上,表示类中所有方法有事务支持
    • 出现在方法上,表示方法有事务支持
/*
*  账户的业务实现类
*
*  事务控制应该都是在业务层
* */
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;

    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);
    }
    //需要的是读写型事务配置
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void transfer(String sourceName, String targetName, Float money) {
        //1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.根据名称查出转入账户
        Account target = accountDao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney() - money);
        //4.转入账户加钱
        target.setMoney(target.getMoney() + money);
        //5.更新转出账户
        accountDao.updateAccount(source);
        int i = 1 / 0;
        //6.更新转入账户
        accountDao.updateAccount(target);
    }
}
4.2 配置文件中开启事务的支持(使用xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="username" value="root"></property>
        <property name="password" value=""></property>
    </bean>

    <!--spring中基于注解的声明式事务控制配置步骤
        1. 配置事务管理器
        2. 开启spring对注解事务的支持
        3. 在需要事务支持的地方使用@Transactional注解
    -->
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--开启spring对注解事务的支持 @EnableTransactionManagement意思相同-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
4.2 配置类中开启事务的支持(不使用xml)
  • jdbcConfig.properties
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/eesy
jdbc.username = root
jdbc.password =
  • JdbcConfig.java
/*
* 和连接数据库相关的配置类
* */
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    /*
    * 创建JdbcTemplate
    * */
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    /*
    * 创建数据源对象
    * */
    @Bean(name = "dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}
  • TransactionConfig.java
/*
* 和事务相关的配置类
* */
public class TransactionConfig {
    /*
    * 用于创建事务管理器对象
    * */
    @Bean(name = "transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
  • SpringConfiguration.java
/*
* spring的配置类,相当于bean.xml
* */
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class, TransactionConfig.class})
@PropertySource(value = "jdbcConfig.properties") //连接jdbcConfig.properties文件
@EnableTransactionManagement //开启spring对注解事务的支持 <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
public class SpringConfiguration {
}
    * */
    @Bean(name = "dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}
  • TransactionConfig.java
/*
* 和事务相关的配置类
* */
public class TransactionConfig {
    /*
    * 用于创建事务管理器对象
    * */
    @Bean(name = "transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
  • SpringConfiguration.java
/*
* spring的配置类,相当于bean.xml
* */
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class, TransactionConfig.class})
@PropertySource(value = "jdbcConfig.properties") //连接jdbcConfig.properties文件
@EnableTransactionManagement //开启spring对注解事务的支持 <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
public class SpringConfiguration {
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值