Spring整理

Spring整理

IoC概念

  • IoC(Inversion Of Control)控制反转,Spring反向控制应用程序所需要使用的外部资源。举例

  • Spring控制的资源将全部放置在Spring容器中,该容器称为IoC容器

  • IoC是面向对象编程的一种设计原则,可以用来降低代码之间的耦合度

bean

  • 名称:bean

  • 类型:标签

  • 归属:beans标签

  • 作用:定义spring中的资源,受此标签定义的资源将受到spring控制

  • 基本属性:

    <bean id="userService" name="userService1, userService2" class="com.itheima.service.impl.UserServiceImpl"/>

    id:bean的名称,通过id值获取bean

    class:bean的类型

    name:bean的名称,可以通过name值获取bean,用于多人共同开发时给bean起别名

set注入(重点)

  • 名称:property

  • 类型:标签

  • 归属:bean标签

  • 作用:使用set方法的形式为bean提供资源

  • 格式:

    <bean>
    <property />
    </bean>
  • 基本属性:

    • 引用类型:自定义类产生的对象,使用ref指定

    • 非引用类型:基本数据类型(int, char,...)和特殊类型String,使用value指定

    <!--非引用类型注入-->
    <property name="propertyName" value="propertyValue" />

    <!--引用类型注入-->
    <property name="propertyName" ref="beanId"/>

name:对应bean中的属性名,要求该属性必须提供可访问的set方法(严格规范:此名称是set方法对应名称)

value:设定非引用类型属性对应的值,不能与ref同时使用

ref:设定引用类型属性对应bean的id ,不能与value同时使用

集合类型数据注入

  • 名称:array,list,set,map,props

  • 类型:标签

  • 归属:property标签 或 constructor-arg标签

  • 作用:注入集合数据类型属性

  • 格式:

(1)集合类型数据注入——list

<!--setAl()-->
<property name="al">
   <list>
       <value>itheima</value>
       <value>66666</value>
   </list>
</property>

(2)集合类型数据注入——props

<property name="properties">
   <props>
       <prop key="name">itheima666</prop>
       <prop key="value">666666</prop>
   </props>
</property>

 

综合案例(重点)(.xml格式)

案例介绍

  • 使用spring整合mybatis技术,完成账户模块(Account)的基础增删改查功能

  • 账户模块对应字段

    • 编号:id

    • 账户名:name

    • 余额:money

案例分析

下图中标红部分需要咱们来实现

基础准备工作

  • 环境准备:导入Spring坐标,MyBatis坐标,MySQL坐标,Druid坐标

    <dependencies>
       
       <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context</artifactId>
           <version>5.1.9.RELEASE</version>
       </dependency>
       
       <dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis</artifactId>
           <version>3.5.3</version>
       </dependency>
       
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>5.1.47</version>
       </dependency>

       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>druid</artifactId>
           <version>1.1.16</version>
       </dependency>
    </dependencies>
  • 业务类与接口准备

    1. 创建数据库表,并制作相应的实体类Account

    2. 定义业务层接口与数据层接口

    3. 在业务层调用数据层接口,并实现业务方法的调用

  • 基础配置文件

    1. jdbc.properties

    2. MyBatis映射配置文件

整合准备工作

1.spring配置文件,加上context命名空间,用于加载properties文件

<?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">

2.开启加载properties文件

<context:property-placeholder location="classpath:jdbc.properties"/>

3.配置数据源Druid

<!--加载druid资源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
   <property name="driverClassName" value="${jdbc.driver}"/>
   <property name="url" value="${jdbc.url}"/>
   <property name="username" value="${jdbc.username}"/>
   <property name="password" value="${jdbc.password}"/>
</bean>

4.定义service层bean,并使用property标签注入dao

<!--配置service作为spring的bean,注入dao-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
   <property name="accountDao" ref="accountDao"/>
</bean>

5.dao的bean无需定义,MyBatis会自动生成代理对象

 

整合工作

