spring

140 阅读8分钟

IOC 控制反转

  1. 程序将类的创建交给第三方容器来管理,即第三方容器能创建类的实例化对象,而不需要程序员new对象
  2. ioc容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在ioc容器中称为Bean
  3. maven坐标
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

bean的注入

  1. 通过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">
   <!--
   1. 引入spring依赖
   2. 创建applicationContext.xml文件
   3. 配置bean
   4. <bean id="唯一标识" class="实现类"/>
   -->

    <bean id="bookDao" class="com.hly.dao.impl.BookDaoImpl"></bean>
    <bean id="bookService" class="com.hly.service.impl.BookServiceImpl"></bean>
</beans>
  1. 获取bean
public static void main(String[] args) {
    // 获取IOC容器
    ApplicationContext ctx =new ClassPathXmlApplicationContext("applicationContext.xml");

    // 通过id获取bean对象
    BookDao bookDao = (BookDao) ctx.getBean("bookDao");
    bookDao.save();

    BookService bookService = 	(BookService)ctx.getBean("bookService");
    bookService.service();
}

工厂模式注入bean

DI依赖注入

  • bean与bean之间有关联关系时,需要采用依赖注入

set注入

  1. 提供set方法
public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    @Override
    public void service() {
        System.out.println("book service ....");

        bookDao.save();
    }

    // 提供set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}
  1. xml中bean中书写property标签
<bean id="bookService" class="com.hly.service.impl.BookServiceImpl">
    <!--
    配置bean与bean之间的依赖
    谁需要谁,就在是谁里配
    <property(属性标签) name="bean中的属性名" ref="需要哪个bean的Id"></property>
    -->
    <property name="bookDao" ref="bookDao"></property>
    </bean>
  1. 底层实现机制 * 通过property标签获取name(属性名) * 通过反射获取setName()方法 * 通过property标签ref获取Bean的id * 通过反射机制调用set方法完成注入
  2. 注意 1. name关联的是setXxx()方法,而不是属性名 2. bean的id必须唯一

构造注入

  1. 提供有参构造方法
  2. xml中bean中写constructor-arg标签
  3. 三种注入方式
    • 可以通过下标
    • 可以通过参数名
    • 可以通过类型判断

set注入案例

  1. 注入Bean
    1. 外部Bean
    2. 内部Bean
  2. 注入简单类型
  3. 注入集合
  4. 注入property
  5. 注入null/""
  6. 根据p命名空间注入
    • set注入
  7. 根据c命名空间注入
    • 构造注入
  8. 根据utils命名空间注入
    • 集合类型

自动装配

  • 基于XML的自动装配
    • 通过类型(byType)
      • bean唯一
    • 通过名称(byName)
    • 注意:
      • 都需要提供set、get方法
  • 基于注解的自动装配

BeanFactory与FactoryBean的区别

  • FactoryBean是辅助Spring容器实例化其他Bean的一个Bean对象
  • BeanFactory是Spring容器创建Bean的工厂

Bean的生命周期

一个bean从创建到死亡的过程

Bean生命周期的5步

  1. 创建Bean
  2. Bean属性的赋值
  3. 初始化Bean init-method
  4. 使用Bean
  5. 销毁Bean destroy-method

Bean生命周期的7步

​ 可以在初始化前后调用某些方法,需要某个Bean实现BeanPostProcessor接口,重写两个方法。在xml中注册该Bean,会对所有bean对象生效

  1. 创建Bean
  2. Bean属性的赋值
  3. 执行"Bean后处理器"的postProcessBeforeInitialization方法
  4. 初始化Bean
  5. 执行"Bean后处理器"的postProcessAfterInitialization方法
  6. 使用Bean
  7. 销毁Bean

Bean生命周期的十步

在某些特定时间点检查该Bean是否实现了特定的某些接口,若实现了则执行这些接口中的某些重写方法,从而更好的管理bean

  1. 创建Bean
  2. Bean属性的赋值
  3. 检查Bean是否实现某些带Aware后缀接口
    1. BeanClassLoaderAware, BeanNameAware, BeanFactoryAware
    2. 可以获取Bean的类加载器,Bean工厂,Bean的名字等操作
  4. 执行Bean后处理器的postProcessBeforeInitialization方法
  5. 检查Bean是否实现了InitializingBean接口
  6. 初始化Bean
  7. 执行"Bean后处理器"的postProcessAfterInitialization方法
  8. 使用Bean
  9. 检查Bean是否实现了DisposableBean接口
  10. 销毁Bean

Spring整合Mybatis

基于XML版

导入对应的依赖坐标

  1. mybatis配置文件: 用于配置mybatis其他配置,例如分页插件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>
</configuration>
  1. jdbc.properties文件
