Spring使用

一、核心基础

(一)基于XML的使用

  1、Ioc配置

    (1)Bean标签介绍

      bean标签作用:用于配置被Spring容器管理的bean信息。默认情况下它调用的是类中的无参构造函数,如果没有无参构造,则不能创建。

      bean标签属性:

标签 描述
id 给对象在容器中提供一个唯一的标识,用于获取对象。
class 指定类的全限定名。用于反射创建对象。默认下调用无参构造
init-method 指定类中的初始化方法名称
destory-method 指定类中的销毁方法,比如DataSource的配置中,一般需要配置destory-method="close"
scope

指定对象的作用范围:

  (1)singleton:默认值,单例,生命周期:

      a、创建:当应用加载,创建容器时,对象就被创建

      b、存活:只要容器存在,一直存货

      c、死亡:当应用卸载,容器销毁时,对象就被销毁

  (2)prototype:多例,每次访问对象时,都会重新创建对象实例。生命周期如下:

      a、创建:访问对象时

      b、存活:只要对象还在使用

      c、死亡:当对象长时间不使用,被垃圾回收器回收

  (3)request:将Spring创建的Bean对象存入到request中

  (4)session:将Spring创建的Bean对象存入Session中

  (5)global session:全局Session

    (2)Bean标签的实例化方式

    a、使用默认无参构造函数(推荐)

    在默认情况下,他会根据默认的无参构造函数来创建对象,如果不存在无参对象,则创建失败。

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

    <bean id="userService" class="com.lcl.galaxy.spring.service.impl.UserServiceImpl"/>
</beans>

    b、使用静态工厂创建

    使用工厂类的静态方法创建一个对象,并将对象放入Spring容器中

package com.lcl.galaxy.spring.factory;

import com.lcl.galaxy.spring.service.UserService;
import com.lcl.galaxy.spring.service.impl.UserServiceImpl;

public class StaticFactory {
    public static UserService createUserService(){
        return new UserServiceImpl();
    }
}
    <bean id="userService2" class="com.lcl.galaxy.spring.factory.StaticFactory" factory-method="createUserService"/>

    c、实例工厂(了解)

    先将工厂类放入Spring容器中,然后再引用工厂Bean

package com.lcl.galaxy.spring.factory;

import com.lcl.galaxy.spring.service.UserService;
import com.lcl.galaxy.spring.service.impl.UserServiceImpl;

public class InstanceFactory {
    public UserService createUserService(){
        return new UserServiceImpl();
    }
}
    <bean id="instanceFactory" class="com.lcl.galaxy.spring.factory.InstanceFactory"/>
    <bean id="userService3" factory-bean="instanceFactory" factory-method="createUserService"/>

  2、DI配置

    DI是指Bean中的属性,依赖(属性)分为简单类型(String和8种基本类型)、对象类型、集合类型;

    依赖注入是Spring IoC的具体实现;

    (1)依赖注入

    为什么要依赖注入:因为Bean对象的创建我们都交给了Spring创建,那么Bean对象种的指,也肯定是需要交给Spring来赋值的。

    依赖注入的方式有两种:构造函数注入和set方法注入

    a、构造函数注入

    构造函数注入就是使用类的构造函数,给成员变量赋值,赋值是Spring直接进行的赋值。

package com.lcl.galaxy.spring.service.impl;

import com.lcl.galaxy.spring.domain.UserDo;
import com.lcl.galaxy.spring.service.UserService;

public class UserServiceImpl implements UserService {

    private String id;
    private String name;

    @Override
    public UserDo getUserById() {
        return UserDo.builder().id(this.id).name(this.name).build();
    }

    public UserServiceImpl(){
        this.id = "initId";
        this.name = "initName";
    }