1.导入Spring整合MyBatis坐标

<!--Spring整合MyBatis坐标-->
<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis-spring</artifactId>
   <version>1.3.0</version>
</dependency>

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-jdbc</artifactId>
   <version>5.1.9.RELEASE</version>
</dependency>

2.将mybatis配置成spring管理的bean(SqlSessionFactoryBean),并将类型别名交由spring处理

<!--spring整合mybatis后控制的创建连接用的对象-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="dataSource" ref="dataSource"/>
   <property name="typeAliasesPackage" value="com.itheima.domain"/>
</bean>

3.通过spring加载mybatis的映射配置文件到spring环境中(映射Mapper扫描工作交由spring处理)

<!--加载mybatis映射配置的扫描,将其作为spring的bean进行管理-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
   <property name="basePackage" value="com.itheima.dao"/>
</bean>

4.使用spring环境获取业务层bean,执行操作

public class App {
   public static void main(String[] args) {
       ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
       AccountService accountService = (AccountService) ctx.getBean("accountService");

       //查询
       Account ac = accountService.findById(13);
       System.out.println(ac);

       //保存
       Account account = new Account();
       account.setName("jack");
       account.setMoney(123456.78);
       accountService.save(account);
  }
}

注解方式开发

常用注解(重点)

启动注解功能

  • 启动注解扫描,加载类中配置的注解项

    <!--注解总开关-->
    <context:component-scan base-package="com.itheima"/>
  • 说明:

    • 在进行包所扫描时,会对配置的包及其子包中所有文件进行扫描

    • 扫描过程是以文件夹递归迭代的形式进行的

    • 扫描过程仅读取合法的java文件

    • 扫描时仅读取spring可识别的注解

    • 扫描结束后会将可识别的有效注解转化为spring对应的资源加入IoC容器

  • 注意:

    • 无论是注解格式还是XML配置格式,最终都是将资源加载到IoC容器中,差别仅仅是数据读取方式不同

    • 从开发效率上来说注解优于XML配置文件

bean的定义

  • 名称:@Component

  • 类型:类注解

  • 位置:类定义上方

  • 作用:设置该类为spring管理的bean

  • 范例:

    @Component("userService")
    public class UserServiceImpl implements UserService {}

     

    加载第三方资源

    • 名称:@Bean

    • 类型:方法注解

    • 位置:方法定义上方

    • 作用:设置该方法的返回值作为spring管理的bean

    • 范例:

      @Bean("dataSource")
      public DruidDataSource createDataSource() {    return ……;    }

bean的非引用类型属性注入

  • 名称:@Value

  • 类型:属性注解、方法注解

  • 位置:属性定义上方,set方法定义上方

  • 作用:设置对应属性的值或对方法进行传参

  • 范例:

    @Value("zhangsan")
    private String username;

     

bean的引用类型属性注入

  • 名称:@Autowired、@Qualifier

  • 类型:属性注解、方法注解

  • 位置:属性定义上方,方法定义上方

  • 作用:设置对应属性的对象或对方法进行引用类型传参

  • 范例:

    @Autowired(required = false)
    private UserDao userDao;

     

加载properties文件

  • 名称:@PropertySource

  • 类型:类注解

  • 位置:类定义上方

  • 作用:加载properties文件中的属性值

  • 范例:

    @PropertySource(value={"classpath:jdbc.properties","classpath:abc.properties"},ignoreResourceNotFound = true)
    public class ClassName {
        @Value("${propertiesAttributeName}")
        private String attributeName;
    }

     

纯注解格式

  • 名称:@Configuration、@ComponentScan

  • 类型:类注解

  • 位置:类定义上方

  • 作用:设置当前类为spring核心配置加载类

  • 范例:

    @Configuration
    @ComponentScan("scanPackageName")
    public class SpringConfigClassName{
    }
  • 说明:

    • 核心配合类用于替换spring核心配置文件,此类可以设置空的,不设置变量与属性

    • bean扫描工作使用注解@ComponentScan替代

