Spring

IoC容器

定义

IoC容器是什么:Bean容器

Bean:被IoC容器管理的对象

Spring 的IoC容器是ApplicationContext

功能

IoC容器可以:

  1. 创建对象(实例化、配置和组装 bean)
  2. 存储对象
  3. 获取存储的对象(getBean()方法)

使用

  1. 构建IoC容器对象

    ConfigurableApplicationContext:可配置的IoC容器

    AbstractApplicationContext:一个IoC容器的抽象类

    AbstractXmlApplicationContext:将xml文件作为配置文件

    FileSystemXmlApplicationContext:单机版的xml IoC容器,相对于文件系统

    String configLocation = "D:\\code\\java\\spring-study\\src\\main\\resources\\spring.xml";
    ApplicationContext context = new FileSystemXmlApplicationContext(configLocation);
    

    ClassPathXmlApplicationContext:单机版的xml IoC容器,相对于类路径

    String configLocation = "spring.xml";
    ApplicationContext context = new ClassPathXmlApplicationContext(configLocation);
    
  2. 配置Bean对象

    1. bean标签:用于配置bean对象

      属性:id,class,scope

      id:bean的名字,必须唯一

      class:bean的类型

      scope:bean的域,可取值:singleton(默认),prototype

      1. singleton:单例,整个容器只会为这个bean配置创建一个对象

      2. prototype:原型(多例),每当调用一次getBean(),就会根据bean配置创建一个对象

      3. 在xml文件里

        <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 https://www.springframework.org/schema/beans/spring-beans.xsd">
        
            <bean id="bean1" class="com.spring.study.entity.TestBean">
        
            </bean>
            
            <bean id="singletonBean1"
                  class="com.spring.study.entity.TestBean"
                  scope="singleton">
            </bean>
        
            <bean id="prototypeBean1"
                  class="com.spring.study.entity.TestBean"
                  scope="prototype">
            </bean>
        
        </beans>
        

        java文件里面

        String configLocation = "spring.xml";
        ApplicationContext context = new ClassPathXmlApplicationContext(configLocation);
        
        Object bean1 = context.getBean("bean1");
               System.out.println(bean1.getClass().getName());
        
        System.out.println(context.getBean("singletonBean1") == context.getBean("singletonBean1"));
        //true
              System.out.println(context.getBean("prototypeBean1") == context.getBean("prototypeBean1"));
        //false
                System.out.println(context.getBean("bean1") == context.getBean("bean1"));
        //true
        
    2. constructor-arg标签:用于配置传入的构造器参数

      属性:index,value,name,type

      index:参数的位置

      value:参数值

      name:参数名

      type:参数类型

      <bean id="bean-constructor1" class="com.spring.study.entity.TestBean2">
      <constructor-arg index="0" value="18"/>
      <constructor-arg index="1" value="abc"/>
      </bean>
      
    3. property标签:setter注入,通过setter方法传入参数

      属性:name,value

      name:setter方法名的set去掉然后首字母小写

      value:参数值

      ref:bean的名字

      <bean id="bean-setter1" class="com.spring.study.entity.TestBean3">
          <property name="field1" value="18"></property>
          <property name="field2" value="abc"></property>
      </bean>
      
    4. <context:component-scan>标签

      用于扫描标注了@Component注解的类,将其实例化并管理起来

      属性:base-package

      属性值:要扫描的包名

      用的时候在beans标签里面要加

      <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
              https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/context
              https://www.springframework.org/schema/context/spring-context.xsd">
      
    5. 自动注入

      bean里面的属性autowire,属性值:

      constructor:对构造器的参数自动注入bean对象

      byName:根据setter方法去掉set首字母小写的那个名字注入

      byType:根据setter方法的参数类型注入

      default:不自动注入

      no:不自动注入

    6. 懒加载

      bean里面的属性lazy-init:在容器启动时不创建对象,第一次调用getBean()获取该对象时创建对象,默认是false

    7. bean的初始化和销毁

      init-method,destroy-method

      属性值是无参函数名

      构造器 --> 依赖注入 -->初始化方法 ---> 具体的使用 -->销毁方法

用注解代替xml

对应的IoC容器:AnnotationConfigApplicationContext

ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

用@Configuration标注一个类为Spring配置类

  1. @Bean

    标注在一个方法上,由这个方法来创建Bean

    方法名就是Bean的名称,返回值类型就是Bean的类型

  2. @Scope(value = "singleton")

    @Scope(value = "prototype")

  3. @Autowired

    当标注在参数或属性上时,表示对这个参数或属性自动注入

    当标注在方法或构造器上时,表示对这个方法或构造器上所有参数自动注入

  4. @Lazy

    懒加载

  5. @Bean(initMethod="", destroyMethod="")

  6. @Component

    和那个component-scan的标签或注解一起用

    作用是把被标注的类变成Bean,其名字为类名首字母小写

    语义化注解:用来标注类的功能,如@Service,@Controller,@Repository

  7. @ComponentScan(basePackages = "包名")

    替代那个component-scan标签