    public UserServiceImpl(String id, String name){
        this.id = id;
        this.name = name;
    }

}
    <bean id="userService4" class="com.lcl.galaxy.spring.service.impl.UserServiceImpl">
        <constructor-arg name="id" value="1"/>
        <constructor-arg name="name" value="lcl"/>
    </bean>

    从上面可以看到,给Service传值,类中必须要有构造函数,同时在bean标签中还需要设置constructor-arg标签,在constructor-arg标签中,有以下几个属性:

        index:指定参数在构造函数中的索引位置

        name:指定参数在构造函数中的名称

        value:赋值操作,可以对简单类型赋值(简单类型:8中基本类型+String)

        ref:赋值操作,可以配置在spring中已经配置的bean

    b、set方法注入

      set方法注入有手动的装配方式(Xml方式)和自动装配方式(注解方式:会用到@Autowired、@Resource、@Inject这些注解)

      手动的装配方式,需要设置bean标签的property标签,同时需要在bean对象中有setter方法。

    <bean id="userService5" class="com.lcl.galaxy.spring.service.impl.UserServiceImpl">
        <property name="id" value="1"/>
        <property name="name" value="lcl"/>
    </bean>

    (2)依赖注入不同类型

         a、简单类型

    <bean id="userService4" class="com.lcl.galaxy.spring.service.impl.UserServiceImpl">
        <constructor-arg name="id" value="1"/>
        <constructor-arg name="name" value="lcl"/>
    </bean>

      b、引用类型

    <bean id="userDao" class="com.lcl.galaxy.spring.dao.UserDao"/>
    <bean id="userService6" class="com.lcl.galaxy.spring.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>

      c、集合类型-List

    <bean id="collectionDto" class="com.lcl.galaxy.spring.dto.CollectionDto">
        <property name="nameList">
            <list>
                <value>lcl</value>
                <value>qmm</value>
            </list>
        </property>
    </bean>

      d、集合类型-Set

    <bean id="collectionDto2" class="com.lcl.galaxy.spring.dto.CollectionDto">
        <property name="nameList">
            <set>
                <value>lcl</value>
                <value>qmm</value>
            </set>
        </property>
    </bean>

      e、集合类型-Map

    <bean id="collectionDto3" class="com.lcl.galaxy.spring.dto.CollectionDto">
        <property name="nameMap">
            <map>
                <entry key="lcl" value="18"/>
                <entry key="qmm" value="15"/>
            </map>
        </property>
    </bean>

      f、集合类型-properties

    <bean id="collectionDto4" class="com.lcl.galaxy.spring.dto.CollectionDto">
        <property name="properties">
            <props>
                <prop key="lcl">21</prop>
                <prop key="qmm">18</prop>
            </props>
        </property>
    </bean>

(二)基于注解和XML的混合使用

    这里使用注解和XML混合使用,主要是指的在XML文件中设置自动扫描,而在具体的bean中使用@Service等注解以便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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.2.xsd">

    <context:component-scan base-package="com.lcl.galaxy.spring"/>
</beans>
package com.lcl.galaxy.spring.service.impl;

import com.lcl.galaxy.spring.service.UserService2;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class UserServiceImpl2 implements UserService2 {

    public UserServiceImpl2(){
      log.info("无参构造被调用");
    }
}

    那么具体有哪些注解可以被扫描到,清单如下所示:

分类 注解 作用  属性

Ioc注解

相当于:

 <bean id="" class=""/>
   
 @Component  让spring来管理资源,相当于对xml中配置的一个bean 指定bean的id,如果不指定,默认bean的id是当前类名,首字母小写 
 @Controller 对于@Component的衍生注解,一般用于表现层注解   
 @Service 同上,主要用于业务层注解   
 @Repository 同上,主要用于持久层注解   

 DI注解

依赖注入

相当于:

<property name="" value=""/>
<property name="" ref=""/>
  
 @Autowired

 1、默认按照类型装配

2、是由org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor实现的

3、是spring自带的注解

4、@Autowired注解默认情况下要求依赖对象必须存在,如果允许不存在,需要设置它的required属性为false

5、如果我们想按照名称装配(byName),可以结合@Qualifier注解进行使用

 
 @Qualifier

1、在自动按照类型注入的同时,在按照bean的id注入

2、它在给字段注入时不能独立使用,必须和@Autowired一起使用

3、给方法参数注入时,可以单独使用 

 
 @Resource

