Spring

IOC

Ioc:控制反转,把对象的创建和对象之间的调用过程交给spring进行管理;

使用 Ioc 的目的:解耦(降低耦合度);

 

ioc底层原理:

  xml解析、工厂模式、反射;

 

ioc过程:

 

ioc(接口):

1、ioc思想基于ioc容器完成、ioc容器底层就是对象工厂;

2、spring提供了ioc容器的两种方式:(两个接口);

  (1)、BeanFactory:ioc容器的基本实现,是spring内部的使用接口,不推荐开发人员进行使用;

        在加载配置文件的时候不会创建对象、在获取(使用)对象的时候才会进行创建;

  (2)、ApplicationContext:BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用;

        在加载配置文件的时候会直接创建对象;

  (3)、ApplicationContext接口的实现类;

    

 

ioc操作bean:

1、spring创建对象;

2、spring注入属性;

 

ioc操作bean的两种方式:

1、基于xml配置文件;

2、基于注解;

 

ioc基于xml配置文件操作bean:(普通bean)

1、基于xml方式创建对象:

  (1)、在spring配置文件中,使用bean标签,标签里面添加对应的属性,就可以实现对象的创建;

  (2)、bean标签中有很多属性、介绍常用属性:

      id属性:唯一标识;

      class属性:类的全路径;

  (3)、创建对象时默认也是调用无参构造器完成对象的创建;

 

2、基于xml方式注入属性:

  (1)、DI:依赖注入、就是注入属性;

      方式一:使用 setter 方法进行注入;

      方式二:使用有参构造器进行注入;

    @Test
    void testBean() {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        User user = context.getBean("user",User.class);
    }
<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util
        https://www.springframework.org/schema/util/spring-util.xsd">

    <!--setter注入:实体类中需要有当前 setter方法-->
    <bean id="user" class="com.ithailin.interview.up.pojo.User">
        <property name="id" value="1"/>
        <property name="name" value="张三"/>
    </bean>
    <!--构造器注入:实体类中需要有当前 有参构造器-->
    <bean id="user2" class="com.ithailin.interview.up.pojo.User">
        <constructor-arg name="id" value="2"/>
        <constructor-arg name="name" value="李四"/>
    </bean>
    <!--构造器注入:实体类中需要有当前 有参构造器,可以使用index(索引值)来表示 name属性-->
    <bean id="user3" class="com.ithailin.interview.up.pojo.User">
        <constructor-arg index="0" value="3"/>
        <constructor-arg index="1" value="王五"/>
    </bean>
    <!--setter注入:实体类中需要有当前 setter方法 属性设置null值-->
    <bean id="user4" class="com.ithailin.interview.up.pojo.User">
        <property name="id" value="4"/>
        <property name="name">
            <null/>
        </property>
    </bean>
    <!--setter注入:实体类中需要有当前 setter方法 属性设置带有特殊符号的值
    若特殊符号是 ”< >“,则可以将其进行转义成
    &lt;(小于)
    &gt;(大于)
    -->
    <bean id="user5" class="com.ithailin.interview.up.pojo.User">
        <property name="id" value="&lt;&gt;>"/>
        <property name="name">
            <value><![CDATA[带有特殊符号的值]]></value>
        </property>
    </bean>

    <!--注入外部bean
    使用setter的方式注入,属性是一个对象时使用 ref来设置,而不是使用 value;
    ref属性:是创建 bean的id值;
    使用setter注入的方式可以解决spring循环依赖的问题(A对象中有属性 对象B、B对象中有属性 对象A)
    构造器的注入方式无法解决spring循环依赖的问题;
    -->
    <bean id="a" class="com.ithailin.interview.three.spring.circulardepend.A" scope="prototype">
        <property name="b" ref="b"></property>
    </bean>
    <bean id="b" class="com.ithailin.interview.three.spring.circulardepend.B">
        <property name="a" ref="a"></property>
    </bean>

    <!--注入内部 bean
    使用setter的方式注入
    -->
    <bean id="a2" class="com.ithailin.interview.three.spring.circulardepend.A">
        <property name="b">
            <bean id="b2" class="com.ithailin.interview.three.spring.circulardepend.B">
            </bean>
        </property>
    </bean>

    <!--使用setter注入一个 Stu 对象
    属性为 String[]、List、Map、Set、List<User>
    -->
    <bean id="stu" class="com.ithailin.interview.up.spring5.Stu">
        <!--数组类型属性注入-->
        <property name="arrs">
            <array>
                <value>array1</value>
                <value>array2</value>
            </array>
        </property>
        <!--List类型属性注入-->
        <property name="lists">
            <list>
                <value>list1</value>
                <value>list2</value>
            </list>
        </property>
        <!--Map类型属性注入-->
        <property name="maps">
            <map>
                <entry key="java" value="JAVA"></entry>
                <entry key="vue" value="VUE"></entry>
            </map>
        </property>
        <!--set类型属性注入-->
        <property name="sets">
            <set>
                <value>mysql</value>
                <value>redis</value>
            </set>
        </property>
        <!--list类型属性注入,其中值为 User 类型-->
        <property name="userList">
            <list>
                <ref bean="user"></ref>
                <ref bean="user2"></ref>
            </list>
        </property>
    </bean>

    <!--setter注入一个 List对象-->
    <util:list id="bookList">
        <value></value>
        <value></value>
        <value></value>
    </util:list>
