Spring02-Spring注解的使用、基于注解的IOC、纯注解配置、整合Junit、AOP入门、基于配置文件的AOP、切入点表达式、基于配置的文件环绕通知

Spring注解的使用

一、项目注解 和 XML 的选择问题

  • 学习基于注解的 IOC 配置,即注解配置 和 XML 配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样。
  • 关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯 , 所以这两种配置方式我们都需要掌握。
  • 把 Spring 的 xml 配置内容改为使用注解逐步实现

二、用于创建对象的注解

1、注解介绍

  • @Component
  • @Repository
  • @Service
  • @Controller
注解说明
@Component使用在类上用于实例化Bean
@Controller使用在web层类上用于实例化Bean
@Service使用在service层类上用于实例化Bean
@Repository使用在dao层类上用于实例化Bean

2、注意问题

  • 使用注解开发,需要在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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <context:component-scan base-package="com.etime"></context:component-scan>
</beans>

3、编写示例代码

(1)使用@Component或@Repository标识UserDaoImpl需要Spring进行实例化。
  • UserDao接口
package com.etime.dao;
public interface UserDao {
    void show();
}
  • UserDaoImpl实现类

package com.etime.dao.impl;
package com.etime.dao.impl;

import com.etime.dao.UserDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

//@Component("ud")
@Repository("ud")
public class UserDaoImpl implements UserDao {
    @Override
    public void show() {
        System.out.println("这是userDao的方法");
    }
}
(2)使用@Component或@Service标识UserServiceImpl需要Spring进行实例化
  • UserService接口
package com.etime.service;

public interface UserService {
    void show();
}
  • UserServiceImpl
package com.etime.service.impl;

import com.etime.service.UserService;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

//@Component("us")
@Service("us")
public class UserServiceImpl implements UserService {
    @Override
    public void show() {
        System.out.println("这是userService的方法");
    }
}
(3)测试
    @Test
    public void t07() {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        UserDao userDao = (UserDao) context.getBean("ud");
        userDao.show();

        UserService userService = (UserService) context.getBean("us");
        userService.show();
    }

三、基于注入数据的注解

(1)注解介绍

  • @Value
  • @Resource
  • @Autowrited
  • Qualifier
注解说明
@Value注入普通属性
@Autowired自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。 如果IOC容器当中有多个接口得实现类, 首先根据类型自动装配, 然后再根据名称自动装配。
@Qualifier结合@Autowired一起使用用于根据名称进行依赖注入
@Resource相当于@Autowired+@Qualifier,按照名称进行注入,是java提供的,不是框架提供的

(2)使用@Value进行字符串的注入

@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Value("字符串")
    private String str;
    public void addUser() {
        System.out.println(str);
        System.out.println("开始新增用户了");
    }
}

(3)使用@Autowired或者@Autowired+@Qulifier或者@Resource进行userDao的注入

第一种:
package com.etime.service.impl;

import com.etime.dao.UserDao;
import com.etime.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

//@Component("us")
@Service("us")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public void show() {
        userDao.show();
    }
}

第二种:
package com.etime.service.impl;

import com.etime.dao.UserDao;
import com.etime.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

//@Component("us")
@Service("us")
public class UserServiceImpl implements UserService {

    @Autowired
    @Qualifier("ud")
    private UserDao userDao;

    @Override
    public void show() {
        userDao.show();
    }
}

第三种:
package com.etime.service.impl;

import com.etime.dao.UserDao;
import com.etime.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

//@Component("us")
@Service("us")
public class UserServiceImpl implements UserService {

    @Resource(name = "ud")
    private UserDao userDao;

    @Override
    public void show() {
        userDao.show();
    }
}

(4)测试

   @Test
    public void t07() {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        /*UserDao userDao = (UserDao) context.getBean("ud");
        userDao.show();*/
        UserService userService = (UserService) context.getBean("us");
        userService.show();
    }

四、和生命周期相关的注解

(1)注解介绍

  • @PostConstruct
  • @PreDestroy
注解说明
@PostConstruct使用在方法上标注该方法是Bean的初始化方法
@PreDestroy使用在方法上标注该方法是Bean的销毁方法

(2) 使用注解初始化方法、注销方法

  • 这两个注解是java提供的