bean的生命周期

构造器

依赖注入(构造器注入,setter注入,自动注入)

初始化方法

具体的操作

销毁方法

AOP

定义

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

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

底层原理

AOP底层使用动态代理

  1. 有接口,使用JDK动态代理

    1. 调用newProxyInstance方法

      有三个参数

      1. 类加载器

      2. 增强方法所在的类,这个类实现的接口,支持多个接口

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

      代码实现

      1)创建接口

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

      2)创建接口实现类,实现方法

      public class UserDaoImpl implements UserDao {
          @Override
          public int add(int a, int b){
              return a+b;
          }
          @Override
          public String update(String id){
              return id;
          }
      }
      

      3)使用Proxy类创建接口代理对象

      public class JDKProxy {
          public static void main(String[] args) {
              //创建接口实现类代理对象
              Class[] interfaces = {UserDao.class};
      //        Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() {
      //            @Override
      //            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
      //                return null;
      //            }
      //        });
              UserDaoImpl userDao = new UserDaoImpl();
              UserDao dao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
              int result = dao.add(1,2);
              System.out.println("result:"+result);
          }
      }
      
      //创建代理对象代码
      class UserDaoProxy implements InvocationHandler {
      
          //1. 创建的是谁的代理对象,把谁传递过来
          //有参数构造传递
          private Object object;
          public UserDaoProxy(Object object){
              this.object = object;
          }
      
      //增强的逻辑
          @Override
          public Object invoke(Object o, Method method, Object[] args) throws Throwable {
              //方法之前
              System.out.println("方法之前执行..."+method.getName()+":传递的参数"+ Arrays.toString(args));
              //被增强的方法执行
              Object res = method.invoke(object, args);
              //方法之后
              System.out.println("方法之后执行"+object);
              return null;
          }
      }
      
  2. 没有接口,使用CGLIB动态代理

术语

  1. 连接点

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

  2. 切入点

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

  3. 通知(增强)

    实际增强的逻辑部分称为通知(增强)

    通知有多种类型:前置通知、后置通知、环绕通知、异常通知、最终通知

  4. 切面

    把通知应用到切点过程

操作

  1. Spring框架一般都是基于AspectJ实现AOP操作

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

  2. 基于AspectJ实现AOP操作

    基于xml配置文件实现、基于注解方式实现(使用)

  3. 在项目工程里引入AOP相关依赖

    image-20220204161326476

  4. 切入点表达式

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

    语法结构:

    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.*.*(..))
    

AspectJ注解

  1. 创建类,在类里面定义方法

    public class User {
    	public void add(){
            System.out.println("add...");
        }
    }
    
  2. 创建增强类(编写增强逻辑)

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

    //增强的类
    public class UserProxy {
    	//前置通知
        public void before(){
            System.out.println("before...");
        }
    }
    
  3. 进行通知的配置

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

    使用注解创建User和UserProxy对象

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

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

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  4. 配置不同类型的通知

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

    @Before(value="execution(* com.atguigu.spring5.aopanno.User.add(..))")
    

    (这几个的value都是一样的)

    @Pointcut(...) 相同切入点抽取

    @Before(...)

    @Add(...)

    后置通知(返回通知):@AfterReturning(...)

    最终通知:@After(...)

    异常通知:@AfterThrowing(...)

    环绕通知:@Around(...)

    @Around(value="execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    	//被增强的方法执行
    	proceedingJoinPoint.proceed();	
    }
    
  5. 有多个增强类对同一个方法进行增强,设置增强类优先级

    在增强类上面添加注解@Order(int),值越小优先级越高

    @Component
    @Aspect
    @Order
    public class PersonProxy
    

AspectJ配置

  1. 创建两个类,增强类和被增强类,创建方法
  2. 在spring配置文件中创建两个类对象
  3. 在spring配置文件中配置切入点

JDBCTemplate

定义

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

jar包

image-20220205135855534

配置

组件扫描

<context:component-scan base-package="com.">

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

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
	<property name="url" value="jdbc:mysql:///user_db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
</bean>

配置JdbcTemplate对象,注入DataSourse

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

创建service类,创建dao类,在dao注入jdbcTemplate对象

java版配置

package com.spring.study.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Properties;

@Configuration
public class JdbcTemplateConfig {

    private Properties properties;
    
    private final String configLocation = "dataSource.properties";