1、默认按照名称装配,可以通过@Resource的name属性指定名称,如果没有指定name属性,当注解写到字段上时,默认取字段名并按照字段名查找,当找不到与字段名匹配的bean时才按照类型进行装配

2、如果name属性指定,则一定会按照名称进行装配

 
@Inject

1、根据类型自动装配,如果需要按照名称进行装配,则需要配合@Name注解

2、@Inject可以作用在变量、setter方法、构造函数上

 
@Value 给简单类型(8种基本类型+String)来注入值  

@Autowired、@Resoure、@Inject区别

1、@Autowired是spring自带的,@Inject是JSR330规范实现的,@Resource是JSR250规范实现的,需要导入不同的包

2、@Autowired和@Inject用发基本一致,但是@Autowired有一个request属性

3、@Autowired和@Inject默认按照类型匹配,@Resource默认按照名称匹配

4、@Autowired要是想按照名称匹配,需要@Qualifier注解配合;@Inject要是想按照名称匹配,需要@Name注解配合

 

@Scope

作用域注解:

改变作用域,相当于下面的配置代码,value内容有:singletion、prototype、request、session、globalsession

<bean id="" class="" scope=""/>
 

 @PostConstrust

@PreDestory

 生命周期注解:

相当于以下代码

   <bean id="" class="" init-method="" destroy-method=""/>
 

    xml配置和注解配置的对比

  Xml配置 注解配置
Bean定义 使用<bean id="" class=""/>来配置 使用@Component和其衍生注解@Controller、@Service、@Repository
Bean名称 通过id或name来设置 通过注解内加名称使用,例如:@Service("UserService")
Bean注入

使用<property>标签注入

使用@Autowired或@Inject或@Inject来注入

如果使用@Autowired注入,可以配合@Qualifier按名称注入等

生命过程和作用域 在bean标签中使用init-method、destory-method、scope来设置 使用@PostConstruct、@PreDestory、@Scope来设置
适用场景

Bean来自第三方

Bean是由我们自己写的

 

(三)基于纯注解的使用

    1、@Configuration

      @Configuration是用来配置Bean的,对应的就是原来spring的xml配置文件,同样,在主函数中也是需要加载该配置类的

package com.lcl.galaxy.spring.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class SpringConfiguration {

    public SpringConfiguration(){
        log.info("spring容器启动");
    }
}
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);

    对应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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.2.xsd">

</beans>
ApplicationContext factory2=new ClassPathXmlApplicationContext("classpath:spring-config2.xml");

   2、@Bean

    Bean标签用来注册bean

    @Bean
    @Scope("singletion")
    public DefaultSqlSessionFactory getSqlSession(){
        return new DefaultSqlSessionFactory(new org.apache.ibatis.session.Configuration());
    }

    对应XML编写

<bean id="userDao" class="com.lcl.galaxy.spring.dao.UserDao"/>

  3、@CompenentScan

    指定要扫描的包

@Configuration
@Slf4j
@ComponentScan(basePackages = "com.lcl.galaxy.spring")
public class SpringConfiguration {

    public SpringConfiguration(){
        log.info("spring容器启动");
    }

}

    对应xml代码

    <context:component-scan base-package="com.lcl.galaxy.spring"/>

  4、@PropertySource

  加载properties文件的内容

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://******
jdbc.username=******
jdbc.password=******
package com.lcl.galaxy.spring.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

@Configuration
@PropertySource("classpath:db.properties")
@Slf4j
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(name = "dataSource")
    public DataSource createDataSource() throws Exception{
        DataSource dataSource = new ComboPooledDataSource();
        ((ComboPooledDataSource) dataSource).setDriverClass(driver);
        ((ComboPooledDataSource) dataSource).setJdbcUrl(url);
        ((ComboPooledDataSource) dataSource).setUser(username);
        ((ComboPooledDataSource) dataSource).setPassword(password);
        log.info("dabasource=============【{}】", dataSource);
        return dataSource;
    }
}

  对应XML代码

    <context:property-placeholder location="classpath:db.properties"/>
    <bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

  5、@Import

    导入其他Spring配置类

