Spring入门笔记

Spring学习笔记

Spring框架概述
  • Spring是轻量级 开源的 J2EE框架
  • Spring可以解决企业应用开发的复杂性
  • Spring有两个核心部分:IOCAOP

IOC:控制反转,把创建对象过程交给Spring进行管理

AOP:面向切面,不修改源代码进行功能增强

Spring特点
  • 方便解耦,简化开发

  • AOP编程支持

  • 方便程序的测试

  • 方便和其他框架进行整合

  • 方便进行事务操作

  • 降低API开发难度

    ……

入门案例

用配置的方式创建对象

  • 下载地址:https://repo.spring.io/release/org/springframework/spring/

  • 创建普通java项目,导入spring的基本jar包。另加一个(commons-logging-1.1.1.jar)

在这里插入图片描述

  • 创建Spring配置文件,在配置文件中配置需要创建的对象

    • 配置文件使用的是.xml文件。我们在src下创建一个随意名字的.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.company.User"></bean>
      </beans>
      

      <bean>标签专门用来做对象创建的事情。class属性是需要创建的对象的全类名

    • 创建一个Test类来测试这种创建对象的方式:

      public class TestSpring {
      
          @Test
          public void testAdd() {
              // 1、加载spring的配置文件
              ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
      
              // 2、获取配置中创建的对象
              User user = context.getBean("user", User.class);
      
              // 测试由Spring配置文件方式创建对象
              System.out.println(user);
              user.add();
          }
      }
      

      其中,getBean方法第一个参数是配置文件bean1.xml中<bean>标签的id值。

IOC容器
什么是IOC
  • 控制反转,把对象的创建 和 对象之间的调用过程 交给Spring进行管理
  • 使用IOC目的:为了降低耦合度
  • 入门案例就是IOC的一种实现
IOC底层原理
  • xml解析
  • 工厂模式
  • 反射
工厂模式

当需要在A类中调用B类的方法时,引入一个工厂类,让工厂类完成对象创建的工作在这里插入图片描述

IOC过程
  • xml配置文件,配置需要创建的对象

    <bean id="dao" class="com.company.User"></bean>
    
  • 有Service类和User类,创建工厂类

    public class UserFactory {
        public static User getUser() {
            String classValue = class属性值  // 通过xml解析得到bean标签中的class属性,即全类名
            Class aClass = Class.forName(classValue);  // 反射 得到类的字节码对象
            return (User) aClass.newInstance();  // 通过字节码对象创建这个类的实例对象,已弃用,可用aClass.getDeclaredConstructor().newInstance()替代
        }
    }
    
IOC(接口)
  • IOC思想基于IOC容器完成,IOC容器底层就是对象工厂
  • Spring提供两种方式实现IOC容器(两个接口)
    • BeanFactory:IOC容器基本实现方式,一般不供开发人员使用
      • 加载配置文件时并不会创建对象,在使用时才创建
    • ApplicationContext:BeanFactory的子接口,功能更强大。给我们开发人员使用
      • 加载配置文件后就已经创建了对象

ApplicationContext接口的实现类(Ctrl+H):

在这里插入图片描述

IOC操作Bean管理
什么是Bean管理?
  • Spring创建对象

  • Spring注入属性

Bean管理操作有两种方式
  • 基于xml配置文件方式实现

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

    <bean>标签的常用属性:

    • id:唯一标识
    • class:类全路径

    创建对象时,默认执行无参构造方法

  • 基于注解方式实现

注入属性
  • 基于xml方式注入属性

    • DI:依赖注入,就是注入属性

      • 使用set方法注入

        <!--set方法注入属性-->
        <bean id="book" class="com.company.Book">
            <property name="bname" value="金瓶梅"/>
        </bean>
        

        将成员变量bname的值设置为金瓶梅

      • 使用有参构造注入

        <!--有参构造注入属性-->
        <bean id="orders" class="com.company.Order">
            <constructor-arg name="oname" value="电脑订单"/>
            <constructor-arg name="address" value="China"/>
        </bean>
        

xml方式注入属性时的特殊情况

  • null值
    <!-- bname属性值为null -->
    <bean id="book" class="com.company.Book">
        <property name="bname">
            <null/>
        </property>
    </bean>
  • 属性值包含特殊符号
<!--    假设属性bname的值为 <<金瓶梅>>    -->
<!--    方式一:采用转义的符号-->
<bean id="book" class="com.company.Book">
    <property name="bname" value="&lt;&lt;金瓶梅&gt;&gt;"/>
</bean>

<!--    方式二:采用CDATA的格式-->
<bean id="books" class="com.company.Book">
    <property name="aname">
        <value><![CDATA[<<金瓶梅>>]]></value>
    </property>
</bean>
注入属性-外部bean

1、创建两个类 service类 和 dao类

在这里插入图片描述

2、在service调用dao里面的方法

普通形式

// UserDaoImpl.java

