(2020)Spring框架(基础和中级部分)

2020 Spring框架

一、框架的概述

1、spring是什么?为了解决什么提出的?

Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑和其它层的耦合问题(简化java开发)

spring可以做很多东西,为企业级平台提供了丰富的功能,但是这些功能的都依赖与他的两个核心特性,控制反转和面向切面编程

2、Spring的优点(减低耦合,增强服务,简化开发)

  • 减低程序之间的耦合
  • 简化开发流程
  • 通过切面编程以动态非侵入的方式增强服务
  • 声明式事务的支持
  • 方便测试
  • 方便集成各种优秀的框架

3、Spring的缺点

  • spring依赖反射,反射影响性能

4、spring的组成

Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container)AOP(Aspect Oriented Programming)和设备支持(Instrmentation)数据访问与集成(Data Access/Integeration)Web消息(Messaging)Test等 6 个模块中。 以下是 Spring 5 的模块结构图:

在这里插入图片描述

  • spring core:提供了框架基本的组成部分,IOC,DI
  • spring beans:提供了BeanFactory,Spring将管理的对象称为Bean
  • spring context:构建core封装包基础上的context封装包,提供了一种对象是的访问方式
  • spring jdbc:封装了jdbc,简化了jdbc开发
  • spring aop:面向切面编程,可以自定义拦截器,切点
  • spring web 对web开发的集成

5、Spring 框架中都用到了哪些设计模式?

  1. 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
  2. 单例模式:Bean默认为单例模式。
  3. 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
  4. 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
  5. 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。

二、IOC的底层原理

1、什么是IOC?

控制反转:使得创建管理对象的权利从以前的程序代码到现在的IOC容器,这样对象的管理只在IOC容器中,这样减低了程序的耦合度。

IOC实质只是一种思想,而真正的实现是依赖注入。

2、Spring IOC的实现机制

实现原理就是:工厂模式和反射机制

interface Fruit {
   public abstract void eat();
 }

class Apple implements Fruit {
    public void eat(){
        System.out.println("Apple");
    }
}

class Orange implements Fruit {
    public void eat(){
        System.out.println("Orange");
    }
}

//工厂模式
class Factory {
    public static Fruit getInstance(String ClassName) {
        Fruit f=null;
        try {
            f=(Fruit)Class.forName(ClassName).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return f;
    }
}

class Client {
    public static void main(String[] a) {
        //生成对象
        Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");
        if(f!=null){
            f.eat();
        }
    }
}

IOC容器能够通过工厂和反射的方式创建出对象,而不是通过new 的方式。

3、IOC支持的功能:

  • 生成对象(通过反射生成的对象)

    ​ 有两种方式可以实现对象的生成

    ​ 1)、基于XML文件

  • 依赖注入(就是注入属性)

  • 依赖检查

  • 自动装备

  • 支持集合

  • 支持初始化方法和销毁方法

  • 支持回调某些方法

4、什么是Bean管理?

bean管理值得就是对象的创建和属性的注入

  • spring创建对象
  • spring注入属性

5、实例化一个Bean

  • 创建一个实体类

  • 创建bean.xml文件,里面是

    <bean id="book" class="com.robin.Spring5.Book"></bean>
    
  • 在主程序中,加载配置文件

     ApplicationContext context=new ClassPathXmlApplicationContext("bean1.xml");
    
  • 创建了一个bean对象

     Library library=context.getBean("library",Library.class);
    

6、依赖注入的三种方式

1)、setter方法
  • 创建一个类,设置它的属性和setter()方法

    package com.robin.spring01.依赖注入;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    
    /**
     * @author robin
     * @email tigerofsky@163.com
     * @create 2020-10-25 17:04
     */
    public class Person {
    
    //    属性值
        private String desc;
    
    //    setter()方法
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "desc='" + desc + '\'' +
                    '}';
        }
    
        public static void main(String[] args){
    
    //        加载配置文件
            ApplicationContext context = new FileSystemXmlApplicationContext("src/bean1.xml");
    
    //        利用反射生成对象
            Person robin = context.getBean("robin", Person.class);
    
            if(robin!=null){
                System.out.println(robin);
                System.out.println("对象创建成功!");
            }
        }
    }
    
  • 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="robin" class="com.robin.spring01.依赖注入.Person">
            <!--bean的属性设置-->
            <property name="desc" value="like play"></property>
        </bean>
    </beans>
    
  • 输出

    Person{desc='like play'}
    对象创建成功!
    