AnnotationConfigApplicationContext

  • 加载纯注解格式上下文对象,需要使用AnnotationConfigApplicationContext

  • 当配置类作为 AnnotationConfigApplicationContext 对象创建的参数时,@Configuration注解可以不写

    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

第三方bean配置与管理(重点)

  • 名称:@Import

  • 类型:类注解

  • 位置:类定义上方

  • 作用:导入第三方bean作为spring控制的资源

  • 范例:

    @Configuration
    @Import(OtherClassName.class)
    public class ClassName {
    }

    注解整合MyBatis步骤

    1.修改mybatis外部配置文件格式为注解格式

    public interface AccountDao {
    
        @Insert("insert into account(name,money)values(#{name},#{money})")
        void save(Account account);
    
        @Delete("delete from account where id = #{id} ")
        void delete(Integer id);
    
        @Update("update account set name = #{name} , money = #{money} where id = #{id} ")
        void update(Account account);
    
        @Select("select * from account")
        List<Account> findAll();
    
        @Select("select * from account where id = #{id} ")
        Account findById(Integer id);
    }

    2.业务类使用@Service声明bean,使用@Autowired注入对象

    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
    
        @Autowired
        private AccountDao accountDao;
    }

    3.编写Spring配置类:SpringConfig,并加载properties文件

    @Configuration
    @PropertySource("classpath:jdbc.properties")
    public class SpringConfig {
    }

    4.建立配置文件JDBCConfig与MyBatisConfig类,并将其导入到核心配置类SpringConfig

    数据源配置类:JDBCConfig

    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;
    
        @Bean(value = "dataSource")
        public DataSource getDataSource(){
            DruidDataSource ds = new DruidDataSource();
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setUsername(userName);
            ds.setPassword(password);
            return ds;
        }
    }

    MyBatis配置类:MyBatisConfig

    public class MyBatisConfig {
    
        @Bean
        public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
            SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
            ssfb.setTypeAliasesPackage("com.itheima.domain");
            ssfb.setDataSource(dataSource);
            return ssfb;
        }
    
        @Bean
        public MapperScannerConfigurer getMapperScannerConfigurer(){
            MapperScannerConfigurer msc = new MapperScannerConfigurer();
            msc.setBasePackage("com.itheima.dao");
            return msc;
        }
    
    }

    5.开启注解扫描,将JDBCConfig与MyBatisConfig类导入到核心配置类SpringConfig中

    @Configuration
    @ComponentScan("com.itheima")
    @PropertySource("classpath:jdbc.properties")
    @Import({JDBCConfig.class,MyBatisConfig.class})
    public class SpringConfig {
    }

    6.使用AnnotationConfigApplicationContext对象加载配置项

    public class App { 
        public static void main(String[] args) {
            ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
            AccountService accountService = (AccountService) ctx.getBean("accountService");
            Account ac = accountService.findById(3);
            System.out.println(ac);
        }
    }

     

采用注解开发的时候会在testjava下运行测试

注解整合Junit

1.导入Spring整合Junit坐标,从Spring5.0以后,要求Junit的版本必须是4.12及以上

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.1.9.RELEASE</version>
</dependency>

2.Spring接管Junit的运行权,使用Spring专用的Junit类加载器

@RunWith(SpringJUnit4ClassRunner.class)

3.加载Spring配置类

@ContextConfiguration(classes = SpringConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
    
    @Test
    public void testSave() {}
}

AOP(重点是注解格式)

AOP概念

  • AOP(Aspect Oriented Programing)面向切面编程,是一种编程范式,隶属于软件工程范畴。

  • AOP基于OOP基础之上进行横向开发,是对 OOP 编程方式的一种补充,并非是取而代之。

  • AOP不是由Spring提出的,最佳开源实现是AspectJ;Spring集成了AspectJ 实现AOP