public class UserDaoImpl implements UserDao{
    @Override
    public void update() {
        System.out.println("dao包中UserDaoImpl类下的update方法被调用");
    }
}
// UserService.java

public class UserService {
    public void add() {
        System.out.println("service包中UserService类下的add方法被调用");
        
        /* 这里,假设要在service中调用dao的方法 */
        // 创建UserDao对象
        UserDao userDao = new UserDaoImpl();
        userDao.update();  // 创建对象后直接调用
    }
}

Spring形式

需要将UserDao作为属性注入到service

// UserService.java

public class UserService {
    // 创建UserDao类型属性,生成其对应set方法
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("service包中UserService类下的add方法被调用");

        userDao.update();  // 调用
    }
}
配置文件:

<!--    1、service和dao对象的创建-->
<bean id="userService" class="com.company.service.UserService">
    <!--    2、注入userDao对象
        name:类中的属性名称
        ref:创建userDao对象 相应的 bean标签中的id值
        -->
    <property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.company.dao.UserDaoImpl"></bean>

用一个测试类观察输出情况:

public class TestBean {
    @Test
    public void test() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");

        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

在这里插入图片描述

注入属性-内部bean
  • 一对多关系:例如部门和员工

    一个部门可以有多个员工,员工属于某一个部门

  • 在实体类之间表示 一对多 关系。代码表示为:

    package com.company.bean;
    
    /**
     * 部门类
     */
    public class Dept {
        private String dname;
    
        public void setDname(String dname) {
            this.dname = dname;
        }
    }
    
    package com.company.bean;
    
    /**
     * 员工类
     */
    public class Emp {
        private String ename;
        private String gender;
    
        // 用一个对象表示员工属于某一个部门
        private Dept dept;
    
        public void setEname(String ename) {
            this.ename = ename;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
    
        public void setDept(Dept dept) {
            this.dept = dept;
        }
    }
    
  • 在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">
    
        <bean id="emp" class="com.company.bean.Emp">
            <property name="ename" value="小明"/>
            <property name="gender" value=""/>
    <!--        设置对象类型的属性-->
            <property name="dept">
                <bean id="dept" class="com.company.bean.Dept">
                    <property name="dname" value="研发部"/>
                </bean>
            </property>
        </bean>
    </beans>
    

    这里可以使用ref属性指向一个外部bean,也可以:在一个bean中直接写另一个bean。

  • toString()方法都加入到EmpDept两个类中,测试一下:

    @Test
    public void testInnerBean(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
        Emp emp = context.getBean("emp", Emp.class);
        System.out.println(emp);
    }
    

    可以看到正常输出:

    Emp{ename='小明', gender='男', dept=Dept{dname='研发部'}}
    
    Process finished with exit code 0
    
xml注入集合属性
  • 注入数组类型属性

  • 注入List集合类型属性

  • 注入Map集合类型属性

演示:创建一个Stu类,该类中包含多个集合类型属性

// Stu.java

package com.company.collectiontype;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class Stu {
    // 1、数组类型属性
    private String[] courses;

    // 2、List集合类型属性
    private List<String> list;

    // 3、Map集合类型属性
    private Map<String, String> map;

    // 4、Set集合类型属性
    private Set<String> set;

    public void setSet(Set<String> set) {
        this.set = set;
    }

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

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public void setCourses(String[] courses) {
        this.courses = courses;
    }
    
    @Override
    public String toString() {
        return "Stu{" +
                "courses=" + Arrays.toString(courses) +
                ", list=" + list +
                ", map=" + map +
                ", set=" + set +
                '}';
    }
}

然后在配置文件中注入对应属性。bean1.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来完成对象创建-->
    <bean id="stu" class="com.company.collectiontype.Stu">
<!--        数组类型的属性注入-->
        <property name="courses">
            <list>
                <value>Java课</value>
                <value>MySQl课</value>
            </list>
        </property>

<!--        List集合类型属性注入-->
        <property name="list">
            <array>
                <value>小明</value>
                <value>小花</value>
            </array>
        </property>

<!--        Map类型属性注入-->
        <property name="map">
            <map>
                <entry key="JAVA" value="Java课"/>
                <entry key="MYSQL" value="MySQL课"/>
            </map>
        </property>

<!--        Set类型属性注入-->
        <property name="set">
            <set>
                <value>Mysql数据库</value>
                <value>Redis数据库</value>
            </set>
        </property>
    </bean>
</beans>

其中,集合数据类型的property标签下,既可以使用<list>标签,也可使用<array>标签。

map类型标签下是<entry>标签。

@Test测试:

// TestSpring.java

public class TestSpring {
    @Test
    public void testCollection() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");

        Stu stu = context.getBean("stu", Stu.class);

