Ari的小跟班

  :: :: 博问 :: 闪存 :: :: :: :: 管理 ::

本笔记参考尚硅谷Spring框架视频教程(spring5源码级讲解)_哔哩哔哩_bilibili,相关资料链接:链接: https://pan.baidu.com/s/1BPdI_vDWW2M-1A0okF3Pww 提取码: 2333 or DS418 - Synology DiskStation (quickconnect.cn)

Spring概念

基本概念

1.Spring 是轻量级(体积小,jar包少)的开源的 JavaEE 框架

2.Spring 可以解决企业应用开发的复杂性

3.Spring 有两个核心部分:IOC 和 Aop
(1)IOC:控制反转,把创建对象过程交给 Spring 进行管理
(2)Aop:面向切面,不修改源代码进行功能增强

4.Spring 特点
(1)方便解耦,简化开发 (2)Aop 编程支持 (3)方便程序测试 (4)方便和其他框架进行整合 (5)方便进行事务操作 (6)降低 API 开发难度

入门案例

​ 写一个入门案例分为几个步骤:①引入jar包②写一个即将交个Spring的IOC容器托管的bean类③编写Spring的xml配置文件④测试由Spring IOC容器来创建对象而非自己new一个对象

一、引入jar包

​ 使用资料里的spring所需jar包-iod基本包,这里面包括了四个Spring的核心jar包以及一个commons-logging的日志包。

​ Spring5的组件图中,Core Container是我们现在比较需要的。

二、编写User类,类中只有一个add方法

package com.atguigu.spring5;
public class User {
    public void add(){
        System.out.println("add方法....");
    }
}

三、编写Spring5的xml配置文件,在src下创建一个Spring_configuration.xml文件,目前文件名可以随意,因为我们下面是通过按照文件名来读取配置文件的,不是spring自动读取

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置 User 对象创建-->
    <bean id="user" class="com.atguigu.spring5.User"></bean>
</beans>

四、测试由Spring IOC容器来创建对象而非自己new一个对象,在test包下编写test方法,使用@Test注释(相关的jar包需要自己导入一下,导入junit包)

@Test
public void testAdd(){
    //1.加载Spring配置文件,通过ApplicationContext来加载,在src包下可以直接写文件名
    //如果配置的xml在其他地方,则可以通过FileSystemXmlApplicationContext()来获取配置文件
    ApplicationContext context =
        new ClassPathXmlApplicationContext("Spring_configuration.xml");
    //2.获取配置创建的对象
    User user = context.getBean("user", User.class);
    System.out.println(user);
    user.add();
}

打印结果:

​ 可以看到,我们没有在任何地方new一个对象出来,但还是成功调用了add方法,这是因为由SpringIOC容器帮我们实现了。


​ 那我自己有个问题,如果User需要构造函数初始化,那么SpringIOC容器会怎么办呢?比如User类中有name属性,我可以get这个属性,但是并不是由我自己初始化,那么这个name属性的值为多少呢?

​ 下面我进行测试:

​ 1.若我把User类改造成如下,有构造函数,但是没有默认的构造函数