2)、有构造函数
  • 创建一个类,设置属性和有参构造

    package com.robin.spring01.依赖注入;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    
    /**
     * @author robin
     * @email tigerofsky@163.com
     * @create 2020-10-25 17:40
     */
    public class Cat {
    
        private String name;
        private String desc;
    
        public Cat(String name, String desc) {
            this.name = name;
            this.desc = desc;
        }
    
        @Override
        public String toString() {
            return "Cat{" +
                    "name='" + name + '\'' +
                    ", desc='" + desc + '\'' +
                    '}';
        }
    
        public static void main(String[] args){
            //        加载配置文件
            ApplicationContext context = new FileSystemXmlApplicationContext("src/bean2.xml");
    
    //        利用反射生成对象
            Cat cat = context.getBean("cat", Cat.class);
    
            if(cat!=null){
                System.out.println(cat);
                System.out.println("对象创建成功!");
            }
        }
    }
    
    
  • 创建bean2.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="cat" class="com.robin.spring01.依赖注入.Cat">
            <constructor-arg name="name" value="咖啡毛"></constructor-arg>
            <constructor-arg name="desc" value="只会吃饭"></constructor-arg>
        </bean>
    </beans>
    
  • 输出

    Cat{name='咖啡毛', desc='只会吃饭'}
    对象创建成功!
    
3)、接口方式

接口注入由于在灵活性和易用性比较差,现在从Spring4开始已被废弃。

7、IOC容器

1、BeanFactory容器

是Spring最底层呢个的接口,包含各种bean的定义,读取bean配置文件,管理bean的加载,实例化,控制bean的生命周期,维护bean之间的依赖关系

2、ApplicationContext容器

ApplicationContext是BeanFactory的子接口,提供了其他的方法

  • 继承MessageSource
  • 统一的资源文件访问方式
  • 提供在监听器中注册的bean事件
  • 同时加载多个配置文件
  • 加载多个有继承关系的上下文

Bean的加载方式

  • BeanFactory的加载方式:懒加载,只有在使用getBean的时候才对bean进行加载实例化
  • ApplicationContext的加载方式:在容器启动的时候,一次性的创建所有的bean

ApplicationContext相比于BeanFactory的不足就是占用内存,当程序配置的bean比较多的时候,程序启动较慢。

创建方式

BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

注册方式

BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

8、Spring是如何设计容器进行管理对象的

  • BeanFactory,就是一个HashMap,只提供注册和获取的功能,也称为低级容器
  • ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。

9、ApplicationContext通常的实现是什么?

FileSystemXmlApplicationContext :此容器从一个XML文件中加载beans的定义,XML Bean 配置文件的全路径名必须提供给它的构造函数。

ClassPathXmlApplicationContext:此容器也从一个XML文件中加载beans的定义,这里,你需要正确设置classpath因为这个容器将在classpath里找bean配置。

WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。

10、依赖注入

组件之间的依赖关系有容器在应用系统运行期来决定,也就是容器动态的将某种依赖关系的目标对象实例注入到系统中的各个关联的组件之中。

11、解释Spring支持的几种bean的作用域

Spring框架支持以下五种bean的作用域:

  • singleton : bean在每个Spring ioc 容器中只有一个实例。
  • prototype:一个bean的定义可以有多个实例。
  • request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

注意: 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。

12、Spring框架中的单例bean是线程安全的吗?

不是,Spring框架中的单例bean不是线程安全的。

spring 中的 bean 默认是单例模式,spring 框架并没有对单例 bean 进行多线程的封装处理。