        System.out.println(stu);
    }
}

可以看到,正常输出:

Stu{courses=[Java课, MySQl课], list=[小明, 小花], map={JAVA=Java课, MYSQL=MySQL课}, set=[Mysql数据库, Redis数据库]}

Process finished with exit code 0
集合里面设置对象类型的值

假设Stu.java中有属性private List<Course> courseList表示学生选的课程,则在bean1.xml中新增如下配置:

# bean1.xml

。。。。。。
<!--    注入list集合类型,值是对象-->
        <property name="courseList">
            <list>
                <ref bean="course1"/>
                <ref bean="course2"/>
            </list>
        </property>
    </bean>

<!--    创建多个Course对象-->
    <bean id="course1" class="com.company.collectiontype.Course">
        <property name="cname" value="语文"/>
    </bean>
    <bean id="course2" class="com.company.collectiontype.Course">
        <property name="cname" value="数学"/>
    </bean>
</beans>
FactoryBean
  • Spring有两种类型bean,一种普通的我们创建的bean,另一种是工厂bean

  • 普通bean:在配置文件中定义的bean类型 就是返回类型

    class="com.company.collectiontype.Stu">这里定义的是Stu类型,代码中得到的也是Stu类型:Stu stu = context.getBean("stu", Stu.class);

    工厂bean:在配置文件中定义的类型 可以跟返回类型不一样

工厂bean:

1、创建类,让这个类作为工厂bean,实现接口FactoryBean

2、实现接口里面的方法,在实现的方法中定义返回的类型

Bean的作用域
  • 在Spring里面,可以设置bean实例是 单实例 或 多实例
  • Spring里面默认情况是单实例

通过spring配置文件bean标签里面的属性(scope)用于设置单实例或是多实例。

不写这个属性默认就是singleton

在这里插入图片描述

singleton 和 prototype 创建对象的时机不一样:

public class TestSpring {
    @Test
    public void testCollection() {

        // ① 单实例 singleton 加载配置文件时就会创建单实例对象
        ApplicationContext context = new ClassPathXmlApplicationContext("facbean.xml");

        // ② 多实例 prototype 在调用getBean时创建对象
        MyBean myBean = context.getBean("myBean", MyBean.class);

        System.out.println(myBean);
    }
}
Bean生命周期

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

bean生命周期

  • 通过构造器创建bean实例(执行无参构造器)
  • 为bean的属性设置值和对其他bean引用(调用set方法)
  • 调用bean的初始化方法(需要进行相关配置)
  • bean可以使用(对象获取到了)
  • 当容器关闭时,调用bean的销毁方法(需要进行配置销毁的方法)

演示

// Orders.java

package com.company.bean;

public class Orders {
    private String oname;

    public Orders() {
        System.out.println("first:执行无参构造器创建bean实例");
    }

    public void setOname(String oname) {
        this.oname = oname;
        System.out.println("second:调用set方法设置属性值");
    }

    // 第三步的内容:需要自己 创建执行的初始化方法。(需要自己配置bean中的 init-method)
    public void initMethod(){
        System.out.println("third:执行初始化方法");
    }

    // 销毁对象的内容:对象销毁时执行这个方法,也需要在bean中配置 destroy-method
    public void destroyMethod(){
        System.out.println("fifth:执行销毁方法");
    }
}
<?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="orders" class="com.company.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="oname" value="汽车订单"/>
    </bean>
</beans>

然后用测试类查看:

    @Test
    public void testLife(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
        Orders orders = context.getBean("orders", Orders.class);
        System.out.println("fourth:得到了创建的对象");
        System.out.println(orders);

        // 手动让bean实例销毁
        context.close();
    }

执行@Test后输出结果如下:

first:执行无参构造器创建bean实例
second:调用set方法设置属性值
third:执行初始化方法
fourth:得到了创建的对象
com.company.bean.Orders@34f6515b
fifth:执行销毁方法

Process finished with exit code 0
bean的后置处理器

在以上5个步骤,第3步 调用初始化方法的前后,各有一步骤

……

  • 2.5、把bean实例传递bean后置处理器方法

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException 
    
  • ③、调用bean的初始化方法(需要进行相关配置)

  • 3.5、把bean实例传递bean后置处理器方法

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
    

……

后置处理器的演示

⒈创建类,实现接口BeanPostProcessor,创建后置处理器的类

// MyBeanPost.java

package com.company.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;

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

⒉在配置文件中指定这个类

<?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="orders" class="com.company.bean.Orders" init-method="initMethod" destroy-method="destroyMethod">
        <property name="oname" value="汽车订单"/>
    </bean>

<!--    后置处理器是需要在配置文件中手动配置的,这样spring才能知道这个类是后置处理器-->
    <bean id="myBeanPost" class="com.company.bean.MyBeanPost"/>
<!--    这个配置会对每个bean都生效-->
</beans>

⒊执行测试类观察输出结果

first:执行无参构造器创建bean实例
second:调用set方法设置属性值
初始化方法之前执行
third:执行初始化方法
初始化方法之后执行
fourth:得到了创建的对象
com.company.bean.Orders@16aa8654
fifth:执行销毁方法

Process finished with exit code 0
xml自动装配

⒈根据指定装配规则,由spring自动将匹配的属性值进行注入

⒉演示自动装配过程

我们新建两个类代表员工Emp和部门Dept,其中员工中有属性private Dept dept;

// Emp.java

package com.company.autowire;

public class Emp {
    private Dept dept;

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "dept=" + dept +
                '}';
    }