package com.etime.dao.impl;

import com.etime.dao.UserDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

//@Component("ud")
@Repository("ud")
public class UserDaoImpl implements UserDao {

    @PostConstruct
    public void init(){
        System.out.println("userDao被初始化了");
    }

    @Override
    public void show() {
        System.out.println("这是userDao的方法");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("userDao被销毁了");
    }
}

(3)测试

@Test
    public void t08() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        UserDao userDao = (UserDao) context.getBean("ud");
        userDao.show();
        context.close();
    }

五、用户改变作用范围的注解:@Scope

1、注解介绍

  • 使用@Scope标注Bean的范围
注解说明
@Scope标注Bean的作用范围,scope取值singleton prototype request session globalsession

Spring基于注解的IOC

1、 构建maven工程,添加框架技术依赖

<dependencies>
    <!--导入spring的context坐标-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Jdbc模块依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--导入C3P0连接池-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.2</version>
    </dependency>
    <!--导入junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

2、创建数据库及数据表,并创建实体类

  • 实体类
package com.etime.entity;

public class User {

    private int id;
    private String name;
    private String gender;

    public User() {
    }

    public User(int id, String name, String gender) {
        this.id = id;
        this.name = name;
        this.gender = gender;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }
}

3、注解配置管理的资源

(1)Dao层

  • UseDaor接口
package com.etime.dao;
import com.etime.entity.User;
import java.util.List;
public interface UserDao {
    List<User> getAllUser();
}
  • USerDaoImpl实现类
package com.etime.dao.impl;
import com.etime.dao.UserDao;
import com.etime.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository("ud")
public class UserDaoImpl implements UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public List<User> getAllUser() {
        List<User> list = null;
        String sql = "select * from user";
        list = jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(User.class));
        return list;
    }
}

(2)Service层

  • UserService接口
package com.etime.service;
import com.etime.entity.User;
import java.util.List;
public interface UserService {
    List<User> getAllUser();
}

  • UseServiceImpl实现类
package com.etime.service.impl;
import com.etime.dao.UserDao;
import com.etime.entity.User;
import com.etime.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("us")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public List<User> getAllUser() {
        return userDao.getAllUser();
    }
}

4、配置文件并开启对注解的支持并配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--配置扫描-->
    <context:component-scan base-package="com.etime"/>

    <!--读取properties配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--JdbcTemplate对象的创建-->
    <bean id="jt" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="ds"></property>
    </bean>
</beans>

5、编写测试代码

    @Test
    public void t09() {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        UserService userService = (UserService) context.getBean("us");
        List<User> list = userService.getAllUser();
        System.out.println(list);
    }

Spring纯注解配置

1、注解IOC案例待改造的问题

(1)问题引入

  • 上面的注解还不能全部替代xml配置文件,还需要使用注解替代

(2)注解替代的配置

  • 非自定义的Bean的配置:@Bean
  • 加载properties文件的配置:context:property-placeholder
  • 组件扫描的配置:context:component-scan
  • 引入其他文件:@import
  • 替代Spring核心配置文件:@Configuration,@ComponentScan,@Import

2、新注解说明

注解说明
@Configuration用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解,作用等价于applicationContext.xml 配置文件。
@ComponentScan用于指定 Spring在初始化容器时要扫描的包。 作用和在 Spring 的 xml 配置文件中的 <context:component-scan base-package=“com.offcn”/>一样
@Bean使用在方法上,标注将该方法的返回值存储到 Spring容器中。 id的值默认是方法的名称, 可以自定义id的值
@PropertySource用于加载xxx.properties 文件中的配置 结合@Value(“${}”) 取配置文件的值。
@Import用于导入其他配置类

2、编写示例代码

(1)创建db.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///spring
jdbc.username=root
jdbc.password=111

(2)@PropertySource,@value,@Bean的使用:

package com.etime.util;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * 做数据库相关配置
 * 1、读取properties文件内容
 * 2、根据读取的内容生成连接池对象
 * 3、根据连接池对象获取JdbcTemplate对象
 */