@Configuration
@Slf4j
@ComponentScan(basePackages = "com.lcl.galaxy.spring")
@Import(JdbcConfig.class)
public class SpringConfiguration {
}

    Xml代码

<import resource="spring-config1.xml"/>

 

二、核心高级

(一)AOP

  1、相关术语

术语 说明  
JoinPoint 连接点:指的是那些被拦截的点。在spring中,连接点指的是方法,因为spring只支持方法类型的连接点  
PointCut 切入点:指我们要对哪些JointPoint进行拦截  
Advice 通知/增强:指拦截到JointPoint后要做的事。通知分为前置通知、后置通知、异常通知、最终通知和环绕通知  
Introduction 引介:是一种特殊的通知,在不修改代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field  
Target

目标对象:代理的目标对象

 
Weaving 织入:是吧增强应用到目标对象来创建新的代理对象的过程  
Proxy 代理:一个类被AOP织入增强后,就产生一个代理类  
Aspect 切面:是切点和通知的结合  
Advisor 通知器/顾问:和Aspect相似  

  2、织入过程

    对于AOP的织入过程,可以分为动态织入和静态织入。

    其中,动态织入实在运行时动态的将要增强的代码织入到目标类中,这种一般基于动态代理来完成,例如JDK的动态代理(Proxy,通过反射实现)或者CGLIB动态代理(通过继承实现),其中,Spring Aop采用的就是基于运行时增强的动态代理技术

  (1)静态代理:AspectJ

    静态代理是在程序进行编译的时候进行的织入,这就需要一种特殊的程序编译器,例如acj编译器,他主要是先将增强的源代码编译成字节码文件(class文件),然后在编译目标对象时,将增强的字节码文件一起织入,生成最终增强后的字节码文件。

  (2)动态代理

    spring AOP是通过动态代理技术实现的,而动态代理又是通过反射实现的。动态代理有两种实现方式:基于接口的JDK动态代理和基于接口的CGLIB动态代理。

    无论是基于什么的动态代理,都是在运行期,针对目标对象胜场Proxy代理对象,然后在代理对象中织入增强处理。

   a、JDK动态代理

    public static UserService getProxyByJdk(final UserService userService){
        //使用proxy类生成代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                new InvocationHandler() {
                    //代理对象方法一执行,invoke方法就会执行一次
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        if("save".equals(method.getName())){
                            log.info("=============记录日志==============");
                        }
                        //让service类的save方法正常的执行下去
                        return method.invoke(userService, args);
                    }
                });
        //返回代理对象
        return proxy;
    }

    b、CGLIB

    public static UserService getProxyByCglib(){
        //创建CGLIB核心类
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(UserServiceImpl.class);
        //设置回调函数
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                if("save".equals(method.getName())){
                    log.info("=============记录日志==============");
                    log.info("=============开启事务==============");
                }
                log.info("=============提交事务志==============");
                return methodProxy.invokeSuper(o, objects)
            }
        });
        //生成代理对象
        UserService proxy = (UserService) enhancer.create();
        return proxy;
    }

(二)基于AspectJ的AOP使用

     基于AspectJ的AOP使用,其实就是Spring对于AspectJ的整合,不过Spring已经将AspectJ整合到自身的框架种了,并且底层织入仍然采用的是动态织入的方式。

    1、准备代码

  (1)添加依赖

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>aopalliance</groupId>
            <artifactId>aopalliance</artifactId>
            <version>1.0</version>
        </dependency>

    (2)编写目标类和目标方法

package com.lcl.galaxy.spring.aop;

public interface UserService {
    public void insert();

    public void insert(String id);

    public void insert(String id, String name);

    public void userInsert();
}
package com.lcl.galaxy.spring.aop.service;

import com.lcl.galaxy.spring.aop.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Override
    public void insert() {
        log.info("=========================insert======================");
    }

    @Override
    public void insert(String id) {
        log.info("=========================insert111======================");
    }

    @Override
    public void insert(String id, String name) {
        log.info("=========================insert2222======================");
    }

    @Override
    public void userInsert() {
        log.info("=========================userInsert======================");
    }
}

    2、Xml实现

    (1)编写通知