    public void test() {
        System.out.println(dept);
    }
}
// Dept.java

package com.company.autowire;

public class Dept {
    @Override
    public String toString() {
        return "Dept{}";
    }
}

配置文件bean5.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="dept" class="com.company.autowire.Dept">
    </bean>

    <bean id="emp" class="com.company.autowire.Emp">
        <property name="dept" ref="dept"/>
    </bean>
</beans>

然后用测试类输出一下看结果:

public class TestSpring {
    @Test
    public void test5() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
        Emp emp = context.getBean("emp", Emp.class);

        System.out.println(emp);  // 打印结果为	Emp{dept=Dept{}}
    }
}

那么如何实现自动装配呢?

bean标签中有个属性autowire

autowire常用的两个属性:

  • byName:根据属性名字注入
    • 注入值bean的id值和类属性名称一样
  • byType:根据属性类型注入
    <bean id="dept" class="com.company.autowire.Dept">
    </bean>

    <bean id="emp" class="com.company.autowire.Emp" autowire="byName">
<!--        <property name="dept" ref="dept"/>-->
    </bean>

可以看到,增加了autowire后即使bean标签中的property被注释,测试类也能正常输出结果

外部属性文件

⒈方式一:直接配置数据库信息(配置Druid数据库连接池)

  • 引入Druid连接池依赖jar包。然后在配置文件中创建对象并设置property,将mysql数据库的信息填入:

    <?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="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/lxyker"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </bean>
    </beans>
    

⒉方式二:引入外部属性文件配置数据库连接池

  • 创建外部属性文件,properties格式文件,写上数据库的信息。jdbc.properties:

    driverClassName = com.mysql.cj.jdbc.Driver
    url = jdbc:mysql://localhost:3306/lxyker
    username = root
    password = 123456
    
  • 把外部属性文件引入到spring配置文件中

    在此之前需引入context名称空间,引入之后才能使用它的相关标签

    在spring配置文件中使用标签:<context:property-placeholder>

    <?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 https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--    引入外部属性文件,classpath后面是.properties文件的路径    -->
        <context:property-placeholder location="classpath:jdbc.properties"/>
    
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${driverClassName}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </bean>
    </beans>
    

基于注解方式

注解是代码中的特殊标记,格式为:@注解名称(属性名称1=属性值1, 属性名称2=属性值2……)

注解可以简化xml配置

spring针对Bean管理中创建对象提供的注解
  • @Component

  • @Service

  • @Controller

  • @Repository

这四个注解功能一样,都可以用来创建bean实例

基于注解方式实现对象的创建

⒈引入依赖 spring-aop-5.3.5.jar

⒉开启组件扫描

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

<!--    开启组件扫描
1、如果要扫描多个包,base-package的值可以用逗号隔开。或者直接写它们的上层目录
-->
    <context:component-scan base-package="com.company"/>
</beans>

⒊创建类,在类上面添加创建对象的注解(上面四个注解之一)

package com.company.service;

import org.springframework.stereotype.Component;

@Component(value = "userService")   // 相当于配置文件中的<bean id="userService" class=.../>
public class UserService {
    public void add() {
        System.out.println("Service add ……");
    }
}

再用测试类查看一下输出:

public class TestSpring {
    @Test
    public void testService() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        UserService userService = context.getBean("userService", UserService.class);

        userService.add();
    }
}

可以发现add()方法被正常调用。

⒋开启组件扫描的细节问题

扫描部分include filter+排除部分exclude filter

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

@Autowired:根据属性类型进行自动装配

@Qualifier:根据属性名称进行注入

@Resource:即可根据类型注入,亦可根据名称注入。jdk11版本没有

@Value:与上面三个不同,这是注入普通类型属性

演示:(以@Autowired为例)

① 把service和dao对象创建,在service和dao类添加创建对象的注解

② 在service注入dao对象。在service类中先添加dao类型属性,在属性上使用注解

// UserService.java

package com.company.service;

import com.company.dao.UserDao;
import com.company.dao.UserDaoImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
//    @Qualifier(value = "userDaoImpl2") 如果有多个对象,用Qualifier区分
    private UserDao userDao;

    public void add() {
        System.out.println("Service add ……");
        userDao.add();
        // 只有前面有Autowired的注解,这里才能直接调用add()
        // 相当于 UserDao userDao = new UserDaoImpl();
    }
}
// UserDaoImpl.java

package com.company.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao{
    @Override
    public void add() {
        System.out.println("UserDaoImpl add.......");
    }
}
  • Qualifier根据名称进行注入 需要和@Autowired一起使用。

⒍完全注解开发

① 创建配置类,用于替代xml配置文件