13、Spring如何处理线程并发问题?

在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。

ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

14、解释Spring框架中bean的生命周期

创建实例(通过无参构造器创建)----》填充属性----》初始化(自定义初始化方法init-method)----》bean可以使用了----》bean的销毁(自定义销毁方法destroy-method)

在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。下图展示了bean装载到Spring应用上下文中的一个典型的生命周期过程。

img

bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制。

正如你所见,在bean准备就绪之前,bean工厂执行了若干启动步骤。

  • Spring对bean进行实例化;
  • Spring将值和bean的引用注入到bean对应的属性中;
  • 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
  • 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
  • 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
  • 如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;
  • 如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用;
  • 如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
  • 此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
  • 如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。

15、哪些是重要的bean生命周期方法? 你能重载它们吗?

有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。

bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。

16、在spring中可以注入java集合

List、Set、HashMap集合

17、什么是bean装配?

在Spring容器中将bean组装到一起,前提是容器知道bean的依赖关系

  • 自动装配:bean的装配,不要容器的配置,而是通过BeanFactory自动的处理bean之间的依赖
  • 5种自动装配:
    • no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。

    • byName(比较精确):通过bean的名称进行自动装配。bean的名称要和类中的属性值一样

      //对象明是personya,类中的属性值名称也是personya
      <bean id="personya" class="com.robin.spring01.依赖注入.Person"/>
      
      private Person personya;
      
    • byType(比较广泛):通过bean对象的类型和类中属性的类型一致

      //对象类型是Person,类中的属性类型也是Person
      <bean id="person" class="com.robin.spring01.依赖注入.Person"/>
      
      private Person person;
      

      byName 和byType的区别就是一个精确指定,一个广泛指定,但是byType指定的时候,如果出现多个相同类型的对象,就会报错

    • constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。

    • autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

18、使用@Autowired注解自动装配的过程是怎样的

使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。

在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

  • 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
  • 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
  • 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

19、常用的注解

  • @Configuration :作为配置类,代替XML配置文件
  • @Bean注解:作用在方法上,将返回的对象注入到IOC容器中

@Component, @Controller, @Repository, @Service 有何区别?

要先使得上面的注解能够生效,必须要开启组件扫描

    <!--扫描包中的全部类到容器中-->
    <context:component-scan base-package="com.robin.spring01.依赖注入"/>

    <!--默认过滤器不使用,使用自定义的:只加载在包中的Controller注解的类-->
    <context:component-scan base-package="com.robin.spring01.依赖注入" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--使用默认的过滤器,不加载包中带有Service注解的类到容器-->
    <context:component-scan base-package="com.robin.spring01.依赖注入">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>

@Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。

@Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。

@Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。

@Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。

@Autowired和@Resource之间的区别

  • @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
  • @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

@Qualifier 注解有什么作用

当您创建多个相同类型的 bean 并希望仅使用属性装配其中一个 bean 时,您可以使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。

完全注解开发:不使用XML来配置,使用一个Configuration配置类代替XMl文件

@Configuration
@ComponentScan(basePackages = "com.robin")
public class TestConfig {
    
}
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Dog.class);

    Cat cat = context.getBean("cat", Cat.class);
    System.out.println(cat);

20、Spring JDBC

JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate

spring JDBC API 中存在哪些类?

JdbcTemplate

SimpleJdbcTemplate

NamedParameterJdbcTemplate

SimpleJdbcInsert

SimpleJdbcCall

JdbcTemplate是什么

JdbcTemplate 类提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。

21、Spring的事务

Spring支持的事务管理类型, spring 事务实现方式有哪些?

事务管理的底层使用AOP

Spring支持两种类型的事务管理:

编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。

声明式事务管理(常用):这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。

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

Spring事务的实现方式和实现原理

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

说一下Spring的事务传播行为

spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。

① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

22、普通bean和FactoryBean的区别?

  • 普通bean:在配置文件中定义的什么类,返回的就是什么类