</beans>

 

ioc基于xml配置文件操作bean:(FactoryBean)

1、spring中有两种 bean,一种是普通 bean,另一种就是 FactoryBean;

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

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

import org.springframework.beans.factory.FactoryBean;

public class MyFactoryBean implements FactoryBean {

    //定义返回的Bean
    @Override
    public Object getObject() throws Exception {
        return new Stu();
    }

    //定义返回的Bean的类型
    @Override
    public Class<?> getObjectType() {
        return null;
    }

    //当前的Bean是否为单例
    @Override
    public boolean isSingleton() {
        return false;
    }
}
    <!--定义的bean、返回是一个 FactoryBean-->
    <bean id="beanFactory" class="com.ithailin.interview.up.spring5.MyFactoryBean">
    </bean>

 

ioc操作bean:(bean的作用域)

在spring中,可以在<bean>的scope属性设置bean的作用域,以决定这个bean是单实例的还是多实例的;

默认情况下,spring只为每个ioc容器声明的bean创建唯一一个实例,整个ioc容器范围内都能共享这个实例,所有后续的getBean()和bean的引用都将返回这个唯一的bean实例,该作用域被称为singleton,他是所有bean的默认作用域;

singleton:在ioc容器中存在一个bean实例,bean以单实例的方式存在;当ioc容器被创建时,实例化该bean;

prototype:当ioc容器被创建时,不会实例化该bean;每次调用getBean()时都会返回一个新的bean实例;

request:每次http请求都会返回一个新的bean实例,该作用域只适用于WebApplicationContext环境;

session:同一个http session共享一个bean实例,该作用域只适用于WebApplicationContext环境; 

 

ioc操作bean:(bean的生命周期)

1、通过构造器创建 bean 的实例(无参构造器);

2、为 bean 的属性设置值,和对其它 bean 的引用(调用 setter 方法);

  调用后置处理器;postProcessAfterInitialization

3、调用 bean 的初始化方法(需要进行配置初始化方法);

  调用后置处理器;postProcessBeforeInitialization

4、bean 可以进行使用了(对象获取到了);

5、当容器关闭的时候,调用 bean 的销毁方法(需要进行配置销毁的方法);

@Data
public class MyBeanTest {
    private String id;
    private String name;

    public MyBeanTest() {
        System.out.println("第一步:无参构造器创建对象");
    }

    public void setId(String id) {
        System.out.println("第二步:setter方法注入属性");
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void initMethod() {
        System.out.println("第三步:执行初始化方法");
    }

    public void destroyMethod() {
        System.out.println("第五步:执行销毁方法");;
    }
}
    <!--setter注入一个 自定义对象、并且声明初始化和销毁的方法-->
    <bean id="myBeanTest"
          class="com.ithailin.interview.up.spring5.MyBeanTest"
          init-method="initMethod"
          destroy-method="destroyMethod">
        <property name="id" value="1"></property>
    </bean>
    @Test
    void testMyBeanTest() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        MyBeanTest myBeanTest = context.getBean("myBeanTest", MyBeanTest.class);
        System.out.println("第四步:获取bean对象:"+myBeanTest);
        context.close();
    }

 