package com.lcl.galaxy.spring.aop.advice;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyAdvice {
    public void log(){
      log.info("===================打印日志===================");
    }
}

    (2)配置通知、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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.2.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.lcl.galaxy.spring.aop"/>
    <bean name="myAdvice" class="com.lcl.galaxy.spring.aop.advice.MyAdvice"/>
    <aop:config>
        <aop:aspect ref="myAdvice">
            <aop:before method="log" pointcut="execution(public void com.lcl.galaxy.spring.aop.service.UserServiceImpl.insert())"/>
        </aop:aspect>
    </aop:config>

</beans>

    切入点表达式:

      execution([修饰符] 返回值类型 包名.类名.方法名(参数))   

    表达式说明:

参数 说明
execution 必须
修饰符 可省略
返回值类型 必须
包名

1、多级包之间使用.分割

2、包可以使用*代替

3、多级包名可以使用多个*代替

4、如果想省略中间的包,可以使用.. 

类名

1、可以使用*代替

2、也可以使用*ServiceImpl

方法名

1、可以使用*代替

2、也可以写成save* 

参数

1、参数使用*代替

2、如果有多个参数,可以使用..代替

    对于不同的写法如下代码所示:

<aop:config>
        <aop:aspect ref="myAdvice">
            <!-- 详细写法 -->
            <!--   <aop:before method="log" pointcut="execution(public void com.lcl.galaxy.spring.aop.service.UserServiceImpl.insert())"/>  -->
            <!-- 省略execution写法-->
            <!-- <aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.service.UserServiceImpl.insert())"/> -->
            <!-- 一层包名使用*写法 -->
            <!--<aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.*.UserServiceImpl.insert())"/>-->
            <!-- 多层包名省略写法-->
            <!-- <aop:before method="log" pointcut="execution(void com.lcl.galaxy..insert())"/>-->
            <!-- 类名使用*写法-->
            <!-- <aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.service.*Impl.insert())"/>-->
            <!-- 方法名使用*写法-->
            <!-- <aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.service.*Impl.insert*())"/>-->
            <!-- 方法名使用*写法-->
            <!-- <aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.service.*Impl.insert*())"/>-->
            <!-- 方法名使用*写法-->
            <aop:before method="log" pointcut="execution(void com.lcl.galaxy.spring.aop.service.UserServiceImpl.insert(..))"/>
        </aop:aspect>
    </aop:config>

    通知类型

通知类型 执行时机 配置文件 应用场景
前置通知 目标对象方法之前执行通知
<aop:before method="" pointcut=""/> 
方法开始时进行校验
后置通知 目标对象方法之后执行,有异常不执行
<aop:after-running method="" pointcut=""/>
可以修改方法的返回值
最终通知 目标对象方法之后执行,有异常也执行
<aop:after method="" pointcut=""/>
资源释放等
环绕通知 目标对象方法之前和之后都会执行
<aop:around method="" pointcut=""/>
事务,统计代码执行时间等
异常通知 目标对象方法执行异常会执行
<aop:after-throwing method="" pointcut=""/>
包装异常

    3、注解 + xml 实现

    (1)通知代码

package com.lcl.galaxy.spring.aop.advice;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component("myAdviceAuto")
public class MyAdviceAuto {