<bean id="cat" class="com.robin.spring01.依赖注入.Cat">
</bean>
        //        加载配置文件
        ApplicationContext context = new FileSystemXmlApplicationContext("src/bean2.xml");

//        利用反射生成对象
        Cat cat = context.getBean("cat", Cat.class);

  • 工厂bean:在配置文件中定义的类可以和返回的类型不一样
    • 第一步创建类,让这个类作为工厂bean,实现接口FactoryBean
    • 第二步实现接口里面的方法,在实现的方法中定义放回的类型
package com.robin.spring01.依赖注入;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * @author robin
 * @email tigerofsky@163.com
 * @create 2020-10-27 9:45
 */
public class ReturnClass implements FactoryBean<Person> {
    @Override
    public Person getObject() throws Exception {
        Person person = new Person();
        person.setDesc("测试FactoryBean");
        return person;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
<bean id="mybean" class="com.robin.spring01.依赖注入.ReturnClass"/>
ApplicationContext context2 = new FileSystemXmlApplicationContext("src/bean1.xml");
Person person = context2.getBean("mybean", Person.class);
System.out.println(person);

23、Spring中bean的作用域

  • singleton:默认是单实例,在IOC容器中只能有一个这种类型的bean
  • prototype:多实例对象,可以创建多个

24、singleton和prototype的区别?

  • singleton:在加载配置文件的时候就会创建
  • prototype:在调用getBean()的时候才会创建

三、AOP的原理

  • 静态动态代理
    • AspectJ
      • 注解版
        • AspectJ依赖
        • 添加类到容器中
        • 开启相关注解
        • 创建一个代理类,创建通知,配置切面
      • 配置版
  • 动态动态代理
    • JDK动态代理:基于接口的
    • CGLib 动态代理:没有接口的

1、什么是AOP?

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

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

2、Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?

AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理是Spring AOP

(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

3、JDK动态代理和CGLIB动态代理

  • JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
  • 如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

4、一个简单的AOP实例(JDK动态代理实现)

1)、创建一个接口

package com.robin.spring01.AOP;

/**
 * @author robin
 * @email tigerofsky@163.com
 * @create 2020-10-27 11:58
 */
public interface UserDao {

    public int add(int a,int b);

    public int update();
}

2)、实现这个接口

package com.robin.spring01.AOP;

/**
 * @author robin
 * @email tigerofsky@163.com
 * @create 2020-10-27 11:58
 */
public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        System.out.println("add方法执行了");
        return a+b;
    }

    @Override
    public int update() {
        return 0;
    }
}

3)创建一个代理对象Proxy

package com.robin.spring01.AOP;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @author robin
 * @email tigerofsky@163.com
 * @create 2020-10-27 11:59
 */
public class TestProxy {

    public static void main(String[] args){

        Class[]interfaces = {UserDao.class};
        UserDao userDao = new UserDaoImpl();

        /**
         * 第一个参数是类加载器
         * 第二个参数是,增强方法所在的类,这个类实现的接口,支持多个接口
         * 第三个参数是,实现一个接口InvocationHandler,创建代理对象,写增强内容
         */
        UserDao dao = (UserDao) Proxy.newProxyInstance(UserDao.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        int sum = dao.add(2, 5);
        System.out.println("sum="+sum);
    }
}
package com.robin.spring01.AOP;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author robin
 * @email tigerofsky@163.com
 * @create 2020-10-27 12:08
 */
public class UserDaoProxy implements InvocationHandler {
    private UserDao userDao;
    public UserDaoProxy(UserDao userDao) {
        this.userDao = userDao;
    }