public class User {
    private String name;
    public void add(){
        System.out.println("add方法....");
    }
    public User(String name) {this.name = name;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
}

​ 则Spring会在ApplicationContext context =new ClassPathXmlApplicationContext("Spring_configuration.xml");这个步骤报错,原因就是没有默认的构造函数,Error creating bean with name 'user' defined in class path resource [Spring_configuration.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.atguigu.spring5.User]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.atguigu.spring5.User.()

​ 2.我把默认的构造函数加上,再进行测试,发现一切正常,只不过get的name值为null。而且可以观察得出,这与我第一次运行时SpringIOC容器创建的User类后面的@的数字是一致的。

​ 3.当我不在User类里定义任何构造方法时,运行也正常。

​ 4.我在User类中定义无参的构造方法,在构造方法里把name赋值为joker,这个时候运行测试时,得到的name就为joker了。


那么我自己回答我刚刚提出的问题:

​ SpringIOC会调用默认的构造方法来初始化对象,但是由java本身的特性所致,如果我在User类里只定义了显式的构造方法,那么默认的构造方法就会失效,这个时候运行就会报错,如果一个构造方法我也没有给的话,java会默认给一个无参的构造方法。IOC容器就会调用无参的构造方法来初始化对象,如果自己给了无参的构造方法,那么IOC就会调用自己给的无参构造方法。

IOC容器

概念

一、什么是IOC?

​ 答:(1)控制反转,把对象创建和对象之间的调用过程,交给 Spring进行管理。这里的调用过程可以举个简单的例子:

​ 比如User类里需要用到Person类里的方法,不使用Spring时,一般要在User类里new一个对象出来,才能调用Person类的方法,如果交给了IOC容器来管理的话,就不用这么麻烦了,全部由Spring来控制。

(2)使用 IOC目的:为了耦合度降低。

我们入门的案例就是使用了IOC,从而使得我们没有new任何一个对象也能成功调用方法。

二、底层原理

​ 1.xml解析:xml解析从而获得class属性值,就是class=后面的字符串,接着使用反射创建对象之后再强转返回即可。这有个好处,就是我们要修改加载的对应的类的话,只需要修改xml里面class后面的属性值即可,其他地方不用修改。

​ 2.工厂模式:可以参考设计模式里的,简而言之就是A想获得B类的对象,这个时候可以通过工厂类C来获得B对象,B对象的实例化全部由C类来实现。由第三方类来完成实例化的过程

​ 3.反射:xml的class属性值交给Class类,然后Class类通过forName方法来创建该类Class对象,然后再调用Class类中的newInstance()方法来实例化对象。

实现IOC的两个接口类

一、IOC思想基于 IOC容器完成,IOC容器底层就是对象工厂,而Spring 提供 IOC 容器实现两种方式:(两个接口)

1.BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用。( 加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象)

2.ApplicationContext:BeanFactory 接口的子接口(所以入门案例中的ApplicationContext类换成BeanFactory也可以正常运行),提供更多更强大的功能,一般由开发人
员进行使用 ( 加载配置文件时候就会把在配置文件对象进行创建)。

​ 而ApplicationContext有两个实现类,入门案例中使用的实现类ClassPathXmlApplication就是通过src文件夹下的xml文件名读取配置的类,还有FileSystemXmlApplicationContext通过系统绝对路径来读取xml配置文件。

Bean管理

一、何为Bean?

​ Bean其实就是要交给Spring托管的类的一种统称。

二、具体什么是Bean管理

​ Bean管理主要包括两个事情:Spring创建对象以及创建对象后由Spring对创建的对象注入属性。目前Bean管理操作有两种方式,分别是基于xml配置文件实现的方式基于注解的方式

三、DI依赖注入本质上其实就是给这些Bean类注入属性的意思

基于xml配置文件的管理方式

​ 注意:xml注入是通过set方法来实现的!!

创建对象

​ xml中一行就可以创建一个对象。

<bean id="user" class="com.atguigu.spring5.User"></bean>

​ 1.这其中id表示创建的这个对象的标识符,可以是任意的,与类名可以毫无关系,之后要拿到这个对象就需要把这个id值传给ApplicationContext类的getBean方法,从而得到已经实例化好的对象。

​ 2.class属性是类的全路径,用于支出创建对象的这个类具体是哪个类。

​ 3.创建对象时,默认也是执行无参数构造方法完成对象创建。若没有默认的无参构造方法调用,则会报错(这种情况常出现于类中显式地定义了一个有参构造而使得默认的无参构造失效时)。

注入属性

​ 对象中有着一个一个的属性,那么由Spring的IOC容器托管的这些对象的属性值的赋值情况是怎样的呢?

如果我们没有注入属性,默认使用的无参构造,那么属性值就是null,如果我们注入了属性,那就是我们注入的属性值,那么注入属性有两种方式

使用set方法

​ 比如入门案例中的User类,有个类型为String的name属性值,并且设置set方法,那么我在xml文件里可以通过这种方式进行属性注入:

​ 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置 User 对象创建-->
    <bean id="user" class="com.atguigu.spring5.User">
        <property name="user_name" value="Lucy"/>
    </bean>
    <bean id="user2" class="com.atguigu.spring5.User">
        <property name="user_name" value="Joker"/>
    </bean>
</beans>

​ User类

package com.atguigu.spring5;
public class User {
    private String user_name;
    public void add(){
        System.out.println("add方法....");
    }
    public User() {}
    public User(String user_name) {this.user_name=user_name;}
    public String getUser_name() {return user_name;}
    public void setUser_name(String user_name) {this.user_name = user_name;}
}

​ 可以看到我在xml里,让IOC容器托管了两个User对象,不过name不一样,我们编写测试类来进行测试:

@Test
public void testAdd(){
    //1.加载Spring配置文件,通过ApplicationContext来加载,在src包下可以直接写文件名
    //如果配置的xml在其他地方,则可以通过FileSystemXmlApplicationContext()来获取配置文件
    ApplicationContext context =
        new ClassPathXmlApplicationContext("Spring_configuration.xml");
    //2.获取配置创建的对象
    User user1 = context.getBean("user", User.class);
    System.out.println(user1+",这个用户的名字为"+user1.getUser_name());
    User user2 = context.getBean("user2", User.class);
    System.out.println(user2+",这个用户的名字为"+user2.getUser_name());
}

使用有参构造

​ 可以看到我的User类中写了一个有参构造,我们也可以使用有参构造来实现DI。

