Spring的简单使用

Spring

学习Spring的笔记

概述

1.Spring是轻量级的开源的JavaEE框架;    
2.Spring可以解决企业应用开发的复杂性;    
3.Spring有两个核心部分:IOC和AOP:        
    1)IOC:控制反转,把创建对象的过程交给Spring进行管理;        
    2)AOP:面向切面,不修改源代码进行功能增强;   
4.Spring特点:        
    1)方便解耦,简化开发;        
    2)AOP编程支持;        
    3)方便程序测试;        
    4)方便和其他框架进行整合;        
    5)方便进行事务操作;        
    6)降低API开发难度;

Spring核心模块

1.beans:IOC    
2.Core::IOC    
3.Context:上下文    
4.Expression:表达式
<!--Spring核心依赖-->
<dependency>             <groupId>org.springframework</groupId>         <artifactId>spring-beans</artifactId>    <version>5.2.6.RELEASE</version></dependency><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-context</artifactId>    <version>5.2.6.RELEASE</version></dependency><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-core</artifactId>    <version>5.2.6.RELEASE</version></dependency><dependency>    <groupId>org.springframework</groupId>    <artifactId>spring-expression</artifactId>    <version>5.2.6.RELEASE</version></dependency><!--日志包-->
<dependency>    
<groupId>commons-logging</groupId>    <artifactId>commons-logging</artifactId>    <version>1.1.1</version></dependency><dependency>    
<groupId>junit</groupId>    <artifactId>junit</artifactId>    <version>RELEASE</version>
<scope>test</scope>
</dependency>

IOC

什么是IOC

(1)控制反转,把对象创建和对象之间的调用过程,交给Spring进行管理;
(2)使用IOC的目的,为了耦合度降低;

IOC底层原理

(1)XML解析;
(2)工厂模式;
(3)反射;

IOC接口

(1)IOC思想基于IOC容器完成,IOC容器底层就是对象工厂;
(2)Spring提供IOC容器实现两种方式:(两个接口)
        1)BeanFactory:IOC容器基本实现,是Spring内部的使用接口,一般不提供给开发人员进行使用;
           *加载配置文件的时候不会创建对象,在获取或使用对象的时候才去创建对象;
        2)ApplicationContext:BeanFactory接口的子接口,提供了更多更强大的功能,一般是开发人员使用的;
           *加载配置文件时候就会把配置文件对象进行创建;
(3)ApplicationContext的主要实现类:
        1)FileSystemXmlApplicationContext:需要指定文件的盘符路径;
        2)ClassPathXmlApplicationContext:需要指定类路径;

IOC操作Bean管理

什么是Bean管理?

Bean管理->两个操作:
(1)Spring创建对象;
(2)Spring注入属性;
Bean管理操作->两种方式:

1.基于XML配置文件实现方式

基于XML方式创建对象

<!--配置User类的对象的创建-->
<bean id="user" class="per.xgt.pojo.User"></bean>

(1)在Spring配置文件中,使用bean标签,标签里面添加对应属性,就可以实现对象创建;
(2)在bean标签里面有很多属性:

  • id属性:唯一标识
    
  • class属性:创建对象所在类的全路径
    

(3)创建对象,默认使无参构造方法

基于XML方式注入属性
(1)DI:依赖注入,就是注入属性;

  • 第一种注入方式:使用set方法进行注入;
    
<!--set方法注入属性-->
<bean id="book" class="per.xgt.pojo.Book">    
<!--使用property完成属性注入        name:类里面属性名称        value:向属性注入的值    -->    
<property name="bname" value="龙抓手"></property>    
<property name="bauthor" value="张四疯"></property>
</bean>

可以简化为P名称空间注入,可以简化基于XML配置方式:

  1. 添加P名称空间
<?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"       
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
  1. 进行属性注入
  • 第二种注入方式:使用有参构造方法进行注入;
    
<!--有参构造注入属性-->
<bean id="orders" class="per.xgt.pojo.Orders">    
<!--name:属性名 value:属性值-->    
<!--<constructor-arg name="oname" value="订单1"></constructor-arg>-->    
<constructor-arg name="address" value="中国"></constructor-arg>    
<!--index:属性的索引位置 value属性值-->    
<constructor-arg index="0" value="订单1"></constructor-arg>
</bean>

3.IOC操作Bean管理(xml注入其他类型属性)

  • 字面量
  1. null值
    
<property name="address">    
<null></null>
</property>
  1. 属性值包含特殊符号
    
<!--转移字符-->
<!--<property name="address" value="&lt;&gt;南京&lt;&gt;"></property>-->
<!--把带特殊符号内容写到CDATA-->
<property name="address">    
<value><![CDATA[<<南京>>]]></value>
</property>
  • 外部Bean
    (1)创建两个类service和dao,在service调用dao里面的方法
<!--service和dao的对象创建-->
<bean id="userDao" 
class="per.xgt.dao.impl.UserDaoImpl"></bean><bean id="userService" 
class="per.xgt.service.UserService">    
<!--注入userDao对象    
name属性值:类里面的属性名    
ref属性值:创建userDao对象bean标签id值-->
<property name="userDao" ref="userDao"></property>
</bean>
  • 内部Bean
    (1)一对多关系:如部门和员工;
// 员工类
public class Emp {    
private String ename;    
private String gender;   
// 员工属于某一个部门    
private Dept dept;    
public void setDept(Dept dept) {        this.dept = dept;    }    
public void setEname(String ename) {        this.ename = ename;    }    
public void setGender(String gender) {        this.gender = gender;    }
}

//部门类
public class Dept {    
private String dname;    
public void setDname(String dname) {        this.dname = dname;    }
}

xml配置

<!--内部bean-->
<bean id="emp" class="per.xgt.pojo.Emp">    
<!--先设置普通属性-->    
<property name="ename" value="Tank"></property>    
<property name="gender" value=""></property>    
<!--对象属性-->    
<property name="dept">        
    <bean id="dept" class="per.xgt.pojo.Dept">
        <property name="dname" value="营销部">
        </property>        
    </bean>    
</property>
</bean>
  • 级联赋值
<!--级联赋值-->
<bean id="emp" class="per.xgt.pojo.Emp">    
<!--先设置普通属性-->    
<property name="ename" value="Tank"></property>    
<property name="gender" value=""></property>    
<!--级联赋值-->    
<property name="dept" ref="dept"></property>
</bean>
<bean id="dept" class="per.xgt.pojo.Dept">    
<property name="dname" value="财务部">
</property>
</bean>

另一种写法,需要相关属性的get方法

// 员工属于某一个部门
private Dept dept;public Dept getDept() {    return dept;}
<!--级联赋值-->
<bean id="emp" class="per.xgt.pojo.Emp">    
<!--先设置普通属性-->    
<property name="ename" value="Tank"></property>    
<property name="gender" value=""></property>    
<!--级联赋值-->    
<property name="dept" ref="dept"></property>    
<property name="dept.dname" value="安保部"></property>
</bean><bean id="dept" class="per.xgt.pojo.Dept">    
<property name="dname" value="财务部"></property>
</bean>
  • 注入数组集合,List集合,Map集合,Set集合
public class Stu {    // 数组类型属性    
private String[] courses;   
// List集合类型属性   
private List<String> list;    
// Map类型属性    
private Map<String,String> maps;    
// Set集合属性    
private Set<String> sets;    
public void setSets(Set<String> sets) {        this.sets = sets;    }    
public void setList(List<String> list) {        this.list = list;    }    
public void setMaps(Map<String, String> maps) {        this.maps = maps;    }    
public void setCourses(String[] courses) {        this.courses = courses;    }}
<!--集合类型属性注入-->
    <bean id="stu" class="per.xgt.pojo.Stu">
        <!--数组类型注入-->
        <property name="courses">
            <array>
                <value>C++</value>
                <value>数据结构</value>
            </array>
        </property>
        <!--List类型注入-->
        <property name="list">
            <list>
                <value>火力少年王</value>
                <value>四驱兄弟</value>
            </list>
        </property>
        <!--Map类型注入-->
        <property name="maps">
            <map>
                <entry key="C++" value="60"></entry>
                <entry key="数据结构" value="70"></entry>
            </map>
        </property>
        <!--Set属性注入-->
        <property name="sets">
            <set>
                <value>来玩啊</value>
                <value>就不来</value>
            </set>
        </property>
    </bean>

在集合里面设置对象类型值:

<!--集合类型属性注入-->
    <bean id="stu" class="per.xgt.pojo.Stu">
        <!--数组类型注入-->
        <property name="courses">
            <array>
                <value>C++</value>
                <value>数据结构</value>
            </array>
        </property>
        <!--List类型注入-->
        <property name="list">
            <list>
                <value>火力少年王</value>
                <value>四驱兄弟</value>
            </list>
        </property>
        <!--Map类型注入-->
        <property name="maps">
            <map>
                <entry key="C++" value="60"></entry>
                <entry key="数据结构" value="70"></entry>
            </map>
        </property>
        <!--Set属性注入-->
        <property name="sets">
            <set>
                <value>来玩啊</value>
                <value>就不来</value>
            </set>
        </property>
        <!--注入对象List集合-->
        <property name="courseList">
            <list>
                <ref bean="course1"></ref>
                <ref bean="course2"></ref>
            </list>
        </property>
    </bean>
    <!--创建多个course对象-->
    <bean id="course1" class="per.xgt.pojo.Course">
        <property name="cname" value="JAVA"></property>
    </bean>
    <bean id="course2" class="per.xgt.pojo.Course">
        <property name="cname" value="C++"></property>
    </bean>