@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {
    @Value("${jdbc.driver}")
    private String driverClass;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    public DataSource getDataSource() throws PropertyVetoException {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        ds.setDriverClass(driverClass);
        ds.setJdbcUrl(url);
        ds.setUser(username);
        ds.setPassword(password);
        return ds;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate getJdbcTemplate() throws PropertyVetoException {
        return new JdbcTemplate(getDataSource());
    }
}

(3)@Configuration,@ComponentScan,@Import的使用

package com.etime.util;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(DataSourceConfig.class)
@ComponentScan("com.etime")
public class SpringConfig {
}

3、通过注解获取容器

    @Test
    public void t10() {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = (UserService) context.getBean("us");
        List<User> list = userService.getAllUser();
        System.out.println(list);
    }

Spring整合Junit

1、Spring整合Junit介绍

  • Spring整合Junit单元测试在学习阶段、开发阶段是经常使用的。
  • 如果开发过程当中完全不使用该技术点,不影响开发效果,可能会影响开发进度,为提高开发效率,避免重复性代码,Spring整合Junit单元测试务必要掌握,在使用Spring框架开发时,我们也推荐使用整合Junit后进行测试

2、IOC测试类中的问题和解决思路

(1)问题引出

  • 在测试类中,每个测试方法都有以下两行代码:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

(2)解决思路

  • 这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
  • 故,引出Spring整合Junit

3、注解介绍

注解说明
@RunWith替换原有运行器
@ContextConfiguration指定 Spring 配置文件的位置
@Autowired给测试类中的变量注入数据

4、 整合Junit配置步骤

(1) 添加技术依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
</dependency>

(2)使用注解完成容器的加载

@RunWith :替换原有运行器

@ContextConfiguration:指定 Spring 配置文件的位置

@Autowired:给测试类中的变量注入数据

package com.etime.test;

import com.etime.entity.Account;
import com.etime.dao.UserDao;
import com.etime.entity.User;
import com.etime.service.AccountService;
import com.etime.service.UserService;
import com.etime.util.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
//加载Spring的配置类,生成容器对象
@ContextConfiguration(classes = {SpringConfig.class})
public class SpringTest {

    @Autowired
    private ApplicationContext context;

    @Test
    public void t11() {
        UserService userService = (UserService) context.getBean("us");
        List<User> list = userService.getAllUser();
        System.out.println(list);
    }
}

SpringAOP入门

1、什么是 AOP

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

2、AOP编程思想

  • 面向切面编程(AOP)是一种编程的思想,是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是 函数式编程的一种衍生泛型。
  • 利用 AOP 可以对业务逻辑的各个部分进行隔离,,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  • AOP 使用场景
    • 事务管理
    • 权限校验
    • 日志记录
    • 性能监控

3、SPring中的常用术语

(1)Joinpoint

  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。
    • 在spring中,这些点指的是方法,因为spring只支持方法类型的连接点

(2)Pointcut

  • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
  • 真正被增强的方法

(3)Advice

  • Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。

4、面试问题

(1)通知的类型

  • 前置通知,正常返回通知,异常通知,最终通知,环绕通知。

(2)Introduction

  • Introduction(引介,了解): 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方 法或 Field。

(3)Target

  • Target(目标对象):代理的目标对象

(4)Proxy

  • Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类

(5)Weaving

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

(5)Aspect

  • Aspect(切面):是切入点和通知(引介)的结合,描述了 增强具体应用的位置。

5、AOP 的作用及优势

(1)作用

  • 在程序运行期间,在不修改源码的情况下对方法进行功能增强。体现了java语言的动态性【反射】

(2)优势

  • 减少重复代码,提高开发效率,并且便于维护

Spring实现转账案例

一、实现步骤

1、搭建一个转账的业务环境,准备所需资源,数据库表以及实体类
2、持久层具体实现使用DBUtils 方便操作连接对象,管理事务
3、搭建业务层,完成对持久层的调用
4、演示转账测试用例,在没有遇到异常情况下成功转账,在转账过程当中遇到意外情况,损失了数据的一致性,从而引出事务。

二、编写代码示例

1、准备Account表以及对应的实体bean

public class Account implements Serializable {
    private Integer id;
    private String name;
    private double money;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public double getMoney() {
        return money;
    }

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

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

2、创建AccountDao接口以及对应的实现类

  • AccountDao接口
package com.etime.dao;

import com.etime.entity.Account;

public interface AccountDao {
    public Account getByName(String name);
    public int update(Account account);
}
  • AccountDaoImpl实现类
package com.etime.dao.impl;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.etime.dao.AccountDao;
import com.etime.entity.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account getByName(String name) {
        String sql ="select *from account where name =? ";
        return jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<Account>(Account.class),name);
    }

    @Override
    public int update(Account account) {
        String sql ="update account set money =? where name =? ";
        return jdbcTemplate.update(sql,account.getMoney(),account.getName());
    }
}