    <bean id="user" class="com.atguigu.spring5.User">
        <constructor-arg name="user_name" value="Lucy"/>
    </bean>
    <bean id="user2" class="com.atguigu.spring5.User">
        <constructor-arg name="user_name" value="Joker"/>
    </bean>

注入其他属性

​ 刚刚我们给类中的属性注入的都是String类型,那如果我们想注入外部bean,和带特殊符号的String以及集合,该怎么办呢?

字面量

1.注入null

<bean id="user" class="com.atguigu.spring5.User">
    <property name="user_name" value="Lucy">
    	<null/>
    </property>
</bean>

2.当注入的String有特殊符号,比如尖括号<>这个时候可以使用一下方式来处理

<bean id="user" class="com.atguigu.spring5.User">
    <property name="user_name" value="$lt;Lucy$gt;"/>
    <!--就等于给user_name赋值<Lucy>-->
</bean>

​ 如果不想转义,可以将带特殊符号的内容写到CDATA中:

<bean id="user" class="com.atguigu.spring5.User">
    <constructor-arg name="user_name">
        <value><![CDATA[<Lucy>]]></value>
    </constructor-arg>
</bean>

注入外部bean

​ 比如现在有一个给User类加钱的类AddMoney,AddMoney类中聚合了User类,属性名为addmoney_user,这个时候User类就是AddMoney类的属性的类,这个时候注入时xml需要这么写:

public class AddMoney {
    private User addmoney_user;//AddMoney类
    public User getAddmoney_user() {return addmoney_user;}
    public void setAddmoney_user(User addmoney_user) {this.addmoney_user = addmoney_user;}
    public void add_money(){
        System.out.println("我是AddMoney类,我要给名为"+ addmoney_user.getUser_name()+"的用户加钱");
    }

​ 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.atguigu.spring5.User">
        <constructor-arg name="user_name">
            <value><![CDATA[<Lucy>]]></value>
        </constructor-arg>
    </bean>
    <bean id="addmoney1" class="com.atguigu.spring5.AddMoney">
        <!--注入User对象 
        name 属性:类里面属性名称 
        ref 属性:创建 userDao 对象 bean 标签 id 值 -->
        <property name="addmoney_user" ref="user"/>
    </bean>
</beans>

测试结果如下:

​ 那如果我不想额外创建标签,想现创建一个User对象的话可以这么写:

<bean id="addmoney1" class="com.atguigu.spring5.AddMoney">
    <property name="addmoney_user" >
        <!--  <bean id="user_in_addmoney1" class="com.atguigu.spring5.User">  -->
        <bean class="com.atguigu.spring5.User">
            <property name="user_name" value="joker"/>
        </bean>
    </property>
</bean>

​ 里面的property创建的User类,我给它id属性是,idea提示我id是多余的,果然不加id也可以运行,那么我加了id之后,我可以通过ApplicationContext来获取到这个addmoney1对象里的User对象吗?

​ 答案是不能,在bean1中被初始化的bean2无法通过id找到,所以写了id和没写没有区别,其实我们可以通过bean1来找到bean2。

注入数组或集合

一、Stu类中包含了数组、集合、map、set类型,它们的Bean赋值的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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置 User 对象创建-->
    <bean id="stu" class="com.atguigu.spring5.bean.Stu">
        <!--数组类型属性注入-->
        <property name="courses">
            <array>
                <value>java 课程</value>
                <value>数据库课程</value>
            </array>
        </property>
        <!--list 类型属性注入-->
        <property name="list">
            <list>
                <value>张三</value>
                <value>小三</value>
            </list>
        </property>
        <!--map 类型属性注入-->
        <property name="maps">
            <map>
                <entry key="JAVA" value="java"/>
                <entry key="PHP" value="php"/>
            </map>
        </property>
        <!--set 类型属性注入-->
        <property name="sets">
            <set>
                <value>MySQL</value>
                <value>Redis</value>
            </set>
        </property>
    </bean>
</beans>

​ Stu类的代码:

public class Stu {
    private String[] courses;
    private List<String> list;
    private Map<String,String> maps;
    private Set<String> sets;
    public List<String> getList() {return list;}
    public void setList(List<String> list) {this.list = list;}
    public Map<String, String> getMaps() {return maps;}
    public void setMaps(Map<String, String> maps) {this.maps = maps;}
    public String[] getCourses() {return courses;}
    public void setCourses(String[] courses) {this.courses = courses;}
    public Set<String> getSets() {return sets;}
    public void setSets(Set<String> sets) {this.sets = sets;}
}

二、集合或者数组中包含外部bean

<bean id="course1" class="com.atguigu.spring5.collectiontype.Course"> 
    <property name="cname" value="Spring5 框架"></property> 
</bean> 
<bean id="course2" class="com.atguigu.spring5.collectiontype.Course"> 
    <property name="cname" value="MyBatis 框架"></property> 
</bean> 
<!--注入 list 集合类型,值是对象--> 
<property name="courseList"> 
    <list> 
        <ref bean="course1"></ref> 
        <ref bean="course2"></ref> 
    </list> 
</property>

xml注入好麻烦啊!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

FactoryBean工厂Bean

​ Spring有两种类型的bean,一种是普通bean,另外一种是工厂bean(FactoryBean)。

普通Bean

​ 在配置文件中定义 bean 类型就是返回类型

工厂Bean

​ 在配置文件定义 bean 类型可以和返回类型不一样,具体演示如下:

​ 第一步,创建类,让这个类作为工厂 bean,实现接口 FactoryBean,实现接口里面的方法,在实现的方法中定义返回的 bean 类型。这里我定义返回的类型为Dept类。

public class MyBean implements FactoryBean<Dept> {
    //定义返回 bean,这时返回的就是Dept类
    @Override
    public Dept getObject() throws Exception {
        Dept dept = new Dept();
        dept.setDname("技术部");
        return dept;
    }
    @Override
    public Class<?> getObjectType() {return null;}
    @Override
    public boolean isSingleton() {return false;}
}

​ 第二步,在xml配置文件里注册好

<bean id="mybean" class="com.atguigu.spring5.factorybean.MyBean"/>

​ 第三步,测试

@Test
public void testFacotryBean(){
    ApplicationContext context =
        new ClassPathXmlApplicationContext("Spring_configuration.xml");
    Dept mybean = context.getBean("mybean", Dept.class);
    System.out.println(mybean+"  "+mybean.getDname());
}

​ 测试结果:

Bean作用域

一、单实例与多实例

​ Spring创建对象时,我们可以设置SpringIOC容器创建的是单实例还是多实例,这就是Bean的作用域,不过默认是单实例的。

​ 何为单实例呢?就是我们在使用context.getBean方法获取xml配置的对象时,通过多次调用该方法得到的对象是否是一致的,典型的单实例如下:

二、设置多实例

​ 1.在 spring 配置文件 bean 标签里面有属性(scope)用于设置单实例还是多实例

​ 2.scope的属性值为singleton(默认的)时为单实例,为prototype时,为多实例对象。

(1)设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象 。

(2) 设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建 对象,而是在调用getBean 方法时候创建多实例对象。

​ 为什么?因为Spring并不知道你要创建多少个实例对象,所以无法提前为你创建,只有在你实际使用的过程中,Spring才能知道究竟要创建多少个实例对象。

Bean的生命周期

​ 没加后置处理器就是五步,加了就是七步。

一、创建Bean的过程

​ 1.通过构造器创建 bean 实例(无参数构造)

​ 2.为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)

(后置处理器1)

​ 3.调用 bean 的初始化的方法需要进行配置初始化的方法

(后置处理器2)

​ 4.bean 可以使用了(对象获取到了)
​ 5.当容器关闭时候,调用 bean 的销毁的方法需要进行配置销毁的方法

二、演示bean的生命周期

1.Orders类作为Bean

public class Orders {
    //无参数构造
    public Orders() {System.out.println("第一步 执行无参数构造创建 bean 实例");}
    private String oname;
    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("第二步 调用 set 方法设置属性值");
    }
    //创建执行的初始化的方法
    public void initMethod() {System.out.println("第三步 执行初始化的方法");}
    //创建执行的销毁的方法
    public void destroyMethod() {System.out.println("第五步 执行销毁的方法");}
}