把集合注入部分提取出来:先在spring配置文件中引入名称空间util,然后再注入

public class Books {

    private List<String> list;

    public void setList(List<String> list) {
        this.list = list;
    }

}
<!--集合类型属性注入-->
    <util:list id="boosList">
        <value>九阴白骨爪</value>
        <value>九阳神功</value>
        <value>降龙十八掌</value>
    </util:list>
    <!--提取List集合类型属性注入使用-->
    <bean id="books" class="per.xgt.pojo.Books">
        <property name="list" ref="boosList"></property>
    </bean>

工厂Bean

  • Spring有两种类型bean,一种普通bean,一种工厂bean(FactoryBean)
  • 普通bean:在配置文件中定义bean类型就是返回类型;
  • 工厂bean:在配置文件定义bean类型可以和返回类型不一样;
    第一步:创建类,让这个类作为工厂bean,实现接口FactoryBean
    第二步:实现接口里面的方法,在实现的方法中定义返回bean类型;
public class MyBean implements FactoryBean<Course> {
    // 定义返回bean
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setCname("aaa");
        return course;
    }
    public Class<?> getObjectType() {
        return null;
    }
    public boolean isSingleton() {
        return false;
    }
}

Bean的作用域
1.在Spring里面,默认情况下,bean是单实例对象;
2.在Spring里面设置创建bean实例是单实例还是多实例;
在bean标签里的scope属性:

<bean id="user" class="per.xgt.pojo.User" scope="singleton"></bean>
    <bean id="user" class="per.xgt.pojo.User" scope="prototype"></bean>

singleton:单实例,加载Spring配置文件时候就会创建一个单实例对象;
prototype:多实例,不是在加载Spring配置文件的时候就创建对象,而是在调用getBean方法时候才会创建一个新的实例对象;
request:一次请求,每次创建对象会放在request域对象中;
session:一次会话,每次创建对象会放在session域对象中;

Bean生命周期
生命周期:从对象创建到对象销毁的过程

  • 通过构造器创建bean实例(无参构造方法)
  • 为bean的属性设置值和其他bean的引用(调用set方法)
  • 把bean的实例传递给bean后置处理器的方法postProcessBeforeInitialization
  • 调用bean的初始化的方法(需要进行配置初始化方法)
  • 把bean的实例传递给bean后置处理器的方法postProcessAfterInitialization
  • bean可以使用了(对象获取到了)
  • 当容器关闭时,调用bean的销毁方法(需要进行配置销毁的方法)
public class Orders {
    private String oname;
    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("set方法......属性设置");
    }
    public Orders() {
        System.out.println("无参构造......实例化");
    }
    // 创建执行的初始化方法
    public void initMethod(){
        System.out.println("初始化方法......");
    }
    // 创建执行的销毁方法
    public void destroy(){
        System.out.println("销毁方法......");
    }
}
// 实现接口BeanPostProcessor,创建后置处理器
public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化之前执行的......"+beanName);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化之后执行的......"+beanName);
        return bean;
    }
}
<bean id="orders" class="per.xgt.bean.Orders" init-method="initMethod" destroy-method="destroy">
        <property name="oname" value="电脑"></property>
    </bean>
    <!--配置后置处理器-->
    <bean id="myBeanPost" class="per.xgt.bean.MyBeanPost"></bean>
@Test
    public void testBean7(){
        //加载Spring的配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean8.xml");
        //获取配置创建的对象
        per.xgt.bean.Orders orders = context.getBean("orders", per.xgt.bean.Orders.class);
        System.out.println(orders);
        ((ClassPathXmlApplicationContext)context).close();
    }
无参构造......实例化
set方法......属性设置
在初始化之前执行的......orders
初始化方法......
在初始化之后执行的......orders
per.xgt.bean.Orders@553a3d88
销毁方法......

Bean管理XML方式(自动装配)
自动装配:根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入;

  • 根据属性名称自动注入
<!--实现自动装配
        bean标签属性autowire,配置自动装配
        常用属性值:
            byName:根据属性名称注入,注入值bean的id值和类属性名称一样
            byType:根据属性类型注入
    -->
    <bean id="empo" class="per.xgt.autowire.Empo" autowire="byName">
        <!--<property name="depat" ref="depat"></property>-->
    </bean>
    <bean id="depat" class="per.xgt.autowire.Depat"></bean>

问题:如果根据类型,那么相同类型的Bean不能配置多个,否则报错;

Bean管理XML方式(引入外部属性文件)

  • 德鲁伊连接池,模拟配置
<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>
  • 直接配置连接池
<!--直接配置连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
  • 引入外部属性文件配置连接池
    创建外部的properties文件,配置数据库连接信息
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root

把外部properties属性文件引入到Spring配置文件中

<!--引入外部properties属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
2.基于注解方式实现
  • Component
  • Service
  • Controller
  • repository
    上面的四个注解功能是一样的,都可以用来创建bean实例

基于注解方式实现对象创建
需要引入一个额外依赖:AOP

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.2.6.RELEASE</version>
 </dependency>

开启组件扫描

<!--开启组件扫描
        如果多个包,多个包之间用“,”隔开
    -->
    <context:component-scan base-package="per.xgt"></context:component-scan>

开启组件扫描的细节:

<!--开启组件扫描
        如果多个包,多个包之间用“,”隔开
        use-default-filters:false,表示现在不使用默认filter,使用自己配置filter
        context:include-filter:设置扫描哪些内容
        context:exclude-filter:设置不扫描哪些内容
    -->
    <context:component-scan base-package="per.xgt" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <context:component-scan base-package="per.xgt">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

创建类,在类上面添加创建对象注解,value值等于xml方式的id,可以省略不写,默认值为类名称首字母小写

// value值等于xml方式的id,可以省略不写,默认值为类名称首字母小写
@Service(value = "userService")
public class StudentServiceImpl {
    public void add(){
        System.out.println("service add......");
    }
}

基于注解方式实现属性注入

  • @Autowired:根据属性类型进行自动装配
    (1)创建Service和dao对象,在service和dao类添加创建对象注解
    (2)在service注入dao,service类添加dao类型属性,在属性上面使用注解@Autowired
@Repository(value = "studentDao")
public class StudentDaoImpl implements StudentDao {
    @Override
    public void add() {
        System.out.println("studentDao add......");
    }
}
// value值等于xml方式的id,可以省略不写,默认值为类名称首字母小写
@Service(value = "studentService")
public class StudentServiceImpl {

    @Autowired
    private StudentDao studentDao;

    public void add(){
        studentDao.add();
        System.out.println("service add......");
    }
}
  • @Qualifier:根据属性名称进行注入
    (1)@Qualifier需要和@Autowired一起使用
@Service(value = "studentService")
public class StudentServiceImpl {

    @Autowired
    @Qualifier("studentDao")
    private StudentDao studentDao;

    public void add(){
        studentDao.add();
        System.out.println("service add......");
    }
}
  • @Resource:可以根据类型,也可以根据名称注入
@Service(value = "studentService")
public class StudentServiceImpl {

//    @Autowired
//    @Qualifier("studentDao")
//    private StudentDao studentDao;

    @Resource(name = "studentDao")
    private StudentDao studentDao;

    public void add(){
        studentDao.add();
        System.out.println("service add......");
    }
}

**PS:**Resource属于包:javax.annotation.Resource,所以spring官方更推荐使用@Autowired和@Qualifier

  • @Value:注入普通类型属性
@Value("哈哈哈哈")
    private String name;

完全注解开发例子
(1)创建配置类,替代xml配置文件

@Configuration // 作为配置类,替代XML配置文件
@ComponentScan(basePackages = {"per.xgt"},
        useDefaultFilters = false,
        includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)
        }
)
public class SpringConfig {

}
// 包扫描两种情况
@Configuration // 作为配置类,替代XML配置文件
@ComponentScan(basePackages = {"per.xgt"},
        useDefaultFilters = true,
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)
        }
)
public class SpringConfig {

}

(2)编写测试类

@Test
    public void test1(){
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        StudentServiceImpl studentService = context.getBean("studentService", StudentServiceImpl.class);
        System.out.println(studentService);
        studentService.add();
    }

AOP

概述

(1)面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间耦合度降低,提高程序的可重用性,同时提高了开发的效率;
(2)通俗讲:不通过修改源代码方式,在主干功能里面添加新功能;

AOP底层原理

1.AOP底层使用动态代理
(1)有两种情况动态代理:

  • 有接口情况:使用JDK动态代理
    =》创建接口实现类代理对象
    =》通过代理对象,增强功能
  • 没有接口情况:使用CGLIB动态代理
    =》创建当前类的子类代理对象
    =》通过子类增强逻辑

JDK动态代理实现

调用newProxyInstance方法:
=》ClassLoader loader:类加载器;
=》Class<?>[] interfaces:增强方法所在类的所实现的接口,支持多个接口;
=》InvocationHandler h:实现这个接口,InvocationHander,创建代理对象,写增强的方法;

实现

  • 创建接口,定义方法
public interface UserDao {
    public int add(int a,int b);
    public String update(String id);
}
  • 创建接口实现类