自定义后置处理器类

//实现了 BeanPostProcessor,当ioc注入时会将其识别为一个 Bean后置处理器
public class MyBeanPost implements BeanPostProcessor {

    //bean实例初始化之前执行
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    //bean实例初始化之后执行
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}
    <!--配置了后置处理器、则ioc在进行bean初始化前后都会执行当前后置处理器中的方法-->
    <bean id="myBeanPost" class="com.ithailin.interview.up.spring5.MyBeanPost"/>

 

ico操作bean:(xml自动装配)

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

    <!--spring 实现自动装配
    bean标签属性 autowire:
        byName:根据属性名称注入,属性名称和注入的 bean 的 id值一样
        byType:根据属性类型注入,当前类型的实例 bean 不能定义多个
    -->
    <bean id="myAutowire" class="com.ithailin.interview.up.spring5.springiocxml.MyAutowire" autowire="byName"/>

 

ioc操作 bean:(引入外部的属性文件)

jdbc.properties

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_boot_data?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456
    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

 

ico操作bean:(基于注解的方式)

    <!--开启组件扫描
    如果扫描多个包
        1、可以使用 逗号 隔开;
        2、直接扫描当前多个包的上层目录
    -->
    <context:component-scan base-package="com.ithailin.interview.up.spring5.springiocanno"></context:component-scan>

 

spring针对bean管理创建对象提供的注解

@Component、@Controller、@Service、@Repository

 

spring基于注解的方式实现属性注入;

@AutoWired、@Qualifier、@Resource

 

Aop

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

 

aop 底层原理:

  1、有接口情况、使用 jdk 动态代理对象;

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

  2、没有接口的情况、使用CGLIB动态代理;

    创建子类的代理对象,增强类的方法

 

jdk动态代理

public interface UserService {
    public int add(int a,int b);
}
@Service
public class UserServiceImpl implements UserService {