2.在xml文件中配置

    <bean id="orders" class="com.atguigu.spring5.lifecycle.Orders"
          init-method="initMethod" destroy-method="destroyMethod">
        <property name="oname" value="手机"/>
    </bean>

3.测试:

@Test
public void testLifeCycle(){
    ClassPathXmlApplicationContext context =
        new ClassPathXmlApplicationContext("Spring_configuration.xml");
    Orders orders = context.getBean("orders", Orders.class);
    System.out.println("第四步 获取bean对象实例");
    System.out.println(orders);
    //手动让bean实例销毁,close方法在ClassPathXmlApplicationContext中,
    // 所以需要用子接口
    context.close();
}

三、Bean的后置处理器

​ (1)通过构造器创建 bean 实例(无参数构造)
​ (2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
​ (3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization ,所有bean都是通过这个后置处理器(如果设置的话),并非对bean单独设置后置处理器
​ (4)调用 bean 的初始化的方法(需要进行配置初始化的方法)

​ (5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization
​ (6)bean 可以使用了(对象获取到了)
​ (7)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

注:后置处理器可以有多个,按照在xml配置的先后顺序依次执行

1.编写后置处理器:

public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化之前执行的方法");
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化之后执行的方法");
        return bean;
    }
}

2.在xml配置该类,告知Spring这个类的存在

<bean id="myBeanPost" class="com.atguigu.spring5.postProcess.MyBeanPost"/>

原来的代码不用动,运行进行测试:


突发奇想,如果xml里有两个实现了BeanPostProcessor接口的后置处理器,Spring会走哪个后置处理器呢?

​ 测试结果是按照在xml里配置的顺序依次执行。

自动装配(类比@autowired)

一、概念

​ 自动装配指的是根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入。

二、byName注入

1.Dept类

public class Dept {
    private String dept_name;
    public String getDept_name() {return dept_name;}
    public void setDept_name(String dept_name) {this.dept_name = dept_name;}
    @Override
    public String toString() {
        return "Dept{" +
                "dept_name='" + dept_name + '\'' +
                '}';
    }
}

2.Emp类

public class Emp {
    private String emp_name;
    private Dept dept_of_emp;
    public String getEmp_name() {return emp_name;}
    public void setEmp_name(String emp_name) {this.emp_name = emp_name;}
    public Dept getDept_of_emp() {return dept_of_emp;}
    public void setDept_of_emp(Dept dept_of_emp) {this.dept_of_emp = dept_of_emp;}

    @Override
    public String toString() {
        return "Emp{" +
                "emp_name='" + emp_name + '\'' +
                ", dept_of_emp=" + dept_of_emp +
                '}';
    }
}

手动装配写法:

<bean id="dept" class="com.atguigu.spring5.autowired.Dept">
    <property name="dept_name" value="财务部"/>
</bean>
<bean id="emp" class="com.atguigu.spring5.autowired.Emp" autowire="byName">
    <property name="dept_of_emp" ref="dept"/>
    <property name="emp_name" value="joker"/>
</bean>

自动装配方法:

<bean id="dept_of_emp" class="com.atguigu.spring5.autowired.Dept">
    <property name="dept_name" value="财务部"/>
</bean>
<bean id="emp" class="com.atguigu.spring5.autowired.Emp" autowire="byName">
<!--    <property name="dept_of_emp" ref="dept"/>-->
    <property name="emp_name" value="joker"/>
</bean>

三、byType自动注入

​ 这种方式不要求Bean的id与带装配的类的属性名一致,只要类型一致就会自动装配上:

​ 但是当IOC有多个属于待装配类的Bean时,就会报错,因为SpringIOC无法判断将哪个bean装配进去:

基于注解管理Bean

​ 注解是代码特殊标记,格式:@注解名称(属性名称=属性值, 属性名称=属性值..)。使用注解,注解可以作用在类上面,方法上面,属性上面,使用注解的目的是简化xml配置。

四个创建Bean实例的注解

1.@Component:表示普通的组件,不过下面三个注解都是在Component注解的基础上实现的,为了标记在不同的地方用,所以名字也不一样。注意只能注解在类上

2.@Service:用在业务逻辑层

3.@Controller:用在Web层

4.@Repository:dao层,持久层。

5.@Bean也是类似的效果,不过@Bean是作用在方法上的,声明该方法返回的实例是受 Spring 管理的 Bean。@Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑。

​ @Bean相对来说就更加灵活了,它可以独立加在方法上,按需注册到spring容器,而且如果你要用到第三方类库里面某个方法的时候,你就只能用@Bean把这个方法注册到spring容器,因为用@Component你需要配置组件扫描到这个第三方类路径而且还要在别人源代码加上这个注解,很明显是不现实的。


这里以使用JdbcTemplate的项目为例,说明一下@Bean的特点:

@Component
@Configuration
@PropertySource("classpath:database.properties")
@ComponentScan(basePackages = "com.atguigu")
public class SpringConfigAll {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    // 产生dataSource的Bean对象
    @Bean
    public DruidDataSource getdruidDataSource() {
        // 设置数据源对象
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }
    // 创建 JdbcTemplate 对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        // 注入 dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
}

​ 我们在使用的时候直接@Autowired Jdbctemplate即可,但是我们可以发现在SpringConfigAll类中的getJdbcTemplate需要一个参数,这个参数其实就是上面@Bean的getdruidDataSource返回的druidDataSource,Spring自动装配进去了,如果我们有多个@Bean了的方法返回的都是DruidDataSource类型的对象,那么Spring就无法装配了。

使用

第一步:引入依赖

​ 除了之前的依赖外,还需要一个额外的依赖spring-aop-5.2.6.RELEASE.jar。

第二步:开启组件扫描
还是使用配置文件

​ 需要在xml文件中引入一个命名空间,引入之后使用context标签开启组件扫描。

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

​ 可以依次引入多个包,也可以直接写最上层的包。

扫描的一些细节

组件扫描里的一些细节问题:

​ 可以配置哪些类和哪些包不用扫描

1.只扫描带指定注解的类:比如只扫描带@Controller注解的类

​ (1)use-default-filters="false" 表示现在不使用默认 filter,自己配置 filter

​ (2)context:include-filter ,设置扫描哪些内容

 <context:component-scan base-package="com.atguigu" use-default-
filters="false"> 
    <context:include-filter type="annotation"expression="org.springframework.stereotype.Controller"/> 
</context:component-scan> 

2.不扫描带@Controller注解的类,其他的都扫描

​ (1)context:exclude-filter: 设置哪些内容不进行扫描

<context:component-scan base-package="com.atguigu"> 
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 
</context:component-scan

完全摆脱xml文件

​ 我们可以创建一个配置类,完全替代xml配置文件,即一个xml文件都不用写,可以使用@Configuration注释来创建配置类。

1.配置类,配置类的名称可以是任意的。

@Configuration
@ComponentScan(basePackages = {"com.atguigu"})
public class SpringConfig {
}

2.测试类中的写法就要有点变化了,通过AnnotationConfigApplicationContext来获取context对象。

@Test
public void testAnnotation2(){
    ApplicationContext context
        = new AnnotationConfigApplicationContext(SpringConfig.class);
    UserService userService = context.getBean("userService", UserService.class);
    System.out.println(userService);
    userService.add();
}
第三步:创建类后在类上面添加创建对象注释

​ 比如:我有一个UserService类,在类上写上注释@Component(写四个中的任意一个都行)。

@Component(value = "userService")  //<bean id="userService" class=".."/>
public class UserService {
    public void add() {System.out.println("service add.......");}
}

​ 这就相当于原来传统的方式,在xml文件里写:

<bean id="userService" class="com.atguigu.spring5.User"/>

​ 不过@Component里的value值可以不写,默认是类名,首字母小写,那么UserService类的默认value值就是userService。然后通过ApplicationContext可以拿到创建的这个对象。

第四步:基于注解实现属性注入
一、@Autowired

​ 根据属性类型进行自动装配。

现在有这么几个类:

1.UserDao接口

public interface UserDao {
    public void add();
}

2.UserDaoImpl对UserDao接口的实现类,使用@Repository将类给SpringIOC托管

@Repository
public class UserDaoImpl implements UserDao{
    @Override
    public void add() {System.out.println("dao add....");}
}

3.在UserService类中直接使用注解

@Service  //注意这里我的注解换成了Service
public class UserService {
    @Autowired
    private UserDao userDao;
    public void add() {
        System.out.println("service add.......");
        userDao.add();
    }
}

二、@Qualifier

​ 根据名称进行注入,需要根据@Autowired一起使用。因为如果接口有多个实现类的话,Spring就会不知道根据哪个实现来来注入了。

1.UserService类

@Service  //注意这里我的注解换成了Service
public class UserService {
    @Autowired
    @Qualifier(value = "userdaoImpl1")
    private UserDao userDao;
    public void add() {
        System.out.println("service add.......");
        userDao.add();
    }
}

2.创建两个实现类

(1)Impl1

@Repository(value = "userdaoImpl1")
public class UserDaoImpl implements UserDao{
    @Override
    public void add() {System.out.println("userdaoImpl1 dao add....");}
}

(2)Impl2

@Repository(value = "userdaoImpl2")
public class UserDaoImpl2 implements UserDao{
    @Override
    public void add() {System.out.println("userImpl2 add....");}
}

测试结果:

三、@Resource

​ 可以根据属性自动装配也可以根据名称自动装配。

​ 直接使用@Resource就是根据类型注入,如果使用@Resource(name = "userdaoImpl1")就是根据名字注入。

​ 不过要注意的是,之前使用的@Autowired和@Qualifier都是org.springframework.beans.factory.annotation包下的,而Resource则是javax.annotation下的,即Resource是java拓展包里的不是spring包中的。

​ 故Spring更推荐我们使用@Autowired和@Qualifier

四、@Value注入普通类型属性

​ 比如现在UserService类里有了一个name属性,那么我们想给这个name属性注入值就可以通过@Value的方式,使用xml的方式的话还需要在xml里用标签来实现:

@Service  //注意这里我的注解换成了Service
public class UserService {
    @Value(value = "name_of_userservice")
    private String name;
    @Autowired
    @Qualifier(value = "userdaoImpl1")
    private UserDao userDao;
    public void add() {
        System.out.println("service add.......,它的name是"+this.name);
        userDao.add();
    }
}

AOP概念

一、概念

​ AOP表示面向切面编程,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

二、举例说明AOP

​ 通俗描述就是不通过修改源代码方式,在主干功能里面添加新功能 。

​ 比如已经写好了登录流程,想加一个判断权限的功能,这个时候就可以把判断权限的这个功能单独写一个模块出来,之后通过配置的方式加进去,原有的流程代码可以不改变。

三、底层原理

​ AOP底层使用动态代理,有两种情况的动态代理,若有接口的情况,使用JDK动态代理,如果没有接口的情况,则可以使用CGLIB动态代理

1.JDK动态代理

(1)示意图:

​ 假如想在UserDao的实现类UserDaoImpl中的登录过程中添加一个权限判断的功能,则使用JDK动态代理来创建一个UserDao接口的代理对象

(2)代码实现:

​ 主要使用java.lang.reflect.Proxy中的newProxyInstance方法。

​ 第一参数,类加载器 。
​ 第二参数,增强方法所在的类,这个类实现的接口,支持多个接口 。
​ 第三参数,实现这个接口 InvocationHandler,创建代理对象,写增强的部分 。

①创建接口定义方法
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) {
        System.out.println("add方法执行了");
        return a+b;
    }
    @Override
    public String update(String id) {
        System.out.println("update方法执行了");
        return id;
    }
}
③使用Proxy类创建接口代理对象
public class JDKProxy {
    public static void main(String[] args) {
        //创建接口实现类代理对象
        Class[] interfaces = {UserDao.class};
        UserDaoImpl userDao = new UserDaoImpl();
        UserDaoImpl userDao2 = new UserDaoImpl();
        UserDao dao_enhanced = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        System.out.println("增强过后的add方法的result:" + dao_enhanced.add(1,2));
        System.out.println("未增强的add方法的result:" + userDao2.add(1,2));
        System.out.println("==================================================");
        System.out.println("增强过后的update方法的result:" + dao_enhanced.update("joker"));
        System.out.println("未增强过后的update方法的result:" + userDao2.update("joker"));
        System.out.println(dao_enhanced.equals("fds"));//这证明了UserDao类的父类Object中的方法不会走增强
    }
}
//创建代理对象代码
class UserDaoProxy implements InvocationHandler {
    //1 把创建的是谁的代理对象,把谁传递过来
    //有参数构造传递
    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));
        //可以根据方法名来做响应的增强,如果调用的是add方法就在结果上乘2,如果是update方法,就在String后面加上两个感叹号
        //被增强的方法执行
        Object res = method.invoke(obj, args);
        if(method.getName().equals("add")){
            res = (int)res * 2;
        }else if(method.getName().equals("update")){
            res = (String)res + "!!";
        }
        return res;
    }
}