    /*
    InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法之前
        System.out.println("方法执行之前"+ Arrays.toString(method.getParameterTypes()) + Arrays.toString(method.getExceptionTypes()) +"方法中传递的参数"+ Arrays.toString(args));

        //执行方法
        Object res = method.invoke(userDao, args);


        //方法之后
        System.out.println("方法执行之后"+userDao);

        return res;
    }
}

5、AOP里面的几个名词解释

  • 连接点:类中哪些方法可以被增强,这些方法称之为连接点
  • 切入点:实际上真正被增强的方法
  • 通知:实际增强的逻辑部分称为通知
  • 切面:把通知应用到切入点的过程

6、实现AspectJ的AOP(注解版)

  1. 引入相关的jar包
  • 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

切入点表达式(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强(2)语法结构:execution([权限修饰符] [返回类型] [类全路径] 方法名称 )

  1. 创建一个类
    package com.robin.spring01.AspectJ;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @author robin
     * @email tigerofsky@163.com
     * @create 2020-10-27 17:03
     */
    //被增强类
    
    @Component
    public class User {
        public void display(){
            System.out.println("display方法执行了");
        }
    }
    
  2. 创建一个代理类
    package com.robin.spring01.AspectJ;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    /**
     * @author robin
     * @email tigerofsky@163.com
     * @create 2020-10-27 17:04
     */
    
    //增强类
    @Component
    @Aspect
    public class UserProxy {
    
        @Before(value = "execution(* com.robin.spring01.AspectJ.User.display(..))")
        public void before(){
            System.out.println("before方法之前执行了");
        }
    
        @After(value = "execution(* com.robin.spring01.AspectJ.User.display(..))")
        public void after(){
            System.out.println("After方法之前执行了");
        }
    
        @AfterReturning(value = "execution(* com.robin.spring01.AspectJ.User.display(..))")
        public void afterReturning(){
            System.out.println("AfterReturning方法之前执行了");
        }
    
    
        @AfterThrowing(value = "execution(* com.robin.spring01.AspectJ.User.display(..))")
        public void afterThrowing(){
            System.out.println("AfterThrowing方法之前执行了");
        }
    
    
        @Around(value = "execution(* com.robin.spring01.AspectJ.User.display(..))")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("Around之前执行了");
            proceedingJoinPoint.proceed();
            System.out.println("Around之前执行了");
        }
    
    }
    
  3. 创建一个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"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           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
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--开启注解扫描-->
        <context:component-scan base-package="com.robin.spring01.AspectJ"/>
    
    
        <!--开启aop的自动配置代理-->
        <aop:aspectj-autoproxy/>
    
    </beans>
    
  4. 创建测试类
    package com.robin.spring01.AspectJ;
    
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    
    /**
     * @author robin
     * @email tigerofsky@163.com
     * @create 2020-10-27 17:20
     */
    public class Test {
    
    
        @org.junit.Test
        public void testAspectProxy(){
            FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("src/bean3.xml");
            User user = context.getBean("user", User.class);
            user.display();
        }
    
    }
    

    在上面的增强类中的切入点表达式都是重复的,这里可以做一个切入点表达式的抽取工作

    //抽取功能的切入点表达式
    @Pointcut(value = "execution(* com.robin.spring01.AspectJ.User.display(..))")
    public void pointCut(){
    
    }
    

    在对于多个增强类对一个方法增强的时候可以设置执行的顺序:设置的值越小,优先级越高

    @Order(1)
    public class OtherProxy {
    
    @Order(4)
    public class UserProxy {
    

7、实现AspectJ的AOP(配置文件版)

  1. 创建被增强类
    package com.robin.spring01.AspectJXml;
    
    /**
     * @author robin
     * @email tigerofsky@163.com
     * @create 2020-10-27 18:06
     */
    public class OtherUser {
        public void display(){
            System.out.println("display方法执行了");
        }
    }
    
  2. 创建代理类
    package com.robin.spring01.AspectJXml;
    
    import org.aspectj.lang.annotation.*;
    
    /**
     * @author robin
     * @email tigerofsky@163.com
     * @create 2020-10-27 17:04
     */
    
    //增强类
    public class UserProxy {
    
        @Before(value = "execution(* com.robin.spring01.AspectJ.User.display(..))")
        public void before(){
            System.out.println("before方法之前执行了");
        }
    
    
    }
    