3、创建AccountService接口以及对应的实现类

package com.etime.service;

public interface AccountService {
    public void transfer(String sourceAccountName,String targetAccountName,double money);
}
package com.etime.service.impl;

import com.etime.dao.AccountDao;
import com.etime.dao.impl.AccountDaoImpl;
import com.etime.entity.Account;
import com.etime.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public void transfer(String sourceAccountName, String targetAccountName, double money) {
        //收钱的
        Account tAccount = accountDao.getByName(targetAccountName);
        tAccount.setMoney(tAccount.getMoney()+money);
        int rows2 = accountDao.update(tAccount);
        System.out.println("收钱的:"+rows2);

        //给钱的
        Account sAccount = accountDao.getByName(sourceAccountName);
        sAccount.setMoney(sAccount.getMoney()-money);
        int rows1 = accountDao.update(sAccount);
        System.out.println("给钱的:"+rows1);
    }
}

4、测试转账业务

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DataSourceConfiguration.class})
public class SpringTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void t01(){
        AccountService us = (AccountService)applicationContext.getBean("accountService");
        us.transfer("aaa","bbb",500);
    }

5、测试没有事务正常情况和异常情况转账结果

  • 没有遇到异常情况,转账结果正常。aaa账户减少500元,bbb账户增加500元

  • 模拟异常情况转账结果aaa向bbb转账1000

6、编写ConnectionUtils工具类管理数据库连接

  • 转账过程,由于遇到异常情况,转账损失了数据的一致性,加入事务控制。

  • 事务要么同时成功,事务提交;要么都同时失败,事务回滚;要保证咋同一个事务当中进行操作,那么必须保证同一个连接对象。

  • 如何保证持久层和业务层使用同一个连接对象呢?

    • 引入ThreadLocal对象,该对象的特点,对象提供的存,取,移除的常用方法。
  • ConnectionUtils 工具类提供的管理连接的方法,就能保证持久层以及业务层获得相同的连接对象,保证在同一个事务当中进行操作

package com.etime.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.Connection;

@Component
public class ConnectionUtil {
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection() {
        try{
            //1.先从ThreadLocal上获取
            Connection connection = tl.get();
            //2.判断当前线程上是否有连接
            if (connection == null) {
                //3.从jdbcTemplate中获取一个连接,并且存入ThreadLocal中
                connection = jdbcTemplate.getDataSource().getConnection();
                tl.set(connection);
            }
            //4.返回当前线程上的连接
            return connection;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){
        tl.remove();
    }
}

7、编写事务管理工具类TransactionManager