​ 核心:在invoke中可以根据传来的method的name来判断现在执行的方法是哪个方法,从而对方法做出相应的增强效果,比如我想给add方法添加增强效果为在结果上乘以2,我想给update方法添加的增强效果为,返回的String值后面加上两个感叹号。

​ 测试结果如下:

2.CGLIB代理

​ ...有待更新

四、AOP术语

1.连接点:可以被增强的方法被称为连接点

2.切入点:实际被增强的方法被称为切入点

3.通知(增强):

​ (1)实际增强的逻辑部分就称为通知,比如刚刚add的增强方法里把结果res乘以2再返回的逻辑部分。

​ (2)通知有多种类型:前置通知(切入点之前执行的)、后置通知(切入点之后执行的)、环绕通知(在切入点的前后都会执行)、异常通知(发生异常时候的通知)、最终通知(finally,无论是否发生异常都会执行的)。

4.切面

​ 这是一个动作,把通知应用到切入点的过程就被称为切面。

五、在Spring中使用AOP

​ Spring框架一般基于AspectJ实现AOP操作。Spring会根据需要代理的类是否有实现接口而动态选择JDK代理或CGlib代理。

概述

1.AspectJ不是Spring的组成部分,是一个独立的AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作。

2.AspectJ有两种实现方式,基于xml配置文件实现和基于注解方式实现