    @Before(value = "execution(void *..*Impl.insert(..))")
    public void log(){
      log.info("===================打印日志===================");
    }
}

    (2)配置文件

    目标类仍然使用上面的目标类,但是需要在配置文件添加注解扫描和开启AOP自动代理

    <context:component-scan base-package="com.lcl.galaxy.spring.aop"/>
    <aop:aspectj-autoproxy/>

    (3)对于环绕通知的写法

    @Around(value = "execution(* *..*Impl.*(..))")
    public String trans(ProceedingJoinPoint proceedingJoinPoint){
        Object[] args = proceedingJoinPoint.getArgs();
        String proceed = null;
        try {
            proceed = "返回结果" + (String) proceedingJoinPoint.proceed(args) ;
        } catch (Throwable throwable) {
            return "================异常";
        }
        return proceed;
    }

    (4)定义通用切入点

    通用切入点指的是,再通知内设置value时,不再设置具体的拦截方案,会调用一个通用的拦截方案。

    在通用

    @Before(value = "MyAdviceAuto.fn()" )
    public void logfn(){
        log.info("=========================logfn=============================");
    }

    @Before(value = "MyAdviceAuto.fn()" )
    public void killfn(){
        log.info("=========================killfn=============================");
    }

    @Pointcut(value = "execution(* *..*.*(..))")
    public void fn(){
        log.info("===================fn()====================");
    }

    4、纯注解方式

    纯注解和注解+xml的区别仅在于需要使用注解替换在xml中配置的,直接在配置类上加上@EnableAspectJAutoProxy注解即可,其余的都一致。

    那么 注解+xml 的方式在xml里面主要做了两件事,注解扫描和开启AOP自动代理

package com.lcl.galaxy.spring.demo.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.springframework.context.annotation.*;

@Configuration
@Slf4j
@ComponentScan(basePackages = "com.lcl.galaxy.spring")
@Import(JdbcConfig.class)
@EnableAspectJAutoProxy
public class SpringConfiguration {

    public SpringConfiguration(){
        log.info("spring容器启动");
    }

    @Bean
    @Scope("singletion")
    public DefaultSqlSessionFactory getSqlSession(){
        return new DefaultSqlSessionFactory(new org.apache.ibatis.session.Configuration());
    }

}

三、组件支撑

(一)整合Junit

    在测试类中,每一个测试类都要有加载xml文件的代码

@Slf4j
public class LclSpringTest {

    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-config4.xml");
        UserService userService = (UserService) context.getBean("userServiceImpl");
        String name = userService.around("lcl");
        log.info("=============={}", name);
    }
}

    可以看到上面的测试代码,在测试方法中需要加载spring配置文件,同时需要获取对应的Bean,但是如果有很多测试方法,那么每个方法都需要去写这两行代码。

    好的一点是,Junit提供了RunWith注解用来让我们替换他的执行器,同时,Spring也提供了一个运行器,可以读取配置文件来创建容器,我们只需要告诉它配置文件的地址就OK。

    那么主要就需要调整三个点:

      (1)使用Junit的@RunWith注解,向注解内传入Spring的Junit执行器

      (2)使用@ContextConfiguration注解加载配置文件或配置类

      (3)使用@Autowired注解直接获取Bean

package com.lcl.galaxy.mybatis;

import com.lcl.galaxy.spring.aop.UserService;
import com.lcl.galaxy.spring.demo.config.SpringConfiguration;
import lombok.extern.slf4j.Slf4j;
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.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(locations = "classpath:spring-config4.xml")
@ContextConfiguration(classes = SpringConfiguration.class)
public class LclSpringTest2 {

    @Autowired
    private UserService userService;

    @Test
    public void test(){
        String name = userService.around("lcl");
        log.info("=============={}", name);
    }
}

(二)事务支持

  1、事务相关特性介绍

  (1)事务的特性(ACID)

事务缩写 名称 说明
A 原子性:Atomicty 事务是一个原子操作,只会成功或失败,不存在中间状态
C 一致性:Consistency 在一个事务内,要么全部成功,要么全部失败
I 隔离性:Isolation 各个事务相互隔离,一个事务没有提交之前,不会被其他事务看到
D 持久性:Durability 指一个事务一旦被提交,就是永久性的。

  (2)事务的并发问题

问题 描述
脏读 一个事务读取到了另一个事务未提交的数据
不可重复读 一个事务因读取到了另一个事务已提交的数据,导致对同一条记录读取两次以上的结果不一致,主要针对update
幻读 一个事务因读取到了另一个事务已提交的数据,导致对同一条记录读取两次以上的结果不一致,主要针对delete和insert

  (3)事务隔离级别

    四种隔离级别