AOP作用和优势

  • AOP能够将那些与业务无关,却为业务模块所共同调用的功能,比如

    • 日志记录

    • 性能监控

    • 事务管理

    • 权限控制

      减少系统的重复代码,降低模块之间的耦合度,并有利于将来维护。

AOP核心概念

  • Joinpoint(连接点):就是方法

  • Pointcut(切入点):就是挖掉共性功能的方法

  • Advice(通知):就是共性功能,最终以一个方法的形式呈现

  • Aspect(切面):就是共性功能与挖的位置的对应关系

  • Target(目标对象):就是挖掉功能的方法对应的类产生的对象,这种对象是无法直接完成最终工作的

  • Weaving(织入):就是将挖掉的功能回填的动态过程

  • Proxy(代理):目标对象无法直接完成工作,需要对其进行功能回填,通过创建原始对象的代理对象实现

入门案例制作

下面使用XML方式来开发:

1.导入aspectj的坐标

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

2.确认要抽取的功能:并将其制作成方法保存到专用的类中,最后删除原始业务中对应的功能 1591282302976

3.引入apo命名空间,然后将所有进行AOP操作的资源加载到IoC容器中

  • 引入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:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           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/context
            https://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--3.开启AOP命名空间-->
  • 在applicationContextx.xml中配置userService和通知类AOPAdvice

    <bean id="userService" class="com.itheima.service.impl.UserServiceImpl"/>
    
    <!--2.配置共性功能成为spring控制的资源-->
    <bean id="myAdvice" class="com.itheima.aop.AOPAdvice"/>
  • 对比之前新增的改动 1591282320624

4.使用配置的方式描述被抽取功能的位置,并描述被抽取功能与对应位置的关系

<!--aop配置-->
<aop:config>
    <!--配置切入点-->
    <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
    <!--配置切面-->
    <aop:aspect ref="myAdvice">
        <!--通知与切入点之间的关系-->
        <aop:before method="logAdvice" pointcut-ref="pt"/>
    </aop:aspect>
</aop:config>

5.运行App类

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.save();

AOP配置

aop:config

  • 名称:aop:config

  • 类型:标签

  • 归属:beans子标签

  • 作用:设置AOP

  • 格式:

    <beans>
        <aop:config>……</aop:config>
        <aop:config>……</aop:config>
    </beans>
  • 说明:一个beans标签中可以配置多个aop:config标签

aop:aspect

  • 名称:aop:aspect

  • 类型:标签

  • 归属:aop:config标签

  • 作用:设置具体的AOP通知对应的切入点

  • 格式:

    <bean id="myAdvice" class="com.itheima.aop.AOPAdvice"/>
    
    <aop:config>
        <aop:aspect ref="myAdvice">
            <aop:before method="before" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>
  • 说明:

    一个aop:config标签中可以配置多个aop:aspect标签

  • 基本属性:

    • ref :通知类的beanId

aop:pointcut

  • 名称:aop:pointcut

  • 类型:标签

  • 归属:aop:config标签、aop:aspect标签

  • 作用:设置切入点

  • 格式:

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* *..*(..))"/>
        
        <aop:aspect ref="myAdvice">
            <aop:before method="before" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>

     

切入点表达式的组成

  • 切入点表达式是一个快速匹配方法描述的通配格式,类似于正则表达式

    关键字([访问修饰符] 返回值 包名.类名.方法名(参数) [异常名])
    
    #简写方式,省略(访问修饰符、异常名)
    关键字(返回值 包名.类名.方法名(参数))

关键字:描述表达式的匹配模式(参看关键字列表) 常用execution

访问修饰符:方法的访问控制权限修饰符,public、 protected、private,可以省略不写

类名:方法所在的类(此处可以配置接口名称)

异常:方法定义中指定抛出的异常,可以省略不写

切入点表达式——逻辑运算符

  • && :连接两个切入点表达式,表示两个切入点表达式同时成立的匹配

  • || :连接两个切入点表达式,表示两个切入点表达式成立任意一个的匹配

  • ! :连接单个切入点表达式,表示该切入点表达式不成立的匹配