使用

3.使用,仅说明完全基于注解的方式

引入依赖

(1)先引入依赖,spring-aspects-5.2.6.RELEASE.jar和aspectj所需要的三个依赖:com.springsource.net.sf.cglib-2.2.0.jar,com.springsource.org.aopalliance-1.0.0.jar,com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar。

切入点表达式

(2)切入点表达式

​ ①切入点表达式作用:知道对哪个类里面的哪个方法进行增强

​ ②语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]( [参数列表] )

​ 举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
execution(* com.atguigu.dao.BookDao.add(..))

​ 举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
execution(* com.atguigu.dao.BookDao.* (..))

​ 举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强

execution(* com.atguigu.dao.. (..))

创建配置类和待增强类与增强类

(3)创建配置类,把annotation包里的配置类注释掉,在aspectJ包中创建配置类

@Configuration
@ComponentScan(basePackages = {"com.atguigu"})
@EnableAspectJAutoProxy(proxyTargetClass = true)//默认是false
public class SpringConfig_AspectJ {
}

(4)编写待增强的类UserDao和它的实现类,功能结构都和之间使用JDK代理实现AOP的类一致。

①UserDao类

public interface UserDao {
    public int add(int a,int b);
    public String update(String id);
}

②UserDaoImpl类

@Component //被增强的类
public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        System.out.println("add方法执行了");
        return a+b;
    }
    @Override
    public String update(String id) {
        System.out.println("update方法执行了");
        return id;
    }
}

(5)编写增强类

@Component  //增强类
@Aspect //生成代理对象
public class UserDaoProxy {
    //为了省略一点,可以先定义用的比较多的切入点
    @Pointcut("execution(* com.atguigu.spring5.aspectJ.UserDaoImpl.add(..))")
    public void point(){}
    //前置通知
    @Before("point()")
    public void before(){System.out.println("before.....");}
    //后置通知
    @After("point()")
    public void after(){System.out.println("after......");}
    // 环绕通知,ProceedingJoinPoint只能给@Around用的。
    @Around("point()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前");
        Object[] args = pjp.getArgs();//获取参数
        System.out.println(pjp.getKind()+"       "+ Arrays.toString(args));
        Object result = pjp.proceed();
        System.out.println("环绕后,进行结果+2的操作");
        return result;
    }
    @Around("execution(* com.atguigu.spring5.aspectJ.UserDaoImpl.update(..))")//切入点为update
    public Object around_for_update(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("update里环绕前");
        Object result = pjp.proceed();
        System.out.println("update环绕后,进行结果加感叹号的操作的操作");
        return (String)result+"!!";
    }
    // 后置 发生异常时不会执行
    @AfterReturning("point()")
    public void returning() {System.out.println("After returning 后置");}
    // 发生异常
    @AfterThrowing("point()")
    public void throwing() {System.out.println("发生异常了");}

}