    {
        properties = new Properties();
        try {
            properties.load(JdbcTemplateConfig.class.getResourceAsStream(configLocation));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Bean(destroyMethod = "close")
    public DruidDataSource dataSource()
    {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(properties.getProperty("driverClassName"));
        dataSource.setUsername(properties.getProperty("username"));
        dataSource.setPassword(properties.getProperty("password"));
        dataSource.setUrl(properties.getProperty("url"));
        return dataSource;
    }

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

使用

package com.spring.study.dao;

import com.spring.study.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public int insert(User user)
    {
        String sql = "INSERT INTO `user` (`name`,`gender`,`age`,`address`,`qq`,email) values (?,?,?,?,?,?)";
        return jdbcTemplate.update(sql,user.getName(),user.getGender(), user.getAge(),user.getAddress(),user.getQq(),user.getEmail());
    }

    public int update(User user)
    {
        String sql = "UPDATE user SET name = ? ,gender = ?,age = ?,address = ?,qq = ?,email = ? WHERE id = ?";
        return jdbcTemplate.update(sql,user.getName(),user.getGender(),user.getAge(),user.getAddress(),user.getQq(),user.getEmail(),user.getId());
    }

    // 删除用户id为userId的用户
    public int delete(Integer userId)
    {
        String sql = "DELETE FROM user WHERE id = ?";
        return jdbcTemplate.update(sql,userId);
    }

    public User selectByUserId(Integer userId)
    {
        String sql = "select * from `user` where id = ?";
        User user = jdbcTemplate.queryForObject(sql, User.class,userId);
        return user;
    }

    public List<User> selectAll()
    {
        String sql = "select * from `user`";
        List<User> users = jdbcTemplate.queryForList(sql, User.class);
        return users;
    }
}

常用API

update()

queryForList()

queryForObject()

事务

定义

事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。

事务就是一系列的动作,它们被当做一个单独的工作单元,这些动作要么全部完成,要么不起作用。

事务的四个关键属性(ACID)

  1. 原子性(atomicity):事务是一个原子操作,由一系列动作组成,事务的原子性确保动作要么全部完成要么完全不起作用
  2. 一致性(consistency):一旦所有事务动作完成,事务就被提交,数据和资源就处于一种满足业务规则的一致性状态中
  3. 隔离性(isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开,防止数据损坏
  4. 持久性(durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,通常情况下,事务的结果被写入到持久化存储器中

Spring中的事务管理

作为企业级应用程序框架,Spring在不同的事务管理API之上定义了一个抽象层,而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制

Spring既支持编程式事务管理,也支持声明式的事务管理

编程式事务管理:将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码

声明式事务管理:大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点,可以通过AOP的方法模块化。Spring通过Spring AOP框架支持声明式事务管理

Spring从不同的事务管理API中抽象出了一整套的事务机制,开发人员不必了解底层的API,就可以利用这些事务机制。有了这些事务机制,事务管理代码就能独立于特定的事务技术了

Spring的核心事务管理抽象是Interface Platform TransactionManager管理封装了一组独立于技术的方法,无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的

Spring中的事务管理器的不同实现

Class Jta TransactionManager:

在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理

Class Hibernate TransactionManager:

用Hibernate框架存储数据库

事务管理器以普通的Bean形式声明在Spring IOC容器中

使用

配置事务管理器

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

启用事务注解

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

添加事务注解

@Transactional

事务的传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播,例如:方法可能继续出现在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行

事务的传播行为可以由传播属性指定,Spring定义了7种传播行为

REQUIRED

当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行,这个默认的传播行为就是REQUIRED,因此在checkout()方法的开始和终止边界内只有一个事务,这个事务只在checkout()方法结束时的时候被提交,结果用户一本书都买不了了

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

@Transactional(propagation=Propagation.REQUIRED)

使用propagation指定事务的传播行为,即当前的事务方法被另一个事务方法调用时,如何使用事务,默认取值为REQUIRED,即使用调用方法的事务

REQUIRES_NEW

表示该方法必须启动一个新事务,并在自己的事务内运行,如果有事务在运行,就应该先挂起它

SUPPORTS

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

NOT SUPPORTED

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

MANDATORY

当前的方法必须运行在事务内部,如果没有正在运行的事务,就抛出异常

NEVER

当前的方法不应该运行在事务中,如果有运行的事务,抛出异常

NESTED

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

并发事务所导致的问题

当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多意外的问题

脏读:对于两个事务T1,T2,T1读取了已经被T2更新但还没有被提交的字段,之后,若T2回滚,T1读取的内容就是临时且无效的

不可重复读:对于两个事务T1,T2,T1读取了一个字段,然后T2更新了该字段,之后,T1再次读取同一个字段,值就不同了

幻读:对于两个事务T1,T2,T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,之后,如果T1再次读取同一个表,就会多出几行

隔离级别

使用isolation指定事务的隔离级别,最常用的取值为READ_COMMITTED

@Transactional(propagation=Propagation.REQUIRED,
              isolation=Isolation.READ_COMMITTED)

默认情况下Spring的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置,通常情况下取默认值即可

@Transactional(propagation=Propagation.REQUIRED,
              isolation=Isolation.READ_COMMITTED,
          noRollbackFor={UserAccountException.class})

超时和只读属性

使用readOnly指定事务是否为只读

由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响;如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化

超时事务属性:事务在强制回滚之前可以保持多久,这样可以防止长期运行的事务占用资源

只读事务属性:表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务,若真的是一个只读取数据库值的方法,应设置readOnly=true

使用 timeout 指定强制回滚之前事务可以占用的时间