切入点表达式——范例

execution(* *(..))
execution(* *..*(..))
execution(* *..*.*(..))
execution(public * *..*.*(..))
execution(public int *..*.*(..))
execution(public void *..*.*(..))
execution(public void com..*.*(..)) 
execution(public void com..service.*.*(..))
execution(public void com.itheima.service.*.*(..))
execution(public void com.itheima.service.User*.*(..))
execution(public void com.itheima.service.*Service.*(..))
execution(public void com.itheima.service.UserService.*(..))
execution(public User com.itheima.service.UserService.find*(..))
execution(public User com.itheima.service.UserService.*Id(..))
execution(public User com.itheima.service.UserService.findById(..))
execution(public User com.itheima.service.UserService.findById(int))
execution(public User com.itheima.service.UserService.findById(int,int))
execution(public User com.itheima.service.UserService.findById(int,*))
execution(public User com.itheima.service.UserService.findById(*,int))
execution(public User com.itheima.service.UserService.findById())
execution(List com.itheima.service.*Service+.findAll(..))

通知类型(重点)

aop:before

  • 名称:aop:before

  • 类型:标签

  • 归属:aop:aspect标签

  • 作用:设置前置通知

  • 格式:

    <aop:aspect ref="adviceId">
        <!--直接配置切入点-->
        <aop:before method="logAdvice" pointcut="execution(* *(..))"/>
    </aop:aspect>
    <aop:aspect ref="adviceId">
        <!--使用公共切入点-->
        <aop:before method="logAdvice" pointcut-ref="pt"/>
    </aop:aspect>

     

aop:after

  • 名称:aop:after

  • 类型:标签

  • 归属:aop:aspect标签

  • 作用:设置后置通知

  • 格式:

    <aop:aspect ref="adviceId">
        <aop:after method="methodName" pointcut="……"/>
    </aop:aspect>

     

aop:after-returning

  • 名称:aop:after-returning

  • 类型:标签

  • 归属:aop:aspect标签

  • 作用:设置返回后通知

  • 格式:

    <aop:aspect ref="adviceId">
        <aop:after-returning method="methodName" pointcut="……"/>
       
    </aop:aspect>

     

aop:after-throwing

  • 名称:aop:after-throwing

  • 类型:标签

  • 归属:aop:aspect标签

  • 作用:设置抛出异常后通知

  • 格式:

    <aop:aspect ref="adviceId">
        <aop:after-throwing method="methodName" pointcut="……" throwing="t"/>
    </aop:aspect>
  • 说明:一个aop:aspect标签中可以配置多个aop:after-throwing标签

  • 基本属性:

    • method :在通知类中设置当前通知类别对应的方法

    • pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突

    • pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突

aop:around(重点)

  • 名称:aop:around

  • 类型:标签

  • 归属:aop:aspect标签

  • 作用:设置环绕通知

  • 格式:

    <aop:aspect ref="adviceId">
        <aop:around method="methodName" pointcut="……"/>
    </aop:aspect>
  • 说明:一个aop:aspect标签中可以配置多个aop:around标签

  • 基本属性:

    • method :在通知类中设置当前通知类别对应的方法

    • pointcut :设置当前通知对应的切入点表达式,与pointcut-ref属性冲突

    • pointcut-ref :设置当前通知对应的切入点id,与pointcut属性冲突

环绕通知的开发方式:

  • 环绕通知可以在原始方法的前后分别添加功能,因此必须在在环绕通知中对原始方法进行显式调用

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("原始方法执行前执行...")
        Object ret = pjp.proceed();
        System.out.println("原始方法执行后执行...")
        return ret;
    }
  • 注意事项

    • 方法的第一个参数必须是ProceedingJoinPoint,通过该对象的proceed()方法,实现对原始方法的调用

    • 使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常,需要强制抛出Throwable对象,封装原始方法中可能出现的异常信息

通知中获取数据

获取参数数据