(6)编写测试类

@Test
public void testAspectJ(){
    ApplicationContext context
        = new AnnotationConfigApplicationContext(SpringConfig_AspectJ.class);
    UserDaoImpl userDaoImpl = context.getBean("userDaoImpl", UserDaoImpl.class);
    System.out.println(userDaoImpl.add(1,2));
    System.out.println(userDaoImpl.update("joker"));
}

结果如下:

​ 注意:只有@Around通知才可以获得方法获得的参数以及返回值,可以采取一些操作,所以环绕通知是比较重要的也是用的比较多的!

4.若有多个增强类,可以按照@Order(数字类型值)里面的数字来决定执行的优先度,数字越小表示优先级越高。

Spring中的事务管理

一、事务的四大特性

1.原子性

事务的原子性是指事务必须是一个原子的操作序列单元。事务中包含的各项操作在一次执行过程中,只允许出现两种状态之一,要么都成功,要么都失败
任何一项操作都会导致整个事务的失败,同时其它已经被执行的操作都将被撤销并回滚,只有所有的操作全部成功,整个事务才算是成功完成。

2.一致性

事务的一致性是指事务在执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态
比如:张三给李四转钱,不可能张三被扣了钱,李四没有加钱。

3.隔离性

事务的隔离性是指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。也就是说,不同事物并非操作相同数据时,每个事务都有完整的数据空间。
一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务是不能互相干扰的。

4.持久性

事务的持久性是指事务一旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态。

二、搭建事务操作环境

​ 1.数据库准备,准备一个表t_account,里面的记录和字段如下图所示:

​ 2.创建service,搭建dao,完成对象创建和注入

service 注入 dao,在 dao 注入 JdbcTemplate,在 JdbcTemplate 注入 DataSource,并且写好方法。

public interface UserDao {
    public void addMoney();
    public void reduceMoney();
}

它的实现类:

@Repository
public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public void reduceMoney() {//lucy 转账 100 给 mary
        String sql = "update t_account set money=money-? where username=?";
        jdbcTemplate.update(sql, 100, "lucy");
    }
    @Override
    public void addMoney() {
        String sql = "update t_account set money=money+? where username=?";
        jdbcTemplate.update(sql, 100, "mary");
    }
}

service类:

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    public void accountMoney() {//转账的方法
        userDao.reduceMoney();//lucy 少 100

        userDao.addMoney();//mary 多 100
    }
}

​ 这个转账流程,如果正常执行是没有问题的,如果代码执行中出现异常,就有问题了。比如,在userDao.reduceMoney();//lucy 少 100下面加一行"int i = 10/0",那么lucy钱是少了,但是mary钱没有多。

三、事务操作

1.Spring事务介绍:

(1)事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)

(2)有两种方式实现事务管理:编程式事务管理(比如通过try catch来写,会比较臃肿)和声明式事务管理(使用)

(3)基于注解实现、基于xml配置实现

(4)在 Spring 进行声明式事务管理,底层使用 AOP 原理

(5)Spring 事务管理 API :

​ ①提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类

2.使用注解方式配置事务操作

(1)引入DataSourceTransactionManager事务管理类,注入DataSource。注意要加@EnableTransactionManagement开启事务