级别 描述
读未提交:Read uncommitted 最低级别,任何情况都无法保证
读已提交:Read committed 可以避免脏读
可重复读:Repeatable Read 可以避免脏读、不可重复读
串行化:Serializable 可以避免脏读、不可重复读、幻读

    大多数数据库的默认隔离级别是ReadCommitted,例如Oracle、DB2等

    MySql的默认隔离级别是可重复读(Repeatable Read)

    这里有一点需要注意:隔离级别越高,越能保证数据的完整性和一致性,但是对于并发性能影响也越大

  (4)Spring事务管理接口

    Spring并不是直接操作事务,而是提供事务管理接口PlatformTransactionManager,通过这个接口,Spring为各个平台(JDBC、Hibernate等)都提供了对应的事务管理器。

    这里有几个接口需要介绍一下

接口名称 说明 实现类 常用方法
PlatformTransactionManager接口 平台事务管理器,是真正管理事务的类,该接口有具体的实现类,根据不同的持久层框架,需要选择不同的实现类。

JDBC\Mybatis:DataSourceTransactionManager

Hibernate:HibernateTransactionManager

获取事务状态方法: TransactionStatus getTransaction(TransactionDefinition definition)

提交事务方法:void commit(TransactionStatus status)

回滚事务方法:void rollback(TransactionStatus status)

TransactionDefinition接口 事务定义信息,可以定义事务隔离级别、事务传播行为、超时时间、是否只读等信息    
TransactionStatus接口 事务的状态:是否是新事务、是否已提交、是否有保存点、是否回滚    

    上述几个接口的关系:平台事务管理器(PlatformTransactionManager)真正管理事务对象,根据事务定义信息(TransactionDefination)进行事务管理,在管理事务中产生一些状态,将状态等信息记录在事务状态(TransactionStatus)中。

    对于TransactionDefinition中对于事务隔离级别和传播行为有以下几种定义

    a、事务隔离级别常量

隔离级别常量 隔离级别描述
ISOLATION_DEFAULT 采用数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 读未提交
ISOLATION_READ_COMMITTED 读已提交
ISOLATION_REPEATABLE_READ 可重复读
ISOLATION_SERIALIZABLE 串行化

    b、事务传播行为(默认值为PROPAGATION_REQUIRED)

传播行为 描述
PROPAGATION_REQUIRED A中有事务,使用A中的事务;A中没有事务,B开启一个新的事务
PROPAGATION_SUPPORTS A中有事务,使用A中的事务;A中没有事务,B也不使用事务
PROPAGATION_MANDATORY A中有事务,使用A中的事务;A中没有事务,抛出异常
PROPAGATION_REQUIRED_NEW A中有事务,将A的事务挂起,B创建一个新的事务
PROPAGATION_NOT_SUPPORTED A中有事务,将A中的事务挂起
PROPAGATION_NEVER A中有事务,抛出异常
PROPAGATION_NESTED 嵌套事务,当A执行后,就会在这个位置设置一个保存点,如果B没有问题,执行通过。如果B出现异常,根据需求回滚(回滚到保存点或者最初始状态)

  2、Spring框架事务管理的分类

  主要分为编程式事务管理和声明式事务管理,且声明式事务管理可以使用xml编写方式、xml+注解编写方式和纯注解使用方式。

  (1)编程式事务管理

    编程式事务管理主要是使用了TransactionTemplate模板类,通过该模板类进行事务处理。

    首先编写业务逻辑层代码,在业务逻辑层代码中,调用TransactionTemplate的execute方法,在该方法内的所有操作,都是一个事务。

package com.lcl.galaxy.spring.transaction.service.impl;

import com.lcl.galaxy.spring.transaction.dao.OrderInfoDao;
import com.lcl.galaxy.spring.transaction.dao.UserDao;
import com.lcl.galaxy.spring.transaction.domain.OrderInfoDo;
import com.lcl.galaxy.spring.transaction.service.UserService;
import com.lcl.galaxy.spring.transaction.domain.UserDo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

@Slf4j
@Data
public class UserServiceImpl implements UserService {


    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private UserDao userDao;
    @Autowired
    private OrderInfoDao orderInfoDao;