jdbc.driver=com.mysql.cj.jdbc.Driver
# mysql8需要添加时区等
jdbc.url=jdbc:mysql://localhost:3306/book_shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai 
jdbc.username=root
jdbc.password=123456

jdbc.acquireIncrement=5
jdbc.initialPoolSize=5
jdbc.minPoolSize=3
jdbc.maxPoolSize=10
  1. spring配置文件 配置SqlSessionFactoryBean、Mapper接口动态生成代理类、dataSource等
<!--     加载properties目录下的所有properties文件-->
<context:property-placeholder location="classpath:properties/*.properties" file-encoding="utf-8"/>
<!--使用c3p0连接池-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <!--        设置连接池参数-->
    <!-- 一次同时获取的连接数       -->
    <property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
    <!-- 初始化连接数       -->
    <property name="initialPoolSize" value="${jdbc.initialPoolSize}"/>
    <!--  最小连接数      -->
    <property name="minPoolSize" value="${jdbc.minPoolSize}"/>
    <!--最大连接数-->
    <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/>
</bean>

<!--    配置mybatis-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--数据库-->
    <property name="dataSource" ref="dataSource"/>
    <!--  配置mybatis配置文件      -->
    <property name="configLocation" value="classpath:mybatis.xml"/>
    <!--   配置实体类别名     -->
    <property name="typeAliasesPackage" value="com.hly.book.entity"/>
    <!-- 配置映射Mapper配置文件-->
    <property name="mapperLocations" value="classpath*:mapper/*"/>
</bean>
<!--配置mapper扫描器,让spring生成代理类,并交给spring管理-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--  bean工厂的名字      -->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <!--mapper接口所在的包-->
    <property name="basePackage" value="com.hly.book.mapper"/>
</bean>
<!--事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!--开启注解式事务-->
<tx:annotation-driven/>

基于注解

  1. 添加依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.10</version>
</dependency>

<!--mybatis jar包-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>
<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>

<!--spring jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>
<!--spring mybatis jar包-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.0</version>
</dependency>

2.创建SpringConfig类对象,spring的配置类

@Configuration
@ComponentScan("com.hly")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}

3.创建数据源对象

public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.user}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}
  1. 创建Mybatis配置对象
public class MybatisConfig {
    //使用SqlSessionFactoryBean类创建SqlSessionFactory对象Bean
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();

        // 设置数据源
        ssfb.setDataSource(dataSource);
        // 设置实体类映射包
        ssfb.setTypeAliasesPackage("com.hly.domain");
        return ssfb;
    }
    //MapperScannerConfigurer类创建Mapper映射文件对象Bea
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        //mapper映射包
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        //设置mapper映射接口所在的包
        msc.setBasePackage("com.hly.dao");
        return msc;
    }

}

SpringAOP

AOP的概念

  • 概念

    • AOP(Aspect Oriented Programamming) 面向切面编程,是一种编程范式
  • 作用

    • 在不惊动原始数据设计的基础上为方法进行功能增强
  • 核心概念

    • 代理(Proxy)

      • SpringAOP的核心使用代理模式实现的
    • 连接点(JoinPoint)

      • SpringAOP中,理解为任意方法的执行
    • 切入点(Pointcut)

      • 匹配连接点的式子,具有共性功能的方法描述
    • 通知(Advise)

      • 若干个方法的共性功能,最终体现为一个方法
    • 切面(Aspect)

      • 描述通知与切入点的对应关系
    • 目标对象(Target)

      • 被代理的原始对象称为目标对象

AOP的入门案例

  • 第一步:创建一个接口和实现类
  • public interface BookDao {
        void save();
        void update();
    }
    ​
    ​
    @Repository
    @Scope("singleton")
    public class BookDaoImpl implements BookDao{
        @Override
        public void save() {
    ​
            System.out.println("book dao ........");
        }
        public void update(){
            System.out.println("book update ........");
        }
    ​
        @PreDestroy
        public void destroy() throws Exception {
    ​
        }B
    ​
        @PostConstruct
        public void afterPropertiesSet() throws Exception {
        }
    }
    ​
    
  • 第二步:创建一个通知类并写一个公有的通知方法
  • public class MyAdvise {
        public void advise() {
            System.out.println("This is my advise");
        }
    }
    
  • 第三步:创建一个切入点,使用注解标识连接点的匹配规则
  • //切入点是无返回值、无参数、无内容的私有方法
       @Pointcut("execution(void com.hly.*.*Dao.*(..))")
       private void point(){}
    
  • 第四步:在通知方法上添加注解标识通知类型
  • @xxx("切入点的方法名()")
  • public class MyAdvise {
        //切入点是无返回值无内容的私有方法
        @Pointcut("execution(void com.hly.*.*Dao.*(..))")
        private void point(){}
    ​
        //定义该方法是执行前
        //@Before("point()")
        public void advise() {
            System.out.println("This is my advise");
        }
    }
    
  • 第五步:给通知类添加注解,标识这个类是一个通知类
  • // 为了让Spring检测到是一个Bean
    @Component
    // Aspect使其不是一个普通的Bean,而是一个切面
    @Aspect
    // 定义一个通知类,包含通知方法
    public class MyAdvise {
        ....
    }
    
  • 第六步:在Spring配置类上添加注解
  • @Configuration
    @ComponentScan("com.hly")
    // 告诉Spring使用了注解式开发aop
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }
    