    @Override
    public int add(int a, int b) {
        System.out.println("add方法执行了");
        return a+b;
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class MyJdkProxyTest{
    public static void main(String[] args) {
        //创建接口实现类的代理对象
        Class[] interfaces = {UserService.class};
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        UserService userService = (UserService) Proxy.newProxyInstance(MyJdkProxyTest.class.getClassLoader(), interfaces, new MyJdkProxy(userServiceImpl));
        int result = userService.add(1, 2);
        System.out.println("result:" + result);
    }

}

class MyJdkProxy implements InvocationHandler {
    //1、创建的是谁的代理对象,把谁传递过来;有参构造器
    private Object obj;
    public MyJdkProxy(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 res = method.invoke(obj, args);
        //方法之后
        System.out.println("方法之后执行。。。"+obj);
        return res;
    }
}

 

aop术语

1、连接点:

  类里面哪些方法可以被增强,这些方法成为连接点;

2、切入点:

  实际被真正增强的方法,称为切入点;

3、通知:

  实际被增强的逻辑部分代码,被称为通知;

  通知的多种类型:

  1. 前置通知(Before Advice): 在连接点之前执行的Advice,不过除非它抛出异常,否则没有能力中断执行流。使用 @Before 注解使用这个Advice。
  2. 返回之后通知(After Retuning Advice): 在连接点正常结束之后执行的Advice。例如,如果一个方法没有抛出异常正常返回。通过 @AfterReturning 关注使用它。
  3. 抛出(异常)后执行通知(After Throwing Advice): 如果一个方法通过抛出异常来退出的话,这个Advice就会被执行。通用 @AfterThrowing 注解来使用。
  4. 后置通知(After Advice): 无论连接点是通过什么方式退出的(正常返回或者抛出异常)都会执行在结束后执行这些Advice。通过 @After 注解使用。
  5. 围绕通知(Around Advice): 围绕连接点执行的Advice,就你一个方法调用。这是最强大的Advice。通过 @Around 注解使用。

4、切面:

  把通知应用的切入点的过程,称为切面;

 

aop操作(准备)

1、Spring框架一般都是基于AspectJ实现aop操作;

  (1)、AspectJ不是Spring的组成部分,独立aop框架,一般把AspectJ和Spring一起使用,进行aop操作;

2、基于AspectJ实现aop操作;

  (1)、基于xml配置文件实现;

  (2)、基于注解的方式实现;(推荐)

3、引入Spring-AOP的依赖;

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.4.5</version>
        </dependency>

 4、切入点表达式:

  (1)、切入点表达式:知道对哪个类里面的哪个方法进行增强;

  (2)、语法结构:

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

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * 这是一个切面
 */
@Component
@Aspect
public class MyAspect {

    @Before("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))")
    public void beforeNotify(){
        System.out.println("*** @Before 我是前置通知");
    }

    @After("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))")
    public void afterNotify(){
        System.out.println("*** @After 我是后置通知");
    }

    @AfterReturning("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))")
    public void afterReturningNotify(){
        System.out.println("*** @AfterReturning 我是返回通知");
    }

    @AfterThrowing("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))")
    public void afterThrowingNotify(){
        System.out.println("*** @AfterThrowing 我是异常通知");
    }

    @Around("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))")
    public Object aroundNotify(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object reValue = null;
        System.out.println("*** @Around 我是环绕通知之前");
        reValue = proceedingJoinPoint.proceed();
        System.out.println("*** @Around 我是环绕通知之后");
        return reValue;
    }

}

5、相同切入点的抽取:

    //抽取共同的切入点
    @Pointcut("execution(public int com.ithailin.interview.three.spring.aopsort.CalcServiceImpl.*(..))")
    public void printCut(){

    }

    @Before("printCut()")
    public void beforeNotify(){
        System.out.println("*** @Before 我是前置通知");
    }

 

6、有多个切面对同一个方法进行增强时,设置切面优先级;

  (1)、在增强类上面添加注解 @Order(数字类型的值),数字越小、优先级越高

 

jdbcTemplate

 

    @Test
    void testJdbcTemplate() {
        System.out.println("数据源类型:" + dataSource.getClass());

        String insertSql = "insert into user (id,name) values(?,?)";
        int insert = jdbcTemplate.update(insertSql, String.valueOf(System.currentTimeMillis()), "这是一个insert的值"+System.currentTimeMillis());
        System.out.println("insert:"+insert);

        //除了直接传递参数、也可以将参数封装成一个数组进行传递、注意参数位置对应
        String updateSql = "update user set name = ? where id = ?";
        Object[] args = {"这是一个update后的值","1"};
        int update = jdbcTemplate.update(updateSql,args);
        System.out.println("update:"+update);

        String deleteSql = "delete from user where id = ?";
        int delete = jdbcTemplate.update(deleteSql, "2");
        System.out.println("delete:"+delete);

        String selectSql1 = "select count(*) from user where name = ?";
        Integer select1 = jdbcTemplate.queryForObject(selectSql1,Integer.class,"这是一个insert的值1623142965005");
        System.out.println("select1:"+select1);

        String selectSql2 = "select * from user where id = ?";
        User select2 = jdbcTemplate.queryForObject(selectSql2, new BeanPropertyRowMapper<>(User.class), "1623142965005");
        System.out.println("select2:"+select2);

        String selectSql3 = "select * from user where name = ?";
        List<User> select3 = jdbcTemplate.query(selectSql3, new BeanPropertyRowMapper<>(User.class), "这是一个insert的值1623142965005");
        System.out.println("select3:"+ select3);

        String selectSql4 = "select * from user where name = ?";
        List<Map<String, Object>> select4 = jdbcTemplate.queryForList(selectSql4, "这是一个insert的值1623142965005");
        System.out.println("select4:"+select4.toString());

    }

 

事务

是数据库操作最基本单元,要么都成功。如果有一个失败则都失败;

 

在Spring进行事务管理操作:

  声明式事务:(推荐)

  编程式事务:通过代码变成进行事务的提交或者回滚;

 

声明式事务管理:

  注解:(推荐)

  xml配置:

 

在Spring进行声明式事务管理,底层使用了 aop 原理;

 

事务管理器:

interface PlatformTransactionManager:事务管理器接口

  class AbstractPlatformTransactionManager

    class DataSourceTransactionManager

      class JdbcTransactionManager

    class RedissonTransactionManager

 

事务传播行为属性可以在注解@Transactional的propagation属性中定义;

当事务方法被另一个事务方法调用时,必须指定事务该如何传播;

例如:方法可能继续在现有的事务中运行,也有可能开启一个新的事务,并在自己的事务中运行;

事务的传播由传播属性来定义,spring定义了7种类型的传播行为;

 

REQUIRED:如果有事务在运行,当前方法就在这个事务内运行,否则就启动一个新事务,并在自己的事务内运行;

REQUIRES_NEW:当前方法必须启动一个新事务,并在自己的事务内运行,如果有事务正在运行,应该将它挂起;

SUPPORTS:如果有事务在运行,当前方法就在这个事务内运行,否则它可以不运行在事务中;

NOT_SUPPORTED:当前方法不应该运行在事务中,如果有事务正在运行,应该将它挂起;

MANDATORY:当前方法必须运行在事务中,如果没有事务正在运行,就抛出异常;

NEVER:当前方法不应该运行在事务中,如果有事务正在运行,就抛出异常;

NESTED:如果有事务在运行,当前方法就在这个事务的嵌套事务内运行,否则就启动一个新事务,并在自己的事务内运行;

 

事务四大特性:ACID

  • Atomicity(原子性):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用
  • Consistency(一致性):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏
  • Isolation(隔离性):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
  • Durability(持久性):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中

CAP:Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)

BASE 理论:Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写

 

事务并发性的问题:

假设有两个事务t1和t2并发执行;

1、脏读