@Component
@Configuration
@PropertySource("classpath:database.properties")
@EnableTransactionManagement
@ComponentScan(basePackages = "com.atguigu")
public class SpringConfigAll {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    // 产生dataSource的Bean对象
    @Bean
    public DruidDataSource getdruidDataSource() {
        // 设置数据源对象
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(driver);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return druidDataSource;
    }
    // 创建 JdbcTemplate 对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {//相当于ref
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //到 ioc 容器中根据类型找到 dataSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    //创建事务管理器
    @Bean
    public DataSourceTransactionManager
    getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

(2)在service类上面(或者 service 类里面方法上面)添加事务注解

​ (1)@Transactional,这个注解添加到类上面,也可以添加方法上面。

​ (2)如果把这个注解添加类上面,这个类里面所有的方法都添加事务

​ (3)如果把这个注解添加方法上面,为这个方法添加事务 。

@Service 
@Transactional 
public class UserService {

​ 添加@Transactional 后,如果中间发生了异常,就会回滚。

四、事务参数配置

​ 在 service 类上面添加注解@Transactional,在这个注解里面可以配置事务相关参数

其中各个参数的解释如下:

1.propagation

表示事务传播行为,意思就是比如一个事务方法(在刚刚的例子中,事务方法就是对数据库进行变化的方法:增、删等等)调用另一个事务方法,Spring如何处理,比如有一个事务方法加了@Transactional注解,另一个没加,或者两个都加了。

如图所示,add方法要调用update方法:

​ 然后propagation又7种取值,各个取值的意思如下所示:

​ 用人话说就是:

​ (1)REQUIRED(默认使用):如果add方法本身有事务,调用update方法之后,update使用当前add方法里面的事务;如果add方法本身没有事务,调用update方法之后,创建新事务。

​ (2)REQUIRED_NEW:使用add方法调用update方法,无论add是否有事务,都会创建新事务。

​ (3)其他的做了解

2.isolation

表示事务隔离级别

​ (1)事务中有一个特性是隔离性,多事务之间操作不会产生影响。若我们没有隔离性原则,那么就会产生三个问题,分别是脏读、不可重复度、幻(虚)读

脏读

​ ①脏读:一个未提交的事务读取到了另一个未提交的事务的数据。比如:比如事务A和事务B都对一个数值进行操作,假设这个数值原来是5000,事务A想把它改为100,事务B想把它改为60000,假设事务B先改了,改成了60000,这个时候事务A正好读取数值,读取到了60000,然后事务B内部发生了一些错误,导致了事务回滚,回滚后数值编程了原来的5000,这个时候事务A读取到的60000(由事务B带来的)与实际的数值5000就不符,这就是脏读。

不可重复读

​ ②不可重复读:即每次读取到的数据都不一样,还是刚刚的例子,事务A读取数值为5000出来之后,事务B马上获取数据之后修改数据为4000,并且提交,这个时候事务A还没提交,又读取到的数值就变成了4000了,简而言之:一个未提交的事务读取到了另一个已提交的事务的数据

幻(虚)读

​ ③幻(虚)读:幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行(比第一次读的多)。比如事务A执行三次select *操作,第一次执行的时候查出一条记录,这个时候事务B马上提交添加一条记录进去,事务A在执行第二次select *操作时,就读出了两条数据,这就是幻读。

​ (2)通过设置事务隔离级别,来解决三个读的问题:

在mysql中默认使用的是可重复读级别

3.timeout

表示超时时间,事务需要在一定时间内提交,若不提交则进行回滚,默认值是-1(就是不超时的意思),timeout是以秒为单位。

4.readOnly

是否只读,默认值是false,表示可以查询,也可以做添加修改删除操作,若设置为true了,就只能对数据库做查询操作了,不能做增删改操作了。

5.rollbackFor

​ 设置出现哪些异常进行实物回滚

6.noRollback

​ 设置出现哪些异常时不进行回滚

Spring5新特性

​ 我们现在用的是5.2.6,现在最新的5.3.19.

一、基于JAVA8

1.整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方法在代码库中删除 。

二、自带Log4j2日志

2.Spring 5.0 框架自带了通用的日志封装
Spring5 已经移除 Log4jConfigListener,官方建议使用 Log4j2 。Spring5 框架已经整合 Log4j2

(1)如何使用日志?

​ ①引入这四个包:

​ ②在src文件夹下创建log4j2.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
    <!--先定义所有的appender-->
    <appenders>
        <!--输出日志信息到控制台-->
        <console name="Console" target="SYSTEM_OUT">
            <!--控制日志输出的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </console>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

​ 如果把status改成DEBUG的话,会显示如下:

三、支持@Nullable注解

​ @Nullable 注解可以使用在方法上面表示方法返回可以为空。

​ 属性上面,属性值可以为空。

​ 参数上面,参数值可以为空 。

四、支持函数式风格、lambda表达式

@Test 
public void testGenericApplicationContext() { 
    //1 创建 GenericApplicationContext 对象 
    GenericApplicationContext context = new GenericApplicationContext(); 
    //2 调用 context 的方法对象注册 
    context.refresh(); 
    context.registerBean("user1",User.class,() -> new User()); //这里的第三个参数与ES6中的箭头函数类似
    //3 获取在 spring 注册的对象 
   // User user = (User)context.getBean("com.atguigu.spring5.test.User"); 
    User user = (User)context.getBean("user1"); 
    System.out.println(user); 
}

五、测试方面的改进:Spring支持整合JUNIT5

1.整合JUnit4

(1)引入Spring相关针对测试的依赖spring-test-5.2.6.RELEASE.jar

(2)创建测试类,使用注解方式完成

注意:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfigAll.class)//指定配置类orxml配置文件
public class JTest4 {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Test
    public void testjdbc(){System.out.println(jdbcTemplate);}//测试成功
}

​ 这种用法和之前写单元测试时的用法有一个重大不同就是,这种用法可以获得IOC容器里的Bean,并且可以实现自动装配,如果是刚刚的只有@Test注解,那么无法取到在SpringConfigAll里交给IOC容器的jdbctemplate对象。@RunWith(SpringJUnit4ClassRunner.class),让测试运行于Spring测试环境,这是关键!(@RunWith(SpringJUnit4ClassRunner.class)使用了Spring的SpringJUnit4ClassRunner,以便在测试开始的时候自动创建Spring的应用上下文。)

@Repository
public class Spring5BeanTest {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Test
    public void testJdbcTemplate(){System.out.println(jdbcTemplate);}
}

2.整合Junit5

有关junit5的更多使用可以参考这篇:(4条消息) JUnit5学习之一:基本操作_程序员欣宸的博客-CSDN博客_junit5

(1)引入JUnit5的jar包

(2)编写测试类,Junit5需要使用@ExtendWith(SpringExtension.class)来替代原来的@RunWith(SpringJUnit4ClassRunner.class)。其他效果一致

如果您想在测试中使用Spring测试框架功能(例如)@MockBean,则必须使用@ExtendWith(SpringExtension.class)。它取代了不推荐使用的JUnit4@RunWith(SpringJUnit4ClassRunner.class).

ExtendWith:这是用来取代旧版本中的RunWith注解,不过在SpringBoot环境如果没有特别要求无需额外配置,因为SpringBootTest中已经有了;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SpringConfigAll.class)//指定配置类orxml配置文件
public class JTest4 {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Test
    public void testjdbc(){System.out.println(jdbcTemplate);}
}

(3)可以使用复合注解@SpringJUnitConfig简化上面的两个注解:

@SpringJUnitConfig(classes = SpringConfigAll.class)
public class JTest4 {....

六、Spring WebFlux

可以参考这篇文章:WebFlux 教程 | 入门篇 - 异常教程 (exception.site)

​ Spring WebFlux是用于对标SpringMVC的,这里先省略...

posted on 2022-04-17 23:51  Ari的小跟班  阅读(88)  评论(0)    收藏  举报