  3. 创建配置文件
    <?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"
           xmlns:aop="http://www.springframework.org/schema/aop"
           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
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    
        <bean id="otherUser" class="com.robin.spring01.AspectJXml.OtherUser"/>
        <bean id="userProxxy" class="com.robin.spring01.AspectJXml.UserProxy"/>
    
        <aop:config>
            <aop:pointcut id="pointcut" expression="execution(* com.robin.spring01.AspectJXml.OtherUser.display(..))"/>
    
            <aop:aspect ref="userProxxy">
                <aop:before method="before" pointcut-ref="pointcut"/>
            </aop:aspect>
        </aop:config>
    
    </beans>
    
  4. 创建测试类
    package com.robin.spring01.AspectJXml;
    
    import com.robin.spring01.AspectJ.User;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    
    /**
     * @author robin
     * @email tigerofsky@163.com
     * @create 2020-10-27 17:20
     */
    public class Test {
    
    
        @org.junit.Test
        public void testAspectProxy(){
            FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("src/bean4.xml");
            OtherUser user = context.getBean("otherUser", OtherUser.class);
            user.display();
        }
    
    }
    

四、Spring JDBC Template

1、添加相关jar依赖

mysql的驱动、druid的数据源,spring的orm,jdbc,tx等

2、创建数据源

<!--配置durid的数据源-->
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="jdbc://192.168.56.111:3306/book"/>
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>


<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="datasource"/>
</bean>


<context:component-scan base-package="com.robin.spring01.JDBCTemplate"/>

3、创建Dao,Service

package com.robin.spring01.JDBCTemplate.Dao;

import com.robin.spring01.JDBCTemplate.entiy.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

/**
 * @author robin
 * @email tigerofsky@163.com
 * @create 2020-10-27 19:40
 */
@Repository
public class BookDaoImpl implements BookDao {

    @Autowired
    JdbcTemplate jdbcTemplate;
    @Override
    public void look(){
        System.out.println("look book");
    }

    @Override
    public List<Book> search() {
        String sql="Select * from book where id=?";

        List<Book> query = jdbcTemplate.query(sql,new  BeanPropertyRowMapper<Book>(Book.class));
        return query;
    }


}

五、Spring事务管理

编程式事务:使用代码实现的事务管理

  • 开启事务
  • 执行业务操作
  • 提交事务
  • 出现异常,事务回滚

1、声明式事务(注解版)

1、创建一个事务管理器
<!--创建事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource"/>
</bean>
2、开启事务注解
<!--开启事务管理注解 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
3、使用事务注解
@Transactional
public class BookService {

2、事务的参数

  • propagation:事务的传播行为,不同的事务的事物之间相互调用

    ① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

    ② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。

    ③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

    ④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

    ⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    ⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

    ⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

  • isolation:

    • ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
    • ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
    • ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
    • ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
    • ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
  • timeout:事务需要在一定时间内执行,超时会回滚,默认是-1,表示不回滚,单位为s

  • readOnly:为true的时候,是只读操作;为false的时候可以是读写

  • rollbackFor:设置出现哪些异常,要回滚

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

3、声明式事务(XML版)

1、配置事务管理器

<!--创建事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource"/>
</bean>

2、配置通知

<!--配置通知:要在按个方法上进行事务-->
<tx:advice id="txadvice">
    <tx:attributes>
        <tx:method name="searchAll" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

3、配置切入点和切面

<!--配置切入点和切面-->
<aop:config>
    <aop:pointcut id="pt" expression="execution(* com.robin.spring01.JDBCTemplate.Service.BookService.searchAll(..))"/>
    <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

4、事务的完全注解开发

package com.robin.spring01.Transational;

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;

/**
 * @author robin
 * @email tigerofsky@163.com
 * @create 2020-10-27 22:15
 */
@Configuration
@ComponentScan(value = "com.robin.spring01.JDBCTemplate")
@EnableTransactionManagement
public class TestTX {

    //创建数据源连接池
    @Bean
    public DruidDataSource getDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:localhost:3306/book");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

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

    //创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无敌的黑星星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值