// SpringConfig.java

package com.company.config;

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

@Configuration  // 把当前类作为配置类,用以替代xml文件
@ComponentScan(basePackages = {"com.company"})	// 相当于之前配置文件中的包扫描路径
public class SpringConfig {
}

② 编写测试类

@Test
public void testComplete() {
    // 加载配置类
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

    UserService userService = context.getBean("userService", UserService.class);
    userService.add();
}

AOP

  • 面向切面编程

通俗描述:不修改源代码,在主干功能上添加新的功能

AOP底层原理

  • AOP底层使用动态代理

    • 有两种情况的动态代理

      • 有接口的情况,使用JDK动态代理

        创建接口实现类代理对象,来增强类的方法

在这里插入图片描述

  • 无接口的情况,使用CGLIB动态代理

在这里插入图片描述

AOP(JDK动态代理)

⒈使用JDK动态代理,使用Proxy类 里面的方法 创建代理对象

在这里插入图片描述

调用newProxyInstance方法

这个方法有三个参数:

ClassLoader loader:类加载器

类<?>[] interfaces:增强方法所在的类 这个类实现的接口,支持多个接口

InvocationHandler h:实现这个接口InvocationHandler,创建代理对象,写增强的方法

⒉编写JDK动态代理的代码

  • 创建接口,定义方法

    package com.company.dao;
    
    public interface UserDao {
        public int add(int a, int b);
    
        public String update(String id);
    }
    
  • 创建接口实现类,实现其方法

    package com.company.dao;
    
    public class UserDaoImpl implements UserDao{
        @Override
        public int add(int a, int b) {
            System.out.println("UserDaoImpl中的add方法执行了……");
            return a + b;
        }
    
        @Override
        public String update(String id) {
            System.out.println("UserDaoImpl中的update方法执行了……");
            return id;
        }
    }
    
  • 使用Proxy类创建接口代理对象

    package com.company;
    
    import com.company.dao.UserDao;
    import com.company.dao.UserDaoImpl;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    
    public class JDKProxy {
        public static void main(String[] args) {
            // 创建接口实现类代理对象
            Class[] interfaces = {UserDao.class};
            UserDao userDao = new UserDaoImpl();
            UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
    
            System.out.println("返回值为:" + dao.add(1, 2));
        }
    }
    
    // 创建代理对象代码
    class UserDaoProxy implements InvocationHandler {
    
        // 1、创建的是谁的代理对象,就把谁传递过来
        // 用 有参构造传递
        private Object obj;
        public UserDaoProxy(Object obj){
            this.obj = obj;
        }
    
        // invoke方法中写 增强的逻辑
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 原方法之前
            System.out.println("方法之前执行……" + method.getName() + ";所传参数:" + Arrays.toString(args));
    
            // 被增强的方法
            Object res = method.invoke(obj, args);
    
            // 原方法之后
            System.out.println("方法之后执行……" + obj);
    
            return res;
        }
    }
    
AOP(相关术语)
  • 连接点:类中的哪些方法可以被增强,这些方法就是连接点
  • 切入点:实际上被增强的方法,被称为切入点
  • 通知(增强):
    • 实际增强的逻辑部分 称为通知
    • 通知的五种类型:
      • 前置通知
      • 后置通知
      • 环绕通知
      • 异常通知
      • 最终通知
  • 切面:是一个动作,把通知应用到切入点的过程
AOP操作

Spring框架一般基于AspectJ实现AOP操作

AspectJ不是Spring的组成部分,它是独立的,一般把两者结合起来 进行AOP操作

基于AspectJ实现AOP操作
  • 基于xml配置文件实现
  • 基于注解方式实现(常用)

在项目工程中先引入AOP相关的依赖
在这里插入图片描述

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

  • 语法结构:

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

举例1:对com.lxyker.dao.BookDao类里面的add()方法进行增强

execution(* com.lxyker.dao.BookDao.add(..))

举例2:对com.lxyker.dao.BookDao类里面的所有方法进行增强

execution(* com.lxyker.dao.BookDao.*(..))

举例3:对com.lxyker.dao包中所有类的所有方法都进行增强

execution(* com.lxyker.dao.*.*(..))
AOP操作(AspectJ注解)

⒈创建类,在类里面定义方法add

// User.java

package com.company.aopanno;

public class User {
    public void add(){
        System.out.println("add()....");
    }
}

⒉创建增强类(编写增强逻辑)

  • 在增强类里面,创建方法,让不同的方法代表不同通知类型

    package com.company.aopanno;
    
    // 增强类
    public class UserProxy {
    
        // 前置通知
        public void before(){
            System.out.println("before().....");
        }
    }
    