public class UserDaoImpl implements UserDao{
    @Override
    public int add(int a, int b) {
        return a+b;
    }
    @Override
    public String update(String id) {
        return id;
    }
}
  • 使用Proxy类创建接口代理对象
public class JDKProxy {
    public static void main(String[] args) {
        Class[] interfaces = {UserDao.class};
        // 创建接口实现类的代理对象
//        Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
//            // 匿名内部类实现
//            @Override
//            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                return null;
//            }
//        });
        UserDao userDao = new UserDaoImpl();
        UserDao user = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        int add = user.add(5, 10);
        System.out.println(add);
    }
}

// 创建代理对象代码
class UserDaoProxy implements InvocationHandler{
    // 1.把创建的是谁的dialing对象,把谁传递过来;
    // 通过有参构造传递
    private Object obj;
    public UserDaoProxy(Object obj){
        this.obj = obj;
    }

    // 增强逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 方法之前
        System.out.println("方法之前执行......" + method.getName() + "......传递的参数:" + Arrays.toString(args));

        // 被增强的方法执行
        Object invoke = method.invoke(obj, args);

        // 方法之后
        System.out.println(obj);

        // 方法返回
        return invoke;
    }
}

相关术语

  • 连接点:能被增强的方法;
  • 切入点:实际被增强的方法;
  • 通知(增强):实际增强的逻辑部分;
    1.前置通知:
    2.后置通知:
    3.环绕通知:
    4.异常通知:
    5.最终通知:
  • 切面:把增强应用到切入点的过程;

Spring实现AOP

Spring框架一般都是基于AspectJ实现AOP操作
AspectJ:不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring一起使用,进行AOP操作;

基于AspectJ实现AOP操作

Spring的Aspects依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>

因为AspectJ独立于Spring所以还需要如下依赖

<!-- https://mvnrepository.com/artifact/net.sourceforge.cglib/com.springsource.net.sf.cglib -->
<dependency>
    <groupId>net.sourceforge.cglib</groupId>
    <artifactId>com.springsource.net.sf.cglib</artifactId>
    <version>2.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.6.8</version>
    <scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>

切入点表达式
作用:知道对哪个类里面的哪个方法进行增强;
语法结构:Execution( [权限修饰符] [返回类型] [类的全路径] 方法名称 )
例1:对per.xgt.dao.UserDao 类里面add方法增强
execution(* per.xgt.dao.UserDao.add(…))
例2:对per.xgt.dao.UserDao 类里面所有方法增强
execution(* per.xgt.dao.UserDao.(…))
例3:对per.xgt.dao 包里面所有类里面的所有方法增强
execution(
per.xgt.dao..(…)))

基于注解方式实现
  • 创建类,在类里面定义方法
public class User {
    public void add(){
        System.out.println("add......");
    }   
}
  • 创建增强类
    在增强类里面,创建方法,让不同方法代表不同通知类型
public class UserProxy {
    // 前置通知
    public void before(){
        System.out.println("before......");
    }
}
  • 进行通知的配置

在Spring配置文件中,开启注解扫描

<context:component-scan base-package="per.xgt.aopAnnotation"></context:component-scan>

使用注解创建User和UserProxy对象

@Component

在增强的类上添加注解@Aspect

@Component
@Aspect
public class UserProxy {
    // 前置通知
    public void before(){
        System.out.println("before......");
    }
}

在Spring配置文件中,开启生成代理对象

<!--开启Aspectj生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  • 配置不同类型的通知
    在增强类的里面作为通知的方法上面添加通知类型的注解,并且使用切入点表达式配置
@Component
@Aspect
public class UserProxy {

    @Before(value = "execution(* per.xgt.aopAnnotation.User.add(..))")
    // 前置通知
    public void before(){
        System.out.println("before......");
    }

    @After(value = "execution(* per.xgt.aopAnnotation.User.add(..))")
    public void after(){
        System.out.println("after......");
    }

    @AfterReturning(value = "execution(* per.xgt.aopAnnotation.User.add(..))")
    public void afterReturning(){
        System.out.println("afterReturning......");
    }

    @AfterThrowing(value = "execution(* per.xgt.aopAnnotation.User.add(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing......");
    }

    @Around(value = "execution(* per.xgt.aopAnnotation.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        System.out.println("aroundBefore......");

        // 被增强的方法执行
        proceedingJoinPoint.proceed();

        System.out.println("aroundAfter......");
    }

}

完全注解开发可以将注解里面内容替换成:

@Configuration
@ComponentScan(basePackages = {"per.xgt.aopAnnotation"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class MyConfig {
}
通知类型
  • @Before:前置通知

  • @After:最终通知:在方法执行之后执行,无论方法是否发生异常都会执行;

  • @AfterReturning:后置通知,返回通知:在方法返回结果之后执行,如果方法有异常就不会执行;

  • @AfterThrowing:异常通知:

  • @Around:环绕通知

没有异常执行结果:

aroundBefore......
before......
add......
aroundAfter......
after......
afterReturning......

有异常执行结果

aroundBefore......
before......
add......
after......
afterThrowing......

java.lang.ArithmeticException: / by zero

对相同切入点进行抽取

//相同切入点抽取
    @Pointcut(value = "execution(* per.xgt.aopAnnotation.User.add(..))")
    public void pointCut1(){}

    @Before(value = "pointCut1()")
    // 前置通知
    public void before(){
        System.out.println("before......");
    }

有多个增强类对同一个方法进行增强,设置优先级
在增强类的上面添加注解@Order(数字类型值),数字类型值越小优先级越高

@Component
@Aspect
@Order(1)
public class PersonProxy {
    @Before(value = "execution(* per.xgt.aopAnnotation.User.add(..))")
    // 前置通知
    public void before(){
        System.out.println("PersonProxy.before......");
    }
}
PersonProxy.before......
aroundBefore......
before......
add......
aroundAfter......
after......
afterReturning......
基于XML配置文件实现
  • 创建增强类与被增强类,创建方法
public class Book {
    public void buy(){
        System.out.println("buy......");
    }
}
public class BookProxy {
    public void before(){
        System.out.println("before......");
    }
}
  • 在Spring配置文件中创建两个类对象
<bean id="book" class="per.xgt.aopXml.Book"></bean>
    <bean id="bookProxy" class="per.xgt.aopXml.BookProxy"></bean>
  • 在Spring配置文件中配置切入点
<!--配置aop的增强-->
    <aop:config>
        <!--切入点-->
        <aop:pointcut id="bookPointCut" expression="execution(* per.xgt.aopXml.Book.buy(..))"/>
        <!--配置切面-->
        <aop:aspect ref="bookProxy">
            <aop:before method="before" pointcut-ref="bookPointCut"></aop:before>
        </aop:aspect>
    </aop:config>

JDBCTemplate

Spring框架对JDBC进行封装,使用JDBCTemplate方便实现对数据库操作;

相关依赖

<!--数据库相关-->
        <!--Spring对JDBC封装-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--Spring事务-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--spring-ORM框架整个相关-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--Mysql连接-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.29</version>
        </dependency>
        <!--德鲁伊连接池-->
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>

配置数据库连接池

因为MYSQL8.0后需要驱动包版本更新,所以:
1.com.mysql.jdbc.Driver 更换为 com.mysql.cj.jdbc.Driver;
2.不需要建立 SSL 连接的,需要显式关闭–》useSSL=false;
3.最后还需要设置 CST。也就是设置时区–》serverTimezone=UTC;

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://111.1.111.11:3306/test?useSSL=false&amp;serverTimezone=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

JDBCTemplate对象注入DataSource

<!--JDBCTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

JDBCTemplate的简单使用

  • 创建service类,创建dao类,在dao注入jdbcTemplate对象
@Service
public class BookService {
    @Autowired
    private BookDao bookDao;
}

public interface BookDao {
}

@Repository
public class BookDaoImpl implements BookDao {
    // 注入JDBCTemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

开启注解扫描

<context:component-scan base-package="per.xgt"></context:component-scan>
  • 创建表以及对应实体类
CREATE TABLE `test_book`  (
  `test_bookid` int NOT NULL AUTO_INCREMENT,
  `test_bookname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `test_bookstatus` int NULL DEFAULT NULL,
  PRIMARY KEY (`test_bookid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
public class Book {

    private int bookId;
    private String bookName;
    private int bookStatus;

    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\'' +
                ", bookStatus=" + bookStatus +
                '}';
    }

    public int getBookId() {
        return bookId;
    }

    public void setBookId(int bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public int getBookStatus() {
        return bookStatus;
    }

    public void setBookStatus(int bookStatus) {
        this.bookStatus = bookStatus;
    }
}
  • 添加操作
    调用jdbcTemplate.update();第一个参数是SQL语句,第二是个可变参数,设置SQL语句中的位置参数;
@Override
    public void add(Book book) {
        // sql
        String sql = "insert into test_book values(null,?,?)";
        // 调用方法实现,返回值是影响行数
        int update = jdbcTemplate.update(sql, book.getBookName(), book.getBookStatus());
        System.out.println(update);
    }
  • 修改和删除与新增是一样的,只是SQL不一样,且在传入参数的地方可以用数组的方式传入;

  • 查询返回某个值
    第一个参数是sql,第二个参数是返回值类型;

@Override
    public int selectCount() {
        String sql = "SELECT count(1) from test_book";
        Integer i = jdbcTemplate.queryForObject(sql, Integer.class);
        return i;
    }
  • 查询返回某个对象
    第一个参数:sql;第二个参数:是接口,返回不同类型数据,使用这个接口里面实现类完成数据的封装;第三个参数是未知数值;
    注意:返回封装的实体类属性名必须和查询结果列名相匹配
@Override
    public Book selectOne(int id) {
        String sql = "SELECT test_bookid bookId,test_bookname bookName,test_bookStatus bookStatus from test_book where test_bookid = ?;";
        Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
        return book;
    }
  • 查询返回集合
@Override
    public List<Book> selectList() {
        String sql = "SELECT test_bookid bookId,test_bookname bookName,test_bookStatus bookStatus from test_book;";
        List<Book> books = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
        return books;
    }
  • 批量添加
    batchUpdate():有两个参数:第一个参数:sql语句;第二个参数:修改的多条数据;
@Override
    public int[] batchAdd(List<Object[]> batchArgs) {
        String sql = "insert into test_book values(null,?,?)";
        // 返回值为int[],数组长度为影响行数
        int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
        return ints;
    }
  • 批量修改和删除与添加类似只是SQL不同;

Spring事务

事务:数据库操作最基本单元,逻辑上的一组操作,要么都成功,如果有一个失败那么所有的操作都失败;

事务四个特性(ACID)

  • 原子性:要么都成功,要么都失败;
  • 一致性:操作之前和操作之后的总量是不变的;
  • 隔离性:多事务操作时,事务之间不会产生影响;
  • 持久性:事务提交后永久生效;

事务传播行为

propagation:当一个事务方法被另一个事务方法调用时,这个事务方法如何进行;
定义多重传播行为:
默认REQUIRED;

  1. REQUIRED:如果有事务在运行,当前的方法就在这个事务内运行,否则就启动一个新的事务,并在自己的事务内运行;
  2. REQUIRED_NEW:当前的方法必须启动新事务,并在它自己的事务内运行,如果有事务在运行,应该将它挂起;
  3. SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中;
  4. NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  5. MANDATORY:当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常;
  6. NEVER:当前的方法不应该运行在事务中,如果有运行的事务,就抛出异常;
  7. NESTED:如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行,嵌套的事务可以独立于当前事务运行。如果当前事务不存在,那么其行为与REQUIRED一样:各个厂商对这种传播行为支持有差异;

事务隔离级别

isolation:事务特性隔离性,多事务操作之间不会产生影响;
问题:
1.脏读:一个未提交的事务读读取到了另一个未提交的数据;
2.不可重复读:一个未提交的事务,读取到了提交事务修改数据;
3.幻读:一个未提交的事务读取到了另一条事务添加数据;
解决问题:通过事务的隔离级别解决问题;
1.READ_UNCOMMITTED读未提交:不能解决问题;
2.READ_COMMITTED读已提交:解决脏读;
3.REPEATABLE_READ可重复度:解决不可重复读;
4.SERIALIZABLE串行化:解决所有问题
Spring默认使用数据库使用的隔离级别,Mysql默认是可重复读

环境搭建

  • 表结构和初始数据
DROP TABLE IF EXISTS `test_account`;
CREATE TABLE `test_account`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `userName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `money` int NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
INSERT INTO `test_account` VALUES (1, 'lucy', 1000);
INSERT INTO `test_account` VALUES (2, 'mary', 1000);
  • 创建service,搭建dao,完成创建和对象注入
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://1111.1.111.11:3306/test?useSSL=false&amp;serverTimezone=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--JDBCTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <context:component-scan base-package="per.xgt"></context:component-scan>
@Repository
public class MoneyDaoImpl implements MoneyDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void addMoney() {
        String sql = "update test_account set money = money + ? where userName = ?";
        int i = jdbcTemplate.update(sql, 100, "mary");
    }

    @Override
    public void reduceMoney() {
        String sql = "update test_account set money = money - ? where userName = ?";
        int i = jdbcTemplate.update(sql, 100, "lucy");
    }
}
@Service
public class MoneyService {

    @Autowired
    private MoneyDao moneyDao;

    public void accountMoney(){
        moneyDao.reduceMoney();
        moneyDao.addMoney();
    }
}

事务操作

  • 事务添加到JavaEE三层结构里面Service层;
  • 在Spring进行事务管理操作:
    1.有两种方式:编程式事务管理和声明式事务管理;

Spring事务管理API
(1)提供一个接口:PlatformTransactionManager,代表事务管理器,这个接口针对不同的框架提供不同的实现类;

声明式事务管理

  • 基于注解方式:底层使用AOP;
    1.在Spring配置文件配置事务管理器
<!--创建事务管理器:就是创建实现类的对象-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

2.开启事务注解

<!--开启事务注解-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>

3.service类上或者方法上添加注解@Transactional
如果把注解添加在类上面,这个类里面所有的方法都添加了事务
如果吧注解添加在方法上面,只是为这个方法添加事务

@Transactional
    public void accountMoney(){
        moneyDao.reduceMoney();
        moneyDao.addMoney();
    }
  • 常见配置参数:
    propagation:事务的传播行为;
    isolation:事务的隔离级别;
    timeout:超时时间,开启事务到提交事务的最长时间,超时自动回滚,Spring默认是-1没有超时时间,设置单位为秒;
    readOnly:只读,默认值为False。当设置为True时,只能查询;
    rollbackFor:设置运行中出现了哪些异常才进行事务回滚;
    noRollbackFor:设置运行中出现了哪些异常不进行回滚;
@Transactional(propagation = Propagation.REQUIRED,
    isolation = Isolation.READ_COMMITTED,
    timeout = 30,
    readOnly = false,
    rollbackFor = NullPointerException.class,
    noRollbackFor = ClassCastException.class)
    public void accountMoney(){
        moneyDao.reduceMoney();
        moneyDao.addMoney();
    }
  • 完全注解声明式事务
    使用配置类替代xml文件
@Configuration // 声明配置类
@ComponentScan(basePackages = "per.xgt")// 开启组件扫描
@EnableTransactionManagement //开启事务
public class txConfig {

    // 数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://xxx:3306/test?useSSL=false&serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    // 创建JdbcTemplate
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        // 到IOC容器中根据类型找到DataSource
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        // 注入dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    // 事务管理器对象
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        return transactionManager;
    }

}
  • 基于XML配置文件方式:
    (1)配置事务管理器
    (2)配置通知
    (3)配置切入点与切面
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://xxx:3306/test?useSSL=false&amp;serverTimezone=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--JDBCTemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--开启组件扫描-->
    <context:component-scan base-package="per.xgt"></context:component-scan>

    <!--创建事务管理器:就是创建实现类的对象-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

   <!--配置通知-->
    <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
        <!--配置事务的相关参数-->
        <tx:attributes>
            <!--指定哪种规则的方法上面添加事务-->
            <tx:method name="account*" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>

    <!--配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="txPointCut" expression="execution(* per.xgt.service..*.*(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"></aop:advisor>
    </aop:config>

日志

Spring5已经移除了Log4jConfigListener,官方建议使用Log4j2;

Spring整个Log4j2

  • 引入依赖
<!--log4j2依赖相关-->
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.11.2</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>
  • 创建log4j2配置文件log4j2.xml(固定名字)
<?xml version="1.0" encoding="UTF-8"?>
<!--&lt;!&ndash;日志级别:OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL ALL显示内容越多&ndash;&gt;-->
<!--&lt;!&ndash;Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出&ndash;&gt;-->
<!--<configuration status="INFO">-->
<!--    &lt;!&ndash;先定义所有的appender&ndash;&gt;-->
<!--    <appenders>-->
<!--        &lt;!&ndash;输出日志信息到控制台&ndash;&gt;-->
<!--        <console name="Console" target="SYSTEM_OUT">-->
<!--            &lt;!&ndash;控制日志输出的格式&ndash;&gt;-->
<!--            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>-->
<!--        </console>-->
<!--    </appenders>-->
<!--    &lt;!&ndash;然后定义logger,只有定义了logger并引入的appender,appender才会生效&ndash;&gt;-->
<!--    &lt;!&ndash;root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出&ndash;&gt;-->
<!--    <loggers>-->
<!--        <root level="info">-->
<!--            <appender-ref ref="Console"/>-->
<!--        </root>-->
<!--    </loggers>-->
<!--</configuration>-->

<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--变量配置-->
<Properties>
    <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <!-- %logger{36} 表示 Logger 名字最长36个字符 -->
    <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
    <!-- 定义日志存储的路径,不要配置相对路径 -->
    <property name="FILE_PATH" value="D:/logs" />
    <!--文件名称-->
    <property name="FILE_NAME" value="test.log" />
</Properties>
<appenders>
    <console name="Console" target="SYSTEM_OUT">
        <!--输出日志的格式-->
        <PatternLayout pattern="${LOG_PATTERN}"/>
        <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
        <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
    </console>
    <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
    <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
        <PatternLayout pattern="${LOG_PATTERN}"/>
    </File>
    <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
    <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
        <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
        <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
        <PatternLayout pattern="${LOG_PATTERN}"/>
        <Policies>
            <!--interval属性用来指定多久滚动一次,默认是1 hour-->
            <TimeBasedTriggeringPolicy interval="1"/>
            <SizeBasedTriggeringPolicy size="10MB"/>
        </Policies>
        <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
        <DefaultRolloverStrategy max="15"/>
    </RollingFile>
    <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
    <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
        <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
        <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
        <PatternLayout pattern="${LOG_PATTERN}"/>
        <Policies>
            <!--interval属性用来指定多久滚动一次,默认是1 hour-->
            <TimeBasedTriggeringPolicy interval="1"/>
            <SizeBasedTriggeringPolicy size="10MB"/>
        </Policies>
        <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
        <DefaultRolloverStrategy max="15"/>
    </RollingFile>
    <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
    <RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
        <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
        <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
        <PatternLayout pattern="${LOG_PATTERN}"/>
        <Policies>
            <!--interval属性用来指定多久滚动一次,默认是1 hour-->
            <TimeBasedTriggeringPolicy interval="1"/>
            <SizeBasedTriggeringPolicy size="10MB"/>
        </Policies>
        <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
        <DefaultRolloverStrategy max="15"/>
    </RollingFile>
</appenders>
<!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
<!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
    <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
    <logger name="org.mybatis" level="info" additivity="false">
        <AppenderRef ref="Console"/>
    </logger>
    <!--监控系统信息-->
    <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
    <Logger name="org.springframework" level="info" additivity="false">
        <AppenderRef ref="Console"/>
    </Logger>
    <root level="info">
        <appender-ref ref="Console"/>
        <appender-ref ref="Filelog"/>
        <appender-ref ref="RollingFileInfo"/>
        <appender-ref ref="RollingFileWarn"/>
        <appender-ref ref="RollingFileError"/>
    </root>
</loggers>
</configuration>

@Nullable

@Nullable注解可以使用在方法上面,属性上面,参数上面,表示方法返回、属性值、参数可以为空;

函数式注册对象

Spring5核心容器支持函数式风格GenericApplicationContext

@Test
    public void testGenericApplicationContext(){
        // 创建GenericApplicationContext对象
        GenericApplicationContext context = new GenericApplicationContext();
        // 把里面内容清空,然后进行对象注册
        context.refresh();
        context.registerBean(MoneyService.class,()->new MoneyService());
        // 获取在Spring注册的对象
        MoneyService moneyService = (MoneyService) context.getBean("per.xgt.service.MoneyService");
        System.out.println(moneyService);
    }

还可以

@Test
    public void testGenericApplicationContext2(){
        // 创建GenericApplicationContext对象
        GenericApplicationContext context = new GenericApplicationContext();
        // 把里面内容清空,然后进行对象注册
        context.refresh();
        context.registerBean("moneyService",MoneyService.class,()->new MoneyService());
        // 获取在Spring注册的对象
        MoneyService moneyService = (MoneyService) context.getBean("moneyService");
        System.out.println(moneyService);
    }

JUnit

整合JUnit4

  • 引入Spring相关针对测试依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.6.RELEASE</version>
            <scope>test</scope>
        </dependency>
  • 编写测试类
@RunWith(SpringJUnit4ClassRunner.class)// 加载JUnit
@ContextConfiguration("classpath:bean1.xml") // 加载配置文件
public class testJunit4 {

    @Autowired
    private MoneyService moneyService;

    @Test
    public void test1(){
        moneyService.accountMoney();
    }

}

整合JUnit5

  • 引入依赖
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.3.2</version>
            <scope>test</scope>
        </dependency>
  • 创建测试类
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean1.xml")
public class testJUnit5 {

    @Autowired
    private MoneyService moneyService;

    @Test
    public void test1(){
        moneyService.accountMoney();
    }

}
  • 优化,使用组合注解简化
@SpringJUnitConfig(locations = "classpath:bean1.xml")
public class testJUnit5 {

    @Autowired
    private MoneyService moneyService;

    @Test
    public void test1(){
        moneyService.accountMoney();
    }

}

WebFlux

SpringWebFlux介绍

使用传统web框架比如SpringMVC,这些基于Servlet容器,WebFlux是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1以后才支持,核心是基于Reactor的相关API实现的;
异步: 调用者发送请求,不等着对方回应就去做其他的;
同步: 调用者发送请求,如果等着对方回应之后才去做其他事情;

非阻塞: 收到请求后马上给出反馈然后再去做事情;
阻塞: 被调用者收到请求后,做完了请求任务后才给出反馈;
以上都是针对对象不一样

  • 特点
    1)非阻塞式:在有限资源下,提高系统吞吐量和伸缩量,以Reactor为基础实现响应式编程;
    2)函数式编程:Spring5框架基于java8,WebFlux使用函数式编程方式实现路由请求;

  • 与SpringMVC的区别:
    1)两个框架都可以使用注解方式,都运行在Tomcat等容器中;
    2)SpringMVC采用命令式编程,WebFlux采用异步响应式编程

响应式编程(Reactor实现)

响应式编程操作中,Reactor是满足Reactive规范框架

Reactor有以下两个核心类:Mono、Flux这两个类实现接口Publisher,提供丰富操作符。Flux对象实现发布者,返回N个元素;Mono实现发布者,返回0或1个元素

Flux和Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号:元素值、错误信号、完成信号,其中错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号同时会将错误传递给订阅者;其次,错误信号和完成信号不能共存;如果没有发送任何元素值,直接发送错误或者完成信号,表示是空数据流;如果没有错误信号也没有完成信号,表示无限数据流;

  • 引入依赖
<!-- https://mvnrepository.com/artifact/io.projectreactor/reactor-core -->
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
    <version>3.1.5.RELEASE</version>
</dependency>
  • Mono和Flux
public static void main(String[] args) {
        // just方法直接声明
        Flux.just(1,2,3,4);
        Mono.just(1);

        // 其他方法
        // 数组
        Integer[] arr = {1,2,3,4};
        Flux.fromArray(arr);

        // 集合
        List<Integer> list = Arrays.asList(arr);
        Flux.fromIterable(list);

        // stream流
        Stream<Integer> stream = list.stream();
        Flux.fromStream(stream);
    }

调用just或者其他方法只是声明数据流,数据流并没有发出,只有进行订阅之后才会发出数据流;

Flux.just(1,2,3,4).subscribe(System.out::print);
        System.out.println();
        Mono.just(1).subscribe(System.out::println);
  • 操作符
    **map:**元素映射为新元素;
    **flatMap:**元素映射为流,把转换之后多个流合并成大的流;

WebFlux执行流程和核心API

SpringWebFlux基于Reactor,默认容器是Netty,Netty是高性能,NIO框架,异步非阻塞的框架;

SpringWebFlux执行过程和SpringMVC相似的:

  • SpringWebFlux核心控制器DispatchHandler,实现接口WebHandler;

  • DispatchHandler负责请求的处理
    1.HandlerMapping根据客户端请求查询请求方法
    2.HandlerAdapter实现具体业务方法
    3.HandlerResultHandler负责响应结果处理

  • SpringMVC方式实现:同步阻塞,基于SpringMVC+Servlet+Tomcat

  • SpringWebFluc方式实现:异步非阻塞,基于SpringWebFlux+Reactor+Netty

SpringWebFlux(基于注解编程模型)

  • 创建SpringBoot工程,引入WebFlux依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-webflux -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
  • 在application.properties配置启动的端口号
# 设置启动端口号
server.port=8081
  • 创建包和相关接口与类
    实体类
public class User {

    private String name;
    private String gender;
    private Integer age;

    public User(String name, String gender, Integer age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public User() {
    }

    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;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

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

创建接口,定义操作方法

public interface UserService {

    // 根据ID查询用户
    Mono<User> findOneById(int id);

    // 查询所有用户
    Flux<User> findAll();

    // 添加用户
    Mono<Void> insertOne(Mono<User> user);

}

接口方法实现类

public class UserServiceImpl implements UserService {

    // 创建map集合,存储数据
    private final Map<Integer,User> users = new HashMap<>();

    public UserServiceImpl() {
        this.users.put(1,new User("xgt","男",26));
        this.users.put(1,new User("zyk","男",25));
        this.users.put(1,new User("wp","男",25));
        this.users.put(1,new User("NB","男",1000));
    }

    // 查询一个元素
    @Override
    public Mono<User> findOneById(int id) {
        return Mono.justOrEmpty(this.users.get(id));
    }

    // 查询多个元素
    @Override
    public Flux<User> findAll() {
        return Flux.fromIterable(this.users.values());
    }

    // 添加用户
    @Override
    public Mono<Void> insertOne(Mono<User> userMono) {
        return userMono.doOnNext(person -> {
            // 向map集合里面方值
            int id = users.size() + 1;
            users.put(id,person);
        }).thenEmpty(Mono.empty());
    }
}

创建controller

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    // id查询
    @GetMapping("/user/findOneById/{id}")
    public Mono<User> findOneById(@PathVariable int id){
        return userService.findOneById(id);
    }

    // 查询所有
    @GetMapping("/user/findAll")
    public Flux<User> findAll(){
        return userService.findAll();
    }

    // 添加
    @PostMapping("/user/insertOne")
    public Mono<Void> insertOne(@RequestBody User user){
        Mono<User> userMono = Mono.just(user);
        return userService.insertOne(userMono);
    }

}

SpringWebFlux(基于函数式编程模型)

实现函数式编程,两个接口:
1.RouterFunction:实现路由功能,将请求转发给对应的Handler;
2.HandlerFunction:处理请求,并且响应函数,执行请求的方法;

  • 在使用函数式编程模型操作的时候,需要自己初始化服务器;
  • SpringWebFlux请求和响应不再是ServletRequest和ServletResponse;

代码实现

  • 创建Handler(具体实现方法)
public class UserHandler {

    public final UserService userService;

    public UserHandler(UserService userService){
        this.userService = userService;
    }

    public Mono<ServerResponse> getUserById(ServerRequest request){
        // 获取ID值
        int id = Integer.valueOf(request.pathVariable("id"));
        // 空值处理
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        // 调用Service的方法得到数据
        Mono<User> userMono = this.userService.findOneById(id);
        // 将userMono进行转换
        // 使用Reactor操作符flatMap
        return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person)).switchIfEmpty(notFound);
    }

    public Mono<ServerResponse> getAll(ServerRequest request){
        // 调用service得到结果
        Flux<User> users = this.userService.findAll();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
    }

    public Mono<ServerResponse> saveUser(ServerRequest request){
        // 得到User对象
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.insertOne(userMono));
    }

}
  • 编写Router、创建服务器、完成适配
public class UserServer {

    public static void main(String[] args) throws Exception {
        UserServer server = new UserServer();
        server.createReactorServer();
        System.out.println("回车退出:");
        System.in.read();
    }

    // 创建Router路由
    public RouterFunction<ServerResponse> routingFunction(){
        UserService userService = new UserServiceImpl();
        UserHandler userHandler = new UserHandler(userService);

        return RouterFunctions.route(GET("/users/{id}").and(accept(APPLICATION_JSON)), userHandler::getUserById).andRoute(GET("/users").and(accept(APPLICATION_JSON)),userHandler::getAll);
    }


    // 创建服务器完成适配
    public void createReactorServer(){
        // 路由和handler适配
        RouterFunction<ServerResponse> router = routingFunction();
        HttpHandler httpHandler = toHttpHandler(router);
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);

        // 创建服务器
        HttpServer httpServer = HttpServer.create();
        httpServer.handle(adapter).bindNow();
    }

}

执行main方法,浏览器访问即可;

  • 使用WebClient调用
public static void main(String[] args) {
        WebClient webClient = WebClient.create("http://127.0.0.1:63308");

        // 根据id查询
        String id = "1";
        User userResult = webClient.get().uri("/users/{id}",id)
                .accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class)
                .block();
        System.out.println(userResult.getName());


        // 查询所有
        Flux<User> results = webClient.get().uri("/users")
                .accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);

        results.map(stu -> stu.getName())
                .buffer().doOnNext(System.out::println).blockFirst();

    }

Spring注解驱动

注册组件

@Configuration 告诉Spring这是一个配置类
@Bean(value = “person”) 给容器注册一个Bean,类型为返回值类型,id默认是方法名作为id; value属性可以指定Bean的id值

// 配置类==配置文件
@Configuration// 告诉Spring这是一个配置类
public class MyConfig {

    // 给容器注册一个Bean,类型为返回值类型,id默认是方法名作为id;
    @Bean(value = "person") // value属性就是指定Bean的id值
    public Person getPerson(){
        return new Person(20, "xgt");
    }

}

自动扫描组件

在配置类上使用此注解:
@ComponentScan(basePackages = “per.xgt”):basePackages指定要扫描的包

指定扫描规则

按照规则指定排除哪些组件excludeFilters = Filter[]
按照规则指定包含哪些组件includeFilters = Filter[],配之前需要关闭扫描

@ComponentScan(basePackages = "per.xgt",useDefaultFilters = false,excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
}//按照规则指定排除哪些组件excludeFilters = Filter[]
,includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
}//按照规则指定包含哪些组件includeFilters = Filter[],陪之前需要关闭扫描的默认规则
)

完整配置

// 配置类==配置文件
@Configuration// 告诉Spring这是一个配置类
@ComponentScan(basePackages = "per.xgt",useDefaultFilters = false,excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
}//按照规则指定排除哪些组件excludeFilters = Filter[]
,includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
}//按照规则指定包含哪些组件includeFilters = Filter[],陪之前需要关闭扫描的默认规则
)
public class MyConfig {
    // 给容器注册一个Bean,类型为返回值类型,id默认是方法名作为id;
    @Bean(value = "person") // value属性就是指定Bean的id值
    public Person getPerson(){
        return new Person(20, "xgt");
    }
}

指定过滤规则typeFilter

ASSIGNABLE_TYPE:指定类型,包括他的子类和实现类

@ComponentScan.Filter(type = 
FilterType.ASSIGNABLE_TYPE,classes = BookService.class)

FilterType.REGEX:正则表达式
ilterType.CUSTOM:自定义规则

@ComponentScan.Filter(type = FilterType.CUSTOM,classes = MyTypeFilter.class)
public class MyTypeFilter implements TypeFilter {
    /**
     *
     * @param metadataReader 读取的当前正在扫描的类的信息
     * @param metadataReaderFactory 可以获取其他任何类的信息
     * @return
     * @throws IOException
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 获取当前类的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 获取当前正在扫描的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 获取当前类的资源信息
        Resource resource = metadataReader.getResource();


        String className = classMetadata.getClassName();
        System.out.println("---->"+className);

        if (className.contains("per"))return true;

        return false;
    }
}

组件作用域

@Configuration
public class MyConfig2 {

    /**
     *  SCOPE_SINGLETON:singleton,单实例,IOC容器创建好后,就会调用方法创建实例放入容器中;
     *  SCOPE_PROTOTYPE:prototype,多实例,ioc容易启动并不会区调用方法创建对象,二十每次获取的时候才会调用方法创建对象;
     *  WEB状态下还能设置:
     *  REFERENCE_REQUEST:request,同一个请求创建一个实例
     *  REFERENCE_SESSION:session:同一个session创建一个实例
     */
    @Scope("prototype")
    @Bean(value = "person")
    public Person person(){
        return new Person(22, "道士");
    }

}

bean懒加载

单例bean,默认在容器启动的时候创建对象;
懒加载:容器启动不创建对象,在第一次获取bean的时候再创建对象且再进行初始化;

@Lazy
    @Bean(value = "person")
    public Person person(){
        return new Person(22, "道士");
    }

按照条件注册bean

@Conditional:按照一定条件进行判断,满足条件给容器中注册bean

@Conditional(MyConditional.class)
    @Bean("p1")
    public Person person01(){
        return new Person(11, "person01");
    }
public class MyConditional implements Condition {

    /**
     *
     * @param conditionContext 判断条件能使用的上下文环境
     * @param annotatedTypeMetadata 注释信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 获取IOC的bean工厂
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        // 获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        // 获取环境变量值
        Environment environment = conditionContext.getEnvironment();
        // 获取到bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();

        String property = environment.getProperty("os.name");
        if (property.contains("Lin")) return true;
        return false;
    }
}

@Import导入组件

  • 在配置类上使用注解快速导入组件
@Import(Color.class)// id默认是组件的全类名
  • @ImportSelector导入组件
    返回需要导入的组件的全类名数组
@Import({Color.class,MyImportSelector.class})// id默认是组件的全类名
public class MyImportSelector implements ImportSelector {

    /**
     *
     * @param annotationMetadata 当前标注@Import注解的类的所有注解信息
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"per.xgt.bean.Blue"};
    }
}
  • 使用ImportBeanDefinitionRegistrar
@Import({Color.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     *
     * @param annotationMetadata 当前类的注解信息
     * @param registry BeanDefinition注册类,把所有需要添加到容器中的Bean:
     *                               BeanDefinitionRegistry.registerBeanDefinition手工注册
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
        boolean red = registry.containsBeanDefinition("per.xgt.bean.Blue");
        boolean blue = registry.containsBeanDefinition("per.xgt.bean.Color");
        if (red && blue){
            // 指定Bean定义信息
            RootBeanDefinition beanDefinition = new RootBeanDefinition(Yellow.class);
            // 注册一个Bean,指定Bean名
            registry.registerBeanDefinition("redBean",beanDefinition);
        }
    }
}

FactoryBean注册组件

@Bean
    public ColorFactoryBean colorFactoryBean(){
        return new ColorFactoryBean();
    }
public class ColorFactoryBean implements FactoryBean<Color> {
    // 返回一个对象,会添加到容器中
    // 工厂Bean获取的事调用getObject创建的对象
    @Override
    public Color getObject() throws Exception {
        return new Color();
    }

    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

Bean生命周期

生命周期:Bean创建–初始化–销毁的过程

bean指定初始化和销毁方法
  • @Bean注解指定
    **注意:**单实例Bean在容器关闭的时候执行销毁方法,多实例Bean容器不会调用销毁方法;,
public class Car {

    public Car() {
        System.out.println("构造器...");
    }

    public void init(){
        System.out.println("init...");
    }

    public void destroy(){
        System.out.println("destroy...");
    }
}
@Bean(initMethod = "init",destroyMethod = "destroy")
    public Car car(){
        return new Car();
    }
  • 实现InitializingBean和DisposableBean接口
@Component
public class Cat implements InitializingBean, DisposableBean {

    public Cat() {
        System.out.println("构造器...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("销毁...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("初始...");
    }
}
  • 使用PostConstruct和PreDestroy注解
    PostConstruct:在bean创建完成并且属性赋值完成,来执行初始化
    PreDestroy:在容器销毁Bean前,进行清理工作
@Component
public class Dog {

    public Dog() {
        System.out.println("dog...");
    }

    @PostConstruct
    public void init(){
        System.out.println("init...");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("destroy...");
    }

}
  • Bean的后置处理器BeanPostProcessor
    在bean初始化前后进行处理
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
        System.out.println("postProcessBeforeInitialization..." + o + "-->" + s);
        return o;
    }

    @Override
    public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
        System.out.println("postProcessAfterInitialization..." + o + "-->" + s);
        return o;
    }
}

属性赋值

@Value
@Value("#{50-8}")
    private Integer age;

    @Value("xgt")
    private String name;
@PropertySource加载外部配置文件
  • 加载properties文件
    在配置类上加上注解:@PropertySource将k/v数据读取到环境变量中
@PropertySource("classpath:/person.properties")
@Value("${person.name}")
    private String boomBoxName;

自动装配

Spring利用依赖注入DI,完成对IOC容器中各个组件的依赖关系赋值;

@Autowired

默认先按照类型在容器中找对应的组件,找到就赋值;如果找到相同类型的组件,再将属性的名称作为组件的id取容器中查找;自动装配一定要将属性赋值好,没有就报错;
required属性可以设置是否必须赋值;

@Autowired
    private BookService bookService;
    @Autowired(required = false)
    private BookDao bookDao;
@Qualifier指定要装配的组件id,而不是属性名
@Qualifier("bookDaoImpl")
    @Autowired
    private BookDao bookDao;
@Primary

让Spring进行自动装配的时候,默认使用首选的Bean,也可以继续使用@Qualifier指定需要装配哪个Bean;

@Primary
    @Bean
    public BookService bookService(){
        return new BookServiceImpl();
    }
@Resource

可以和@Autowired一样实现自动装配,默认是按照组件名称进行装配的;
name属性可以注入指定名称的组件

@Inject

和@Autowired功能一样

  • 导入依赖
<dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
@Autowired标注位置
方法上:
@Autowired
    public void setCar(Car car) {
        this.car = car;
    }

Spring容器创建对象的时候,就会调用方法,完成赋值,方法使用的参数,自定义类型的值,容IOC容器中进行获取;

还可以@Bean+方法参数的方式,参数从容器中获取;默认不写Autowired

构造器上:

默认加载IOC容器的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值,如果组件足有一个有参构造器,这个有参构造器的@Autowired可以省略,还是 可以自动从容器中获取;

Aware注入Spring底层组件

自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,XXX),自定义组件实现XXXAware,在创建对象的时候,会调用接口规范的方法注入相关组件;

@Component
public class Red implements
        ApplicationContextAware,
        BeanNameAware,
        EmbeddedValueResolverAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("传入的IOC:"+applicationContext);
        applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("当前bean的名字:" + s);
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
        String resolveStringValue = stringValueResolver.resolveStringValue("你好${os.name} 我是#{20*18}");
        System.out.println(resolveStringValue);
    }
}

@Profile

Spring为我们提供的可以根据当前环境,动态激活和切换一系列组件的功能
指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境都能注册此组件;
加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中,默认是default环境;也可以写在配置类上,只有环境激活相关配置才会生效

  • 多个DataSourceBean
@Profile("test")
    @Bean("testD")
    public DataSource dataSourceTest() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setJdbcUrl("jdbc:mysql://xxx/test");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }
    @Profile("dev")
    @Bean("devD")
    public DataSource dataSourceDev() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setJdbcUrl("jdbc:mysql://xxx/test");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }
    @Profile("prod")
    @Bean("prodD")
    public DataSource dataSourceProd() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setJdbcUrl("jdbc:mysql://xxx/test");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }
  • 启动设置VM参数 -Dspring.profiles.active=xx 使用xx环境

  • 代码方式:

@Test
    public void test2(){

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfigProfile.class);

        // 创建一个applicationContext
        // 设置需要激活的环境
        context.getEnvironment().setActiveProfiles("dev");
        // 注册主配置类
        context.register(MyConfigProfile.class);
        // 启动刷新容器
        //context.refresh();

        String[] names = context.getBeanNamesForType(DataSource.class);
        for (String name : names) {
            System.out.println(name);
        }
        context.close();
    }

SpringAnnotation AOP

AOP:在程序运行期间动态地将某段代码切入到指定方法指定位置进行运行的编程方法;

代码示例:
  • 导入依赖
<dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
    </dependencies>
  • 业务逻辑类
public class MathCalculator {

    public int div(int i,int j){
        return i/j;
    }

}
  • 日志切面类
public class LogAspect {

    public void logStart(){
        System.out.println("方法运行...参数是:{}");
    }

    public void logEnd(){
        System.out.println("方法结束...");
    }

    public void logReturn(){
        System.out.println("正常返回...返回结果:{}");
    }

    public void logException(){
        System.out.println("方法异常...异常信息:{}");
    }

}
  • 通知方法
    前置通知@Before:在目标方法之前运行;
    后置通知@After:在目标运行结束之后运行,无论方法正常结束还是异常结束;
    返回通知@AfterReturning:在目标方法正常返回之后运行;
    异常通知@AfterThrowing:在目标方法出现异常之后运行;
    环绕通知@Around:动态代理,手动推进目标方法运行;

  • 给切面类的目标方法标注对应通知注解

public class LogAspect {

    // 抽取公共的切入点表达式
    // 本类引用直接引用方法
    // 其他的切面引用,需要引入全名
    @Pointcut(value = "execution(public int aop.MathCalculator.*(..))")
    public void pointCut(){};

    // 在目标方法之前切入,切入点表达式指定在哪个方法切入;
    @Before(value = "execution(public int aop.MathCalculator.*(..))")
    public void logStart(){
        System.out.println("方法运行...参数是:{}");
    }

    //
    @After("pointCut()")
    public void logEnd(){
        System.out.println("方法结束...");
    }

    @AfterReturning("pointCut()")
    public void logReturn(){
        System.out.println("正常返回...返回结果:{}");
    }

    @AfterThrowing("pointCut()")
    public void logException(){
        System.out.println("方法异常...异常信息:{}");
    }

}
  • 将切面类和业务逻辑类,都加入到容器中
@Configuration
public class MyConfigAOP {
    
    @Bean
    public MathCalculator calculator(){
        return new MathCalculator();
    }
    
    @Bean
    public LogAspect logAspect(){
        return new LogAspect();
    } 
    
}
  • 告诉哪个类是切面类(给切面类加注解)
@Aspect //告诉spring哪个类是切面类
public class LogAspect {
}
  • 开启基于注解的AOP模式
@EnableAspectJAutoProxy
@Configuration
public class MyConfigAOP {}
  • 获取相关参数
    JoinPoint一定要出现在参数表的第一位
@Aspect //告诉spring哪个类是切面类
public class LogAspect {

    // 抽取公共的切入点表达式
    // 本类引用直接引用方法
    // 其他的切面引用,需要引入全名
    @Pointcut(value = "execution(public int aop.MathCalculator.*(..))")
    public void pointCut(){};

    // 在目标方法之前切入,切入点表达式指定在哪个方法切入;
    @Before(value = "execution(public int aop.MathCalculator.*(..))")
    public void logStart(JoinPoint joinPoint){
        // 获取方法名
        String name = joinPoint.getSignature().getName();
        // 获取参数列表
        Object[] args = joinPoint.getArgs();
        System.out.println("@Before...方法名:"+ name +"...参数是:{" + Arrays.asList(args) + "}");
    }

    //
    @After(value = "pointCut()")
    public void logEnd(JoinPoint joinPoint){

        System.out.println("@After...");
    }

    @AfterReturning(value = "pointCut()",returning = "result")
    public void logReturn(Object result){
        System.out.println("@AfterReturning...返回结果:{"+result+"}");
    }

    @AfterThrowing(value = "pointCut()",throwing = "exception")
    public void logException(Exception exception){
        System.out.println(" @AfterThrowing...异常信息:{"+exception+"}");
    }

}
AOP原理
/**
 * AOP:【动态代理】
 * 		指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;
 *
 * 1、导入aop模块;Spring AOP:(spring-aspects)
 * 2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常,xxx)
 * 3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;
 * 		通知方法:
 * 			前置通知(@Before):logStart:在目标方法(div)运行之前运行
 * 			后置通知(@After):logEnd:在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
 * 			返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行
 * 			异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
 * 			环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
 * 4、给切面类的目标方法标注何时何地运行(通知注解);
 * 5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
 * 6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect)
 * [7]、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】
 * 		在Spring中很多的 @EnableXXX;
 *
 * 三步:
 * 	1)、将业务逻辑组件和切面类都加入到容器中;告诉Spring哪个是切面类(@Aspect)
 * 	2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)
 *  3)、开启基于注解的aop模式;@EnableAspectJAutoProxy
 *
 * AOP原理:【看给容器中注册了什么组件,这个组件什么时候工作,这个组件的功能是什么?】
 * 		@EnableAspectJAutoProxy;
 * 1、@EnableAspectJAutoProxy是什么?
 * 		@Import(AspectJAutoProxyRegistrar.class):给容器中导入AspectJAutoProxyRegistrar
 * 			利用AspectJAutoProxyRegistrar自定义给容器中注册bean;BeanDefinetion
 * 			internalAutoProxyCreator=AnnotationAwareAspectJAutoProxyCreator
 *
 * 		给容器中注册一个AnnotationAwareAspectJAutoProxyCreator;
 *
 * 2、 AnnotationAwareAspectJAutoProxyCreator:
 * 		AnnotationAwareAspectJAutoProxyCreator
 * 			->AspectJAwareAdvisorAutoProxyCreator
 * 				->AbstractAdvisorAutoProxyCreator
 * 					->AbstractAutoProxyCreator
 * 							implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware
 * 						关注后置处理器(在bean初始化完成前后做事情)、自动装配BeanFactory
 *
 * AbstractAutoProxyCreator.setBeanFactory()
 * AbstractAutoProxyCreator.有后置处理器的逻辑;
 *
 * AbstractAdvisorAutoProxyCreator.setBeanFactory()-》initBeanFactory()
 *
 * AnnotationAwareAspectJAutoProxyCreator.initBeanFactory()
 *
 *
 * 流程:
 * 		1)、传入配置类,创建ioc容器
 * 		2)、注册配置类,调用refresh()刷新容器;
 * 		3)、registerBeanPostProcessors(beanFactory);注册bean的后置处理器来方便拦截bean的创建;
 * 			1)、先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor
 * 			2)、给容器中加别的BeanPostProcessor
 * 			3)、优先注册实现了PriorityOrdered接口的BeanPostProcessor;
 * 			4)、再给容器中注册实现了Ordered接口的BeanPostProcessor;
 * 			5)、注册没实现优先级接口的BeanPostProcessor;
 * 			6)、注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中;
 * 				创建internalAutoProxyCreator的BeanPostProcessor【AnnotationAwareAspectJAutoProxyCreator】
 * 				1)、创建Bean的实例
 * 				2)、populateBean;给bean的各种属性赋值
 * 				3)、initializeBean:初始化bean;
 * 						1)、invokeAwareMethods():处理Aware接口的方法回调
 * 						2)、applyBeanPostProcessorsBeforeInitialization():应用后置处理器的postProcessBeforeInitialization()
 * 						3)、invokeInitMethods();执行自定义的初始化方法
 * 						4)、applyBeanPostProcessorsAfterInitialization();执行后置处理器的postProcessAfterInitialization();
 * 				4)、BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功;--》aspectJAdvisorsBuilder
 * 			7)、把BeanPostProcessor注册到BeanFactory中;
 * 				beanFactory.addBeanPostProcessor(postProcessor);
 * =======以上是创建和注册AnnotationAwareAspectJAutoProxyCreator的过程========
 *
 * 			AnnotationAwareAspectJAutoProxyCreator => InstantiationAwareBeanPostProcessor
 * 		4)、finishBeanFactoryInitialization(beanFactory);完成BeanFactory初始化工作;创建剩下的单实例bean
 * 			1)、遍历获取容器中所有的Bean,依次创建对象getBean(beanName);
 * 				getBean->doGetBean()->getSingleton()->
 * 			2)、创建bean
 * 				【AnnotationAwareAspectJAutoProxyCreator在所有bean创建之前会有一个拦截,InstantiationAwareBeanPostProcessor,会调用postProcessBeforeInstantiation()】
 * 				1)、先从缓存中获取当前bean,如果能获取到,说明bean是之前被创建过的,直接使用,否则再创建;
 * 					只要创建好的Bean都会被缓存起来
 * 				2)、createBean();创建bean;
 * 					AnnotationAwareAspectJAutoProxyCreator 会在任何bean创建之前先尝试返回bean的实例
 * 					【BeanPostProcessor是在Bean对象创建完成初始化前后调用的】
 * 					【InstantiationAwareBeanPostProcessor是在创建Bean实例之前先尝试用后置处理器返回对象的】
 * 					1)、resolveBeforeInstantiation(beanName, mbdToUse);解析BeforeInstantiation
 * 						希望后置处理器在此能返回一个代理对象;如果能返回代理对象就使用,如果不能就继续
 * 						1)、后置处理器先尝试返回对象;
 * 							bean = applyBeanPostProcessorsBeforeInstantiation():
 * 								拿到所有后置处理器,如果是InstantiationAwareBeanPostProcessor;
 * 								就执行postProcessBeforeInstantiation
 * 							if (bean != null) {
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
}
 *
 * 					2)、doCreateBean(beanName, mbdToUse, args);真正的去创建一个bean实例;和3.6流程一样;
 * 					3)、
 *
 *
 * AnnotationAwareAspectJAutoProxyCreator【InstantiationAwareBeanPostProcessor】	的作用:
 * 1)、每一个bean创建之前,调用postProcessBeforeInstantiation();
 * 		关心MathCalculator和LogAspect的创建
 * 		1)、判断当前bean是否在advisedBeans中(保存了所有需要增强bean)
 * 		2)、判断当前bean是否是基础类型的Advice、Pointcut、Advisor、AopInfrastructureBean,
 * 			或者是否是切面(@Aspect)
 * 		3)、是否需要跳过
 * 			1)、获取候选的增强器(切面里面的通知方法)【List<Advisor> candidateAdvisors】
 * 				每一个封装的通知方法的增强器是 InstantiationModelAwarePointcutAdvisor;
 * 				判断每一个增强器是否是 AspectJPointcutAdvisor 类型的;返回true
 * 			2)、永远返回false
 *
 * 2)、创建对象
 * postProcessAfterInitialization;
 * 		return wrapIfNecessary(bean, beanName, cacheKey);//包装如果需要的情况下
 * 		1)、获取当前bean的所有增强器(通知方法)  Object[]  specificInterceptors
 * 			1、找到候选的所有的增强器(找哪些通知方法是需要切入当前bean方法的)
 * 			2、获取到能在bean使用的增强器。
 * 			3、给增强器排序
 * 		2)、保存当前bean在advisedBeans中;
 * 		3)、如果当前bean需要增强,创建当前bean的代理对象;
 * 			1)、获取所有增强器(通知方法)
 * 			2)、保存到proxyFactory
 * 			3)、创建代理对象:Spring自动决定
 * 				JdkDynamicAopProxy(config);jdk动态代理;
 * 				ObjenesisCglibAopProxy(config);cglib的动态代理;
 * 		4)、给容器中返回当前组件使用cglib增强了的代理对象;
 * 		5)、以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程;
 *
 *
 * 	3)、目标方法执行	;
 * 		容器中保存了组件的代理对象(cglib增强后的对象),这个对象里面保存了详细信息(比如增强器,目标对象,xxx);
 * 		1)、CglibAopProxy.intercept();拦截目标方法的执行
 * 		2)、根据ProxyFactory对象获取将要执行的目标方法拦截器链;
 * 			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
 * 			1)、List<Object> interceptorList保存所有拦截器 5
 * 				一个默认的ExposeInvocationInterceptor 和 4个增强器;
 * 			2)、遍历所有的增强器,将其转为Interceptor;
 * 				registry.getInterceptors(advisor);
 * 			3)、将增强器转为List<MethodInterceptor>;
 * 				如果是MethodInterceptor,直接加入到集合中
 * 				如果不是,使用AdvisorAdapter将增强器转为MethodInterceptor;
 * 				转换完成返回MethodInterceptor数组;
 *
 * 		3)、如果没有拦截器链,直接执行目标方法;
 * 			拦截器链(每一个通知方法又被包装为方法拦截器,利用MethodInterceptor机制)
 * 		4)、如果有拦截器链,把需要执行的目标对象,目标方法,
 * 			拦截器链等信息传入创建一个 CglibMethodInvocation 对象,
 * 			并调用 Object retVal =  mi.proceed();
 * 		5)、拦截器链的触发过程;
 * 			1)、如果没有拦截器执行执行目标方法,或者拦截器的索引和拦截器数组-1大小一样(指定到了最后一个拦截器)执行目标方法;
 * 			2)、链式获取每一个拦截器,拦截器执行invoke方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行;
 * 				拦截器链的机制,保证通知方法与目标方法的执行顺序;
 *
 * 	总结:
 * 		1)、  @EnableAspectJAutoProxy 开启AOP功能
 * 		2)、 @EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator
 * 		3)、AnnotationAwareAspectJAutoProxyCreator是一个后置处理器;
 * 		4)、容器的创建流程:
 * 			1)、registerBeanPostProcessors()注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator对象
 * 			2)、finishBeanFactoryInitialization()初始化剩下的单实例bean
 * 				1)、创建业务逻辑组件和切面组件
 * 				2)、AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程
 * 				3)、组件创建完之后,判断组件是否需要增强
 * 					是:切面的通知方法,包装成增强器(Advisor);给业务逻辑组件创建一个代理对象(cglib);
 * 		5)、执行目标方法:
 * 			1)、代理对象执行目标方法
 * 			2)、CglibAopProxy.intercept();
 * 				1)、得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor)
 * 				2)、利用拦截器的链式机制,依次进入每一个拦截器进行执行;
 * 				3)、效果:
 * 					正常执行:前置通知-》目标方法-》后置通知-》返回通知
 * 					出现异常:前置通知-》目标方法-》后置通知-》异常通知
*/

声明式事务

环境搭建
  • 依赖
<dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.44</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
    </dependencies>
  • 配置数据源,JDBCTemplate
@ComponentScan("tx")
@Configuration
public class MyConfigTx {

    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser("root");
        dataSource.setPassword("root");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        return jdbcTemplate;
    }

}
  • 开启基于注解的事务管理功能
@EnableTransactionManagement
  • 注册配置事务管理器
@Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值