AOP工作流程

Spring容器启动

读取所有切面配置的切入点(使用到的才装载)

初始化Bean

0.  匹配失败,创建对象
0.  匹配成功,创建原始对象(目标对象)的代理对象

获取Bean执行方法

-   获取Bean,调用方法执行,完成操作
-   获取的Bean是代理对象时,执行代理模式中的增强 方法,完成操作

AOP的切入点表达式

  • 切入点表达式标准格式:动作关键字(访问修饰符(可以省略) 返回值 包名.类/接口名.方法名(参数)异常名(可省))

    • execution(* com.hly.. * Dao.*(..))
  • 切入点表达式描述通配符

    • 作用:用于快速描述,范围描述
    • *:匹配任意符号
    • .. : 匹配多个连续的任意符号(常用于匹配包名)
    • +:匹配子类的类型
  • 切入点表达式书写技巧

    • 按标准规范开发
    • 查询操作的返回值建议使用*匹配
    • 减少使用..的形式描述包
    • 对接口进行描述,使用*表示模块名,例如BookDao的匹配描述为 *dao
    • 方法名书写保留动词,例如get* getById 匹配为getBy*
    • 参数根据实际情况灵活调整

AOP的通知类型

  • @Before()

    • 连接点执行前
  • @After()

    • 连接点执行后
  • @Around()

    • 环绕执行

    • 注意

      • 需要使用形参,调用连接点,否则会覆盖连接点方法
      • @Around("point()")
        public void advise1(ProceedingJoinPoint pjp) throws Throwable {
            pjp.proceed();
        }
        
      • 环绕通知返回值设置为Object类型
      • 环绕通知可以对原始方法调用过程出现的异常进行处理
  • @AfterReturning()

    • 方法正常执行完后执行
  • @AfterThrowing()

    • 方法抛出异常后执行

AOP案例:打印方法的执行时间

@Around("point()")
    public void advise1(ProceedingJoinPoint pjp) throws Throwable {
        long l = System.currentTimeMillis();
        Signature signature = pjp.getSignature();
        String declaringTypeName = signature.getDeclaringTypeName();
        String name = declaringTypeName+"."+signature.getName();
        for (int i=0;i<10000;i++){
            pjp.proceed();
        }
        long l1 = System.currentTimeMillis();
        System.out.println(name+"执行效率:"+(l1-l)+"ms");
    }

AOP获取数据

  • 获取切入点方法的参数

    • JoinPoint 适用于前置、后置、返回值、抛出异常后通知,设置为方法的第一个形参
  • @Around("MyAdvise.point()")
    public void advise2(JoinPoint jp){
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
    }
    
    • ProceedJointPoint:适用于环绕通知
  • @Around("point()")
    public void advise1(ProceedingJoinPoint pjp) throws Throwable {
        pjp.proceed();
         Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
    }
    
  • 获取切入点返回值

    • 返回后通知
  • @AfterReturning(value = "MyAdvise.point()",returning = "ret")
    public void advise2(JoinPoint jp,Object ret){
    }
    
    • 环绕通知
  • @Around("point()")
    public void advise1(ProceedingJoinPoint pjp) throws Throwable {
       Object ret =  pjp.proceed();
    }
    
  • 获取切入点方法运行异常信息

    • 抛出异常后通知
  • @AfterThrowing(value = "MyAdvise.point()",throwing= "t")
    public void advise3(JoinPoint jp,Throwable t){
    ​
    }
    
    • 环绕通知
  • @Around("MyAdvise.point()")
    public void advise3(ProceedingJoinPoint pjp){
        try {
            Object proceed = pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
    

案例:给参数去掉空格后执行

@Around("MyAdvise.point()")
public void advise3(ProceedingJoinPoint pjp) throws Throwable {
    Object[] args = pjp.getArgs();
    for (int i = 0; i < args.length; i++) {
        if (args[i].equals(String.class)){
            args[i] = args[i].toString().trim();
        }
    }
​
    Object proceed = pjp.proceed(args);
    return;
}