⒊进行通知的配置

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

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--    开启注解扫描-->
        <context:component-scan base-package="com.company.aopanno"/>
    </beans>
    
  • 使用注解创建User和UserProxy对象

    // 增强类
    @Component
    public class UserProxy {
        ……
    
    -------------------------------------------------------------
    // 被增强的类
    @Component
    public class User {
        ……
    
  • 在增强的类上面添加注解@Aspect

    // 增强类
    @Aspect     // 生成代理对象
    @Component
    public class UserProxy {
        ……
    
  • 在spring配置文件中开启生成代理对象

        <!--    开启注解扫描-->
        <context:component-scan base-package="com.company.aopanno"/>
    
        <!--    开启Aspect生成代理对象-->
        <aop:aspectj-autoproxy/>
    

⒋配置不同类型的通知

  • 在增强类的里面,在作为通知方法上面 添加通知类型注解,使用切入点表达式配置

    package com.company.aopanno;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    // 增强类
    @Aspect     // 生成代理对象
    @Component
    public class UserProxy {
    
        // 前置通知
        @Before(value = "execution(* com.company.aopanno.User.add(..))")    // 前置通知,其中value可省略
        public void before(){
            System.out.println("before().....");
        }
    }
    

测试

public class TestAOP {
    @Test
    public void testAopAnno() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
        User user = context.getBean("user", User.class);

        user.add();
    }
}

可以看到,前置通知的输出结果:

before().....
add()....

Process finished with exit code 0

⒌相同的切入点抽取

// 增强类
@Aspect     // 生成代理对象
@Component
public class UserProxy {

    // 相同切入点抽取
    @Pointcut(value = "execution(* com.company.aopanno.User.add(..))")
    public void pointCutDemo() {
    }

    // 前置通知
    @Before(value = "pointCutDemo()")    // 直接写抽取的切入点方法+括号即可
    public void before() {
        System.out.println("before().....");
    }
}

⒍有多个增强类对同一个方法进行增强时,优先级的设置

在增强类上添加注解@Order(数字)。这个数字越小,优先级越高

// 增强类
@Aspect     // 生成代理对象
@Component
@Order(1)
public class UserProxy {
    ……
AOP操作(AspectJ配置文件)

重点了解注解方式


JdbcTemplate

什么是JdbcTemplate?

Spring框架对JDBC进行封装,使用JdbcTemplate能够很方便地对数据库进行操作

准备工作

  • 引入依赖

  • 在spring配置文件中 配置数据库连接池

        <!--    数据库连接池配置-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
            <property name="url" value="jdbc:mysql://localhost:3306/lxyker"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        </bean>
    
  • 配置JdbcTemplate对象,注入DataSource

        <!--    创建JdbcTemplate对象-->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!--        注入DataSource信息-->
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
  • 创建service类和dao类,在dao注入JdbcTemplate对象

    配置文件

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

    Service

    @Service
    public class BookService {
        // 注入dao
        @Autowired
        private BookDao bookDao;
    }
    

    Dao

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

JdbcTemplate操作数据库

  • 对应数据库表 创建实体类

    数据库表如下:

    create table if not exists t_book
    (
        book_id  bigint(20)   not null auto_increment primary key,
        bookname varchar(100) not null,
        bstatus  varchar(50)  not null
    )
    
    package com.company.entity;
    
    public class Book {
        private String bookId;
        private String bookname;
        private String bstatus;
    
        public String getBookId() {
            return bookId;
        }
    
        public void setBookId(String bookId) {
            this.bookId = bookId;
        }
    
        public String getBookname() {
            return bookname;
        }
    
        public void setBookname(String bookname) {
            this.bookname = bookname;
        }
    
        public String getBstatus() {
            return bstatus;
        }
    
        public void setBstatus(String bstatus) {
            this.bstatus = bstatus;
        }
    }
    
  • 编写service和dao

    在dao进行数据库添加操作

    调用JdbcTemplate对象里面的update方法实现添加操作

    update(String sql, Object... args)

    • 第一个参数:sql语句
    • 第二个参数:可变参数;设置sql语句的值
    @Repository
    public class BookDaoImpl implements BookDao {
        // 注入JdbcTemplate
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Override
        public void add(Book book) {
            String sql = "insert into t_book values(?, ?, ?)";
            int update = jdbcTemplate.update(sql, book.getBookId(), book.getBookname(), book.getBstatus());
            System.out.println(update);
        }
    }
    

    测试:

    public class TestBook {
        @Test
        public void testJdbcTemplate() {
            ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
            BookService bookService = context.getBean("bookService", BookService.class);
    
            Book book = new Book();
            book.setBookId("1");
            book.setBookname("Java从入门到精通");
            book.setBstatus("Good");
    
            bookService.addBook(book);
        }
    }
    

    可以看到正常输出1

    4月 23, 2021 3:01:37 下午 com.alibaba.druid.pool.DruidDataSource info
    信息: {dataSource-1} inited
    1
    
    Process finished with exit code 0
    

    同理,更新和删除的操作部分代码如下:

        @Override
        public void update(Book book) {
            String sql = "update t_book set bookname = ?, bstatus = ? where book_id = ?";
            Object[] args = {book.getBookname(), book.getBstatus(), book.getBookId()};
            jdbcTemplate.update(sql, args);
        }
    
        @Override
        public void delete(String id) {
            String sql = "delete from t_book where book_id = ?";
            jdbcTemplate.update(sql, id);
        }
    

JdbcTemplate操作数据库(查询返回某个值)

1、查询表里有多少条记录,返回的是某个值

2、用JdbcTemplate实现查询返回某个值的代码

BookService.java:

    // 查询表中有多少条记录
    public int findCount(){
        return bookDao.selectCount();
    }

BookDao.java中定义这个方法,然后BookDaoImpl.java实现类中写出这个方法的具体逻辑:

    @Override
    public int selectCount() {
        String sql = "select count(*) from t_book";
        return jdbcTemplate.queryForObject(sql, Integer.class);
    }

其中,queryForObject()方法的第一个参数是查询的语句,第二个参数是返回类型的class。

JdbcTemplate操作数据库(查询返回对象)

在这里插入图片描述

调用jdbcTemplate中的queryForObject()方法,其参数说明如下:

  • sql语句
  • RowMapper
    • 是一个接口,返回不同类型的数据,一般用这个接口的实现类 来完成数据的封装
    • 这个实现类BeanPropertyRowMapper<T>(T.class)
  • sql语句中待替换的值

具体代码BookDaoImpl.java如下:

    @Override
    public Book findBookInfo(String id) {
        String sql = "select * from t_book where book_id = ?";
        Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
        return book;
    }

JdbcTemplate操作数据库(查询返回集合)

同理,返回集合 jdbcTemplate应该调用query()方法

在这里插入图片描述

BookDaoImpl.java部分代码如下:

    @Override
    public List<Book> findAll() {
        String sql = "select * from t_book";
        List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
        return bookList;
    }
JdbcTemplate批量操作数据库

批量添加

使用jdbcTemplate的batchUpdate()方法

在这里插入图片描述

  • sql:执行的sql语句
  • batchArgs:List集合,添加多条数据

BookDaoImpl.java:

    @Override
    public void batchAddBook(List<Object[]> batchArgs) {
        String sql = "insert into t_book values (?, ?, ?)";
        jdbcTemplate.batchUpdate(sql, batchArgs);
    }
事务概念

事务是数据库操作的最基本单元,指的是逻辑上的一组操作,要么都成功,要么都失败。

典型例子:银行转账

事务的四大特性:原子一致隔离持久(ACID)

  • 原子性:不可分割
  • 一致性:操作之前和操作之后总量不变
  • 隔离性:多个事务操作时,不会相互影响
  • 持久性:事务提交后表中的数据是真正变化了的
spring中的事务操作

搭建一个银行转账的事务操作环境:

在这里插入图片描述

用于测试的表:

在这里插入图片描述

创建service,搭建dao,完成对象创建和注入关系

  • service注入dao,在dao注入JdbcTemplate,在JdbcTemplate注入DataSource

    bean.xml不变

        <!--    开启组件扫描-->
        <context:component-scan base-package="com.company"/>
    
        <!--    数据库连接池配置-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
            <property name="url" value="jdbc:mysql://localhost:3306/lxyker"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        </bean>
    
        <!--    创建JdbcTemplate对象-->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!--        注入DataSource信息-->
            <property name="dataSource" ref="dataSource"/>
        </bean>
    

    UserDaoImpl.java:

    @Repository
    public class UserDaoImpl implements UserDao{
        @Autowired
        private JdbcTemplate jdbcTemplate;
    }
    

    UserService.java

    @Service
    public class UserService {
        // 注入dao
        @Autowired
        private UserDao userDao;
    }
    
  • 在dao创建多钱和少钱两个方法,在service层创建转账方法

    UserDaoImpl.java:

       @Override
        public void addMoney() {
            String sql = "update t_account set money = money + ? where username = ?";
            jdbcTemplate.update(sql, 100, "Bob");
        }
    
        @Override
        public void reduceMoney() {
            String sql = "update t_account set money = money - ? where username = ?";
            jdbcTemplate.update(sql, 100, "Tom");
        }
    

    service层 UserService.java

        public void transfer(){
            // 少钱
            userDao.reduceMoney();
            // 多钱
            userDao.addMoney();
        }
    

到目前为止,基本的转账环境已经准备就绪。

事务操作的伪代码:

    public void transfer() {
        try {
            // first:开启事务

            // second:进行业务操作

            // 少钱
            userDao.reduceMoney();

            // 模拟异常
            int a = 1/0;

            // 多钱
            userDao.addMoney();

            // third:未发生异常,则提交事务
        } catch (Exception e) {
            // fourth:发现异常,则事务回滚
        }
    }

spring框架已经提供了更方便的事务操作方式

Spring事务管理介绍

⒈事务一般添加到 Java EE三层架构的Service层上(业务逻辑层)

⒉在spring中进行事务管理操作:

  • 编程式事务管理
  • 声明式事务管理(更常用)

⒊声明式事务管理

  • 基于注解方式
  • 基于xml配置文件方式

⒋在spring中进行声明式事务管理,底层使用的是AOP

⒌spring事务管理相关API

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

在这里插入图片描述

注解方式实现声明式事务管理:

⒈在spring配置文件中配置事务管理器

    <!--    创建一个事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--        注入数据源 给哪个数据库进行事务操作-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

⒉配置文件中,开启事务的注解

    <!--    开启事务注解的组件-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

⒊在service类上边添加事务的注解

  • @Transactional 可以添加到类上边,也可以添加到方法上边
  • 如果添加到类上边,表示该类中的所有方法都添加事务操作
声明式事务管理的参数配置
@Transactional(参数)

在这里插入图片描述

  • propagation:事务传播行为

    七种行为,前两种最常用:

    REQUIRED:

    • A方法调用B方法,如果A方法本身有事务,调用B后,直接使用A的事务;
    • A没有事务,调用B后,创建新事务

    REQUIRED_NEW:

    • 总是创建新的事务
    @Transactional(propagation = Propagation.REQUIRED)
    
  • isolation:事务隔离级别

    多个事务之间不会相互影响,这是事务的隔离性

    如果没有事务的隔离性,会产生以下问题:

    • 脏读:一个未提交的事务 读取到了另一个未提交事务的数据
    • 不可重复读:一个未提交的事务 读取到了另一提交事务 的修改数据
    • 幻读:一个未提及的事务 读取到了 另一提交事务 添加的数据
    脏读不可重复读幻读
    READ UNCOMMITTED
    (读未提交)
    READ COMMITTED
    (读已提交)
    REPEATABLE READ
    (可重复读)
    SERIALIZABLE
    (串行化)
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    
  • timeout:超时时间

    • 事务需要在一定时间内进行提交,否则会回滚

    • 默认-1,可以设置,以秒为单位

      @Transactional(timeout = 5)
      
  • readOnly:是否只读

    • 默认为false
    • 设置为true后,只能做查询操作
  • rollbackFor:回滚

    • 出现哪些异常进行回滚
  • noRollbackFor:不回滚

    • 出现哪些异常不回滚

完全注解形式进行声明式事务管理

创建配置类,用于替代xml文件

package com.company.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration  // 表示这是一个配置类
@ComponentScan(basePackages = "com.company")  // 包扫描
@EnableTransactionManagement  // 开启事务
public class TxConfig {
    // 创建数据库的连接池
    @Bean
    public DruidDataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("jdbc:mysql://localhost:3306/lxyker");
        dataSource.setUrl("root");
        dataSource.setUsername("123456");
        dataSource.setPassword("com.mysql.cj.jdbc.Driver");

        return dataSource;
    }

    // 创建jdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();

        // 注入dataSource
        jdbcTemplate.setDataSource(dataSource);

        return jdbcTemplate;
    }

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

Spring5新功能——WebFlux

WebFlux是Spring5新添加的模块,用于Web开发,功能类似SpringMVC。WebFlux使用响应式编程而出现的框架。

同步和异步 阻塞和非阻塞

A调用B,A是调用者,B是被调用者。同步异步针对调用者,阻塞与否针对被调用者。

同步:A发送请求,等待B返回后再做其他操作

异步:A发送请求,不用等待B的返回结果,直接可以做其他事情

阻塞:B收到请求,做完请求的任务之后再给出反馈

非阻塞:B收到请求,立刻给出反馈,再做请求的任务

WebFlux特点:

  • 异步非阻塞式:在有限的资源下提高系统吞吐量和伸缩性,以Reactor为基础实现响应式编程
  • 函数式编程:Spring5框架基于Java8,WebFlux可使用Java8函数式编程方式实现路由请求

WebFlux和SpringMVC的对比

在这里插入图片描述

  • 都可以使用注解方式,都可以运行在Tomcat等容器中
  • SpringMVC采用命令式编程,WebFlux采用异步响应式编程
响应式编程

Java8及其之前版本

提供观察者模式相关的两个类ObserverObservable

public class ObserverDemo extends Observable {
    public static void main(String[] args) {
        ObserverDemo observerDemo = new ObserverDemo();
        // 添加观察者
        observerDemo.addObserver((o, arg) -> {
            System.out.println("-------");
        });
        observerDemo.addObserver((o, arg) -> {
            System.out.println("+++++++");
        });

        observerDemo.setChanged();  // 数据变化
        observerDemo.notifyObservers();  // 通知
    }
}

Reactor实现响应式编程

  • Reactor是满足Reactive规范的框架。

  • Reactor有两个核心类,Mono和Flux,这两个类都实现了接口Publisher

  • Flux和Mono都是数据流的发布者,使用Flux和Mono都可以发出三种数据信号(元素值、错误信号、完成信号)。错误和完成 信号 都是终止信号,终止信号用于告诉订阅者数据流结束了

……


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值