  t1将某条记录的age值从20修改到30;

  t2读取到t1更新后的值30;

  t1回滚,age值恢复为20;

  t2读取到的值是一个无效的值;

 

2、不可重复读

  t1读取age的值20

  t2将age修改为30

  t1再次读取age值为30,和第一次值不一致

 

3、幻读

  t1读取表中一部分数据

  t2向表中插入一条新数据

  t1再次读取时,多出了一条新数据

 

解决事务并发问题的方法

隔离级别:

事务的隔离级别可以在注解@Transactional的isolation属性中定义;

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。

一个事务与其它事务隔离的程度称为隔离级别。

SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据数据一致性就越好,但并发性就越弱。

 

1、读未提交:READ UNCOMMITTED

  允许t1读取t2未提交的数据;

 

2、读已提交:READ COMMITTED(Oracle默认隔离级别,开发时通常使用的隔离级别)

  要求t1只能读取t2已提交的数据;

 

3、可重复读:REPEATABLE READ(MySql默认隔离级别)

  确保t1可以多次从一个字段中读取相同的值,即t1执行期间,禁止其它事务对这个值进行更新;

 

4、串行化:SERIALIZABLE

  确保t1可以多次从一个表中读取到相同的行,在t1执行期间,禁止其它事务对这个表进行新增、删除、修改操作,可以避免任何并发问题,但性能较差;

 

事务超时时间

事务超时时间可以在注解@Transactional的timeout属性中定义;

  事务需要在一定的时间内进行提交,如果不提交则进行回滚;

  默认值 -1:代表不超时;

 

是否只读

事务是否只读属性在注解@Transactional的readOnly属性中定义;

  读:查询操作;

  写:增删改操作;

  默认值:false;

 

事务回滚

事务回滚属性在注解@Transactional的rollbackFor属性中定义;

  设置出现了哪些异常,会进行事务的回滚;

 

事务不回滚

事务不回滚属性在注解@Transactional的noRollbackFor属性中定义;

  设置出现了哪些异常,不进行事务回滚;

posted @ 2021-06-02 21:07  DHaiLin  阅读(58)  评论(0编辑  收藏  举报