第一种方式:

  • 设定通知方法第一个参数为JoinPoint,通过该对象调用getArgs()方法,获取原始方法运行的参数数组

    public void before(JoinPoint jp) {
        //通过JoinPoint参数获取调用原始方法所携带的参数
        Object[] args = jp.getArgs();
        System.out.println("before..." + Arrays.toString(args));
    }
    public class App {
        public static void main(String[] args) {
           	...
            userService.save(666, 888);
        }
    }

获取返回值数据

第一种:适用于返回后通知(after-returning)

  • 设定返回值变量名

  • 原始方法

    public int update() {
        System.out.println("user service update running....");
        return 100;
    }
  • AOP配置

    <aop:aspect ref="myAdvice">
        <aop:pointcut id="pt3" expression="execution(* *(..))  "/>
        <aop:after-returning method="afterReturning" pointcut-ref="pt3" returning="ret"/>
    </aop:aspect>
  • 通知类

    public void afterReturning(Object ret) {
        System.out.println("afterReturning..." + ret);
    }
  • 在main方法中执行userService.update(),查看执行结果

第二种:适用于环绕通知(around)

  • 在通知类的环绕方法中调用原始方法获取返回值

  • 编写带返回值的原始方法

    public int upate() {
        System.out.println("user service running...");
        return 100;
    }
  • AOP配置

    <aop:aspect ref="myAdvice">
        <aop:pointcut id="pt2" expression="execution(* *(..))  "/>
        <aop:around method="around" pointcut-ref="pt2" />
    </aop:aspect>
  • 通知类

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object ret = pjp.proceed();
        System.out.println("around after..." + ret);
        return ret;
    }
  • 在main方法中执行userService.update(),查看执行结果

通知获取异常数据-视频11

第一种:通知类的方法中调用原始方法捕获异常,适用于环绕通知(around)

  • 原始方法:模拟抛出除0异常

    public void save() {
        System.out.println("user service running...");
        int i = 1/0;
    }
  • AOP配置

    <aop:aspect ref="myAdvice">
        <aop:pointcut id="pt4" expression="execution(* *(..))  "/>
        <aop:around method="around" pointcut-ref="pt4" />
    </aop:aspect>
  • 通知类

    public Object around(ProceedingJoinPoint pjp) {
        System.out.println("around before...");
        Object ret = null;
        try {
            //对原始方法的调用
            ret = pjp.proceed();
        } catch (Throwable throwable) {
            System.out.println("around...exception...." + throwable.getMessage());
        }
        System.out.println("around after..." + ret);
        return ret;
    }

     

AOP配置(注解格式)

AOP注解驱动

  • 名称:@EnableAspectJAutoProxy

  • 类型:注解

  • 位置:Spring注解配置类定义上方

  • 作用:设置当前类开启AOP注解驱动的支持,加载AOP注解

综合案例

5.1)案例介绍

对项目业务层接口执行监控,统计业务层接口的执行效率

public interface AccountService {
    void save(Account account);
    void delete(Integer id);
    void update(Account account);
    List<Account> findAll();
    Account findById(Integer id);
}

5.2)案例分析

  • 测量接口执行效率:接口方法执行前后获取执行时间,求出执行时长

    使用System.currentTimeMillis( )获取系统时间

  • 对项目进行监控:项目中所有查询方法,使用AOP的环绕通知,在proceed()方法执行前后获取系统时间

5.3)案例制作步骤

  1. 制作AOP环绕通知