  • TransactionManager 是管理事务的工具类,它主要定义了 和事务相关的方法。
    • 例如:例如:事务的开启,事务的提交,事务的回滚操作等
  • 该工具类目前需要手动创建和管理。
  • 使用Spring进行事务管理后,该工具类由Spring提供,不需要手动编写和管理。
  • 该工具类重在理解,为Spring进行事务管理做好铺垫
package com.etime.util;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TransactionManager {

    @Autowired
    private ConnectionUtil connectionUtil;

    //开启事务
    public  void beginTransaction(){
        try {
            connectionUtil.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //提交事务
    public  void commit(){
        try {
            connectionUtil.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //回滚事务
    public  void rollback(){
        try {
            connectionUtil.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //释放资源
    public  void release(){
        try {
            connectionUtil.removeConnection();//当前线程解除绑定
            connectionUtil.getThreadConnection().close();//还回连接池中
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

8、编写业务层及数据访问层事务控制代码并配置Spring的IOC

(1)Dao层
package com.etime.dao.impl;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.etime.dao.AccountDao;
import com.etime.entity.Account;
import com.etime.util.ConnectionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private ConnectionUtil connectionUtil;

    @Autowired
    private QueryRunner queryRunner;

    @Override
    public Account getByName(String name) throws SQLException {
        Connection conn = connectionUtil.getThreadConnection();
        String sql ="select *from account where name =? ";
        return queryRunner.query(conn,sql,new BeanHandler<>(Account.class),name);
    }

    @Override
    public int update(Account account) throws SQLException {
        Connection conn = connectionUtil.getThreadConnection();
        String sql ="update account set money =? where name =? ";
        return queryRunner.update(conn,sql,account.getMoney(),account.getName());
    }
}
  • 数据访问层用到了QueryRunner,相关配置
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.7</version>
        </dependency>
  • SpringConfiguration.java 配置类将QueryRunner对象交给Spring进行管理
 @Bean
    public QueryRunner getQueryRunner(){
        return new QueryRunner();
    }
(2)Service层
package com.etime.service.impl;

import com.etime.dao.AccountDao;
import com.etime.entity.Account;
import com.etime.service.AccountService;
import com.etime.util.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private TransactionManager transactionManager;

    @Override
    public void transfer(String sourceAccountName, String targetAccountName, double money) {
        try{
            //开启事务
            transactionManager.beginTransaction();

            //收钱的
            Account tAccount = accountDao.getByName(targetAccountName);
            tAccount.setMoney(tAccount.getMoney()+money);
            int rows2 = accountDao.update(tAccount);
            System.out.println("收钱的:"+rows2);

            //给钱的
            Account sAccount = accountDao.getByName(sourceAccountName);
            sAccount.setMoney(sAccount.getMoney()-money);
            int rows1 = accountDao.update(sAccount);
            System.out.println("给钱的:"+rows1);

            //提交事务
            transactionManager.commit();
        }catch (Exception exception){
            //回滚事务
            transactionManager.rollback();
            exception.printStackTrace();
        }finally {
            //释放资源
            transactionManager.release();
        }
    }
}
(3)转账测试
package com.etime.test;

import com.etime.entity.User;
import com.etime.service.AccountService;
import com.etime.service.UserService;
import com.etime.service.impl.AccountServiceImpl;
import com.etime.util.DataSourceConfiguration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DataSourceConfiguration.class})
public class SpringTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void t01(){
        AccountService us = (AccountService)applicationContext.getBean("accountService");
        us.transfer("aaa","bbb",2000);
    }

}
  • 结果:正常转账成功,遇到异常情况, 事务进行回滚,保证了数据的一致性。

Spring基于配置文件的AOP

1、环境搭建

(1)构建maven工程添加依赖

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>

(2)代码编写

  • 实体类、持久层、业务层使用Spring实现转账案例的代码

(3)创建 Spring 的配置文件并导入约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath*:db.properties</value>
            </list>
        </property>
    </bean>
    <!--数据源对象-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--配置JdbcTemplate模板对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <context:component-scan base-package="com.etime"></context:component-scan>
    <bean id="transactionManager" class="com.etime.util.TransactionManager"></bean>
    
</beans>

2、AOP 配置步骤

(1)使用 aop:confing 声明 AOP 配置

  • 作用: 开始声明aop配置
<aop:config>
      <!-- 配置的代码都写在此处 -->
</aop:config>

(2) 使用 aop:aspect 配置切面

  • 作用: 用于配置切面
  • 属性
    • id :给切面提供一个唯一标识。
    • ref:引用配置好的通知类 bean 的 id。
   <aop:aspect id="tm" ref="transactionManager">
            ...
   </aop:aspect>

(3) 使用 aop:pointcut 配置切入点表达式

  • 作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
  • 属性
    • expression:用于定义切入点表达式。
    • id:用于给切入点表达式提供一个唯一标识
<aop:pointcut id="point1" expression="execution(public void com.etime.service.impl.AccountServiceImpl.transfer(..))"/

(4) 使用 aop:xxx 配置对应的通知类型

a.前置通知
  • aop:before
  • 作用:用于配置前置通知。指定增强的方法在切入点方法之前执行
  • 属性
    • method:用于指定通知类中的增强方法名称
    • ponitcut-ref:用于指定切入点的表达式的引用
    • poinitcut:用于指定切入点表达式
  • 执行时间
    • 切入点方法执行之前执行
<aop:before method="beginTransaction" pointcut-ref="point1"></aop:before>
b.后置通知
  • aop:after-returning
  • 作用: 用于配置后置通知
  • 属性
    • method:指定通知中方法的名称。
    • pointct:定义切入点表达式
    • pointcut-ref:指定切入点表达式的引用
  • 执行时间
    • 切入点方法正常执行之后。它和异常通知只能有一个执行
<aop:after-returning method="commit" pointcut-ref="point1"></aop:after-returning> 
c.异常通知
  • aop:after-throwing
  • 作用:用于配置异常通知
  • 属性
    • method:指定通知中方法的名称。
    • pointct:定义切入点表达式
    • pointcut-ref:指定切入点表达式的引用
  • 执行时间
    • 切入点方法执行产生异常后执行。它和后置通知只能执行一个
<aop:after-throwing method="rollback" pointcut-ref="point1"></aop:after-throwing>
d.最终通知
  • aop:after
  • 作用:用于配置最终通知
  • 属性
    • method:指定通知中方法的名称。
    • pointct:定义切入点表达式
    • pointcut-ref:指定切入点表达式的引用
  • 执行时间
    • 无论切入点方法执行时是否有异常,它都会在其后面执行。
<aop:after method="release" pointcut-ref="point1"></aop:after>

3、切入点表达式说明

(1)切点表达式的语法

execution([修饰符] 返回值类型 包名.类名.方法名(参数))
  • 访问修饰符可以省略
  • 返回值类型、包名、类名、方法名可以使用星号* 代表任意
  • 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
  • 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
a.全匹配方式
public void 
com.etime.service.impl.AccountServiceImpl.saveAccount(com.etime.domain.Account)
b.访问修饰符可以省略
void com.etime.service.impl.AccountServiceImpl.saveAccount(com.etime.domain.Account)
c.返回值可以使用*号,表示任意值
* com.etime.service.impl.AccountServiceImpl.saveAccount(com.etime.domain.Account)
d.包名可以使用 * 号,表示任意包 (但是有几级包,需要写几个 *)
* *.*.*.*.AccountServiceImpl.saveAccount(com.etime.domain.Account)
e.使用…来表示当前包,及其子包
* com..AccountServiceImpl.saveAccount(com.etime.domain.Account)
f.类名可以使用*号,表示任意类
* com..*.saveAccount(com.etime.domain.Account)
g.方法名可以使用*号,表示任意方法
* com..*.*( com.etime.domain.Account)
h.参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* com..*.*(*)
i.参数列表可以使用…表示有无参数均可,有参数可以是任意类型
* com..*.*(..)
j.全通配方式:
* *..*.*(..)
k.注意:通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.etime.service.impl.*.*(..))

4、环绕通知配置事务管理

(1)在TransactionManager类当中添加方法

/**
     * 环绕通知:
     * spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
     * 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
     * @param pjp
     * @return
     */
public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object returnValue = null;
    try {
        //获取方法执行所需的参数
        Object[] args = pjp.getArgs();
        //前置通知:开启事务
        beginTransaction();
        //执行方法
        returnValue = pjp.proceed(args);
        //后置通知:提交事务
        commit();
    }catch(Throwable e) {
        //异常通知:回滚事务
        rollback();
        e.printStackTrace();
    }finally {
        //最终通知:释放资源
        release();
    }
    return returnValue;
}

(2)环绕通知

  • aop:around
  • 作用:用于配置环绕通知
  • 属性
    • method:指定通知中方法的名称。
    • pointct:定义切入点表达式
    • pointcut-ref:指定切入点表达式的引用
  • 说明:它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
  • 注意:通常情况下,环绕通知都是独立使用的
    <aop:config>
        <aop:aspect id="tm" ref="transactionManager">
            <aop:pointcut id="point1"
                          expression="execution(public void com.etime.service.impl.AccountServiceImpl.transfer(..))"/>
            <aop:around method="transactionAround" pointcut-ref="point1"></aop:around>
        </aop:aspect>
    </aop:config>
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

咸鱼不咸鱼

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值