    @Override
    public void transactionTest(UserDo userDo, OrderInfoDo orderInfoDo) {
        log.info("事务处理");
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                log.info("=======================");
                userDao.insert(userDo);
                orderInfoDao.insert(orderInfoDo);
            }
        });
        log.info("事务结束");
    }
}

  然后是对TransactionTemplate的配置

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.2.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="****"></property>
        <property name="user" value="******"></property>
        <property name="password" value="5H5eLQsp6yO4"></property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <bean id="userDao" class="com.lcl.galaxy.spring.transaction.dao.impl.UserDaoImpl"/>
    <bean id="orderInfoDao" class="com.lcl.galaxy.spring.transaction.dao.impl.OrderInfoDaoImpl"/>

    <bean id="userService" class="com.lcl.galaxy.spring.transaction.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
        <property name="orderInfoDao" ref="orderInfoDao"/>
        <property name="transactionTemplate" ref="transactionTemplate"/>
    </bean>

</beans>

    (2)声明式事务--xml实现方式

    使用xml的方式实现声明式事务,主要需要做的就是使用AOP的配置,将事务增强到切面方法上。

    <aop:config>
        <aop:advisor advice-ref="txadvice" pointcut="execution(* *..*.save(..))"/>
    </aop:config>

    对于事务增强,又需要事务管理器来处理

    <tx:advice id="txadvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*"/>
            <tx:method name="find*" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    事务管理器又需要DataSource数据源

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="*****"></property>
        <property name="user" value="******"></property>
        <property name="password" value="******"></property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    配置Service类

    <bean id="userService2" class="com.lcl.galaxy.spring.transaction.service.impl.UserService2Impl"/>

    最后测试代码

package com.lcl.galaxy.spring;

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-transaction-xml-config.xml")
public class LclSpringTransactionTest {


    @Autowired
    private UserService2 userService2;

    @Test
    public void test2(){
        UserDo userDo = UserDo.builder().address("beijing").username("lcl111").sex("").build();
        OrderInfoDo orderInfoDo = OrderInfoDo.builder().orderId("1111").name("lcl").payMoney(new BigDecimal(String.valueOf("1.01"))).build();
        log.info("测试启动");
        userService2.saveInfo(userDo, orderInfoDo);
        log.info("测试完成");
    }
}

    (3)声明式事务--注解 + xml实现方式

    使用注解+xml实现声明式事务和纯xml实现声明式事务的主要差别就是我们不需要在xml里面配置事务增强和AOP配置,以及Bean的配置,但是仍然要配置dataSource和TransactionManager。同时需要配置 tx:annotation-driven 标签来开启事务注解

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="******"></property>
        <property name="user" value="******"></property>
        <property name="password" value="******"></property>
    </bean>


    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <context:component-scan base-package="com.lcl.galaxy.spring.transaction"/>

    在使用事务时,使用@Transaction注解来处理,该注解可以使用在类上也可以使用在方法上

package com.lcl.galaxy.spring.transaction.service.impl;


@Slf4j
@Data
@Service
public class UserService3Impl implements UserService3 {

    @Transactional
    @Override
    public void saveInfo(UserDo userDo, OrderInfoDo orderInfoDo) {
        log.info("==================");
    }
}

    (4)声明式事务--纯注解实现方式

    纯注解的实现方式,在xml中只需要配置DataSource,然后在主类上使用@EnableTransactionManagement注解即可。

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-transaction-xml-auto2-config.xml")
@EnableTransactionManagement
public class LclSpringAutoTransactionTest2 {

    @Autowired
    private UserService3 userService3;

    @Test
    public void test(){
        UserDo userDo = UserDo.builder().address("beijing").username("lcl111").sex("").build();
        OrderInfoDo orderInfoDo = OrderInfoDo.builder().orderId("1111").name("lcl").payMoney(new BigDecimal(String.valueOf("1.01"))).build();
        log.info("测试启动");
        userService3.saveInfo(userDo, orderInfoDo);
        log.info("测试完成");
    }
}

 

posted @ 2020-12-11 01:53  李聪龙  阅读(140)  评论(0编辑  收藏  举报