@Component
@Aspect
public class RunTimeMonitorAdvice {
    public Object runtimeAround(ProceedingJoinPoint pjp) throws Throwable {
    }
}
  1. 在通知类RunTimeMonitorAdvice中:定义切入点,绑定到接口上

  //拦截所有的业务层接口中查询操作的执行
  @Pointcut("execution(* com.itheima.service.*Service.find*(..))")
  public void pt(){}
  1. 完善runtimeAround()方法实现,完成测量功能

    @Around("pt()")
    public Object runtimeMonitor(ProceedingJoinPoint pjp) throws Throwable {
        //获取执行签名信息
        Signature signature = pjp.getSignature();
        //通过签名获取执行类型(接口名)
        String targetClass = signature.getDeclaringTypeName();
        //通过签名获取执行操作名称(方法名)
        String targetMethod = signature.getName();
        //获取操作前系统时间beginTime
        long beginTime = System.currentTimeMillis();
        Object ret = pjp.proceed(pjp.getArgs());
        //获取操作后系统时间endTime
        long endTime = System.currentTimeMillis();
        System.out.println(targetClass+" 中 "+targetMethod+" 运行时长 "+(endTime-beginTime)+"ms");
        return ret;
    }
  2. 开启注解驱动支持:@EnableAspectJAutoProxy

    @Configuration
    @ComponentScan("com.itheima")
    @PropertySource("classpath:jdbc.properties")
    @Import({JDBCConfig.class,MyBatisConfig.class})
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }
  3. 运行UserServiceTest单元测试

    //设定spring专用的类加载器
    @RunWith(SpringJUnit4ClassRunner.class)
    //设定加载的spring上下文对应的配置
    @ContextConfiguration(classes = SpringConfig.class)
    public class UserServiceTest {
    
        @Autowired
        private AccountService accountService;
    
        @Test
        public void testFindById(){
            Account ac = accountService.findById(13);
            System.out.println(ac);
        }
    
        @Test
        public void testFindAll(){
            List<Account> list = accountService.findAll();
            System.out.println(list);
        }
    
        @Test
        public void testUpdate() {
            Account ac = accountService.findById(13);
            ac.setMoney(0.01);
            ac.setName("没钱了");
            accountService.update(ac);
        }
    
    }

     

Spring事务

事务的作用

事务特征(ACID)

  • 原子性(Atomicity)指事务是一个不可分割的整体,其中的操作要么全执行或全不执行

  • 一致性(Consistency)事务前后数据的完整性必须保持一致。比如:张三向李四转100元,转账前和转账后的数据是正确状态这叫一致性,如果出现张三转出100元,李四账户没有增加100元这就出现了数据错误,就没有达到一致性。

  • 隔离性(Isolation)事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离

  • 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

四种隔离级

未提交读(Read Uncommitted) - 允许脏读,会去读到其他事务中未提交的数据

提交读(Read Committed) ----- 解决了脏读

可重复读(Repeatable Read) ------消除了不可重复读

串行读(Serializable) ------- 解决了幻读

Spring事务核心对象

Spring提供如下三个接口为业务层提供了整套的事务解决方案

  • 事务定义对象:TransactionDefinition

  • 事务状态:TransactionStatus

  • 平台事务管理器:PlatformTransactionManager

TransactionDefinition(事务对象)

此接口定义了事务的基本信息

  • 获取事务定义名称

    String getName()
  • 获取事务的读写属性

    boolean isReadOnly()
  • 获取事务隔离级别

    int getIsolationLevel()
  • 获事务超时时间

    int getTimeout()
  • 获取事务传播行为特征

    int getPropagationBehavior()

TransactionStatus(事务状态)

PlatformTransactionManager:事务管理器

DataSourceTransactionManager(事务管理器的实现类) 适用于Spring JDBC或MyBatis

 

声明式事务(XML)

使用声明式事务需要引入tx命名空间:

<?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"
       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/context
        https://www.springframework.org/schema/context/spring-context.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">

tx:advice

  • 名称:tx:advice

  • 类型:标签

  • 归属:beans标签

  • 作用:配置事务专属通知类

  • 格式:

    <beans>
        <tx:advice id="txAdvice" transaction-manager="txManager">
        </tx:advice>
    </beans>
  • 基本属性:

    • id :用于配置aop时指定通知类的id

    • transaction-manager :指定事务管理器bean

tx:attributes

  • 名称:tx:attributes

  • 类型:标签

  • 归属:tx:advice标签

  • 作用:定义通知属性

  • 格式:

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="*" read-only="false" />
            <tx:method name="get*" read-only="true" />
            <tx:method name="find*" read-only="true" />
         </tx:attributes>
    </tx:advice>

aop:advisor

使用aop:advisor在AOP配置中引用事务专属通知类

<aop:config>
    <aop:pointcut id="pt" expression="execution(* com.itheima.service.*Service.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>

 

使用XML配置事务

  1. 设置事务管理器

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
  2. 配置专用事务通知器

    <!--定义事务管理的通知类-->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!--定义控制的事务-->
        <tx:attributes>
            <tx:method name="*" read-only="false"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="transfer" read-only="false"/>
        </tx:attributes>
    </tx:advice>
  3. AOP配置切面,使用通知器绑定切入点

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.itheima.service.*Service.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>
  4. 测试转账

    public class App {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            AccountService accountService = (AccountService) ctx.getBean("accountService");
            accountService.transfer("tom","itheima",100D);
        }
    }

     

声明式事务(纯注解驱动)

pom.xml

 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.9.RELEASE</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

jdbc.properties配置文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db
jdbc.username=root
jdbc.password=root

Dao包接口SQL语句采用注解格式

public interface AccountDao {
    /**
     * 入账操作
     * @param name      入账用户名
     * @param money     入账金额
     */
    @Update( "update account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    /**
     * 出账操作
     * @param name      出账用户名
     * @param money     出账金额
     */
    @Update( "update account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}

domain包

public class Account implements Serializable {

    private Integer id;
    private String name;
    private Double money;
    省略set方法和toString...
    }

service接口

public interface AccountService {
    /**
     * 转账操作
     * @param outName     出账用户名
     * @param inName      入账用户名
     * @param money       转账金额
     */
     //代表切入点和事务的一系列默认值
    @Transactional

    public void transfer(String outName,String inName, Double money);

}

service实现类

//注解代表配置文件里面的Bean:id
@Service("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public void transfer(String outName, String inName, Double money) {
        
        accountDao.outMoney(outName, money);
//        int a = 1 / 0;
        accountDao.inMoney(inName, money);


    }
}

JDBCConfig类

//扫描配置文件,并且加载
@Component
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;

    //连接池
    @Bean("dataSource")
    public DruidDataSource getDataSource() {

        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);

        return ds;
    }
    //事务管理器
    @Bean
    public PlatformTransactionManager getTransactionManager(DataSource dataSource){
       return new DataSourceTransactionManager(dataSource);
    }
}

MyBatisConfig类

public class MyBatisConfig {
    //定义SqlSessionFactoryBean的时候,dataSource属性是必须指定的,它表示用于连接数据库的数据源
    //spring整合mybatis后控制的创建连接用的对象
    @Bean
    public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
        SqlSessionFactoryBean ssfb=new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    //整合dao接口所有的操作,不用再去为dao配Bean了
    //加载mybatis映射配置的扫描,将其作为spring的bean进行管理
    @Bean
    public MapperScannerConfigurer getMapperScannerConfigurer(){
        MapperScannerConfigurer msc=new MapperScannerConfigurer();
        //basePackage:这个属性就是映射接口的包,这个包里面的所有的接口扫描到
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }

}

SpringConfig类(重点)

//纯注解格式
@Configuration
//设置当前类为spring核心配置加载类
@ComponentScan("com.itheima")
//加载properties文件中的属性值
@PropertySource("classpath:jdbc.properties")
//导入写配置的两个类,事务管理器也在JDBCConfig类里面
@Import({JDBCConfig.class,MyBatisConfig.class})
//注解 @EnableTransactionManagement 开启事务支持
@EnableTransactionManagement
public class SpringConfig {
}

主方法测试类UserServiceTest

//设定Spring专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
//设定加载Spring上下文对应的配置
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
        accountService.transfer("Mike","Jock",500D);
    }
}

 

原文地址:https://www.cnblogs.com/sunhao410526/p/14525853.html