1、Spring

1.1、简介

  • Spring:给软件行业带来了春天!
  • 2002年,首次推出了Spring框架的雏形:interface 21框架
  • 2004年3月24号,Spring框架以interface21为基础,发布了正式版本1.0版本
  • Rod Johnson,Spring framework创始人
  • spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!

通过切面和模版减少样板式代码

  • SSH:Struct2 + Spring + Hibernate
  • SSM:SpringMVC+Spring+Mybatis

官网:https://spring.io/

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

Github:https://github.com/spring-projects/spring-framework

官方文档:https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#spring-core

  1. <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-webmvc</artifactId>
  5. <version>5.2.9.RELEASE</version>
  6. </dependency>
  7. <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
  8. <dependency>
  9. <groupId>org.springframework</groupId>
  10. <artifactId>spring-jdbc</artifactId>
  11. <version>5.2.9.RELEASE</version>
  12. </dependency>

1.2、优点

  • Spring是一个开源的免费的框架(容器)!
  • Spring是一个轻量级的、非入侵式的框架!
  • 控制反转,面向切面编程(AOP)
  • 支持事务的处理,对框架整合的支持

总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架

1.3、Spring的组成

截屏2021-01-28 13.13.51.png

1.4、拓展

Spring官网介绍:现代化的java开发!说白了就是基于spring的开发

截屏2021-01-28 13.15.57.png

  • Spring Boot:
    • 一个快速开发的脚手架。
    • 基于Spring Boot可以快速的开发单个微服务
    • 约定大于配置!
  • Spring Cloud
    • SpringCloud是基于SpringBoot实现的。

目前大多数公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!承上启下的作用!

弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱

2、IOC理论的推导

  1. UserDao接口
  2. UserDaoImpl实现类
  3. UserService业务接口
  4. UserServiceImpl实现类

在之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!

假设程序代码量十分大,修改一次代码的成本价十分昂贵!

例如,如果我们需要实现从Oracle数据库获取数据而不是Mysql数据库获取数据,那么按照原来的代码接口,我们不但需要去创建一个Oracle的Dao接口还需要在Service的实现类中new一个Oracle的实现类,最后再到Servlet层修改代码,这样的话需要修改的代码量非常大!!

解决方法

我们使用一个set接口实现

private UserDao userDao;

//利用set进行动态实现值的注入!
public void setUserDao(userDao userDao){
  this.userDao = userDao;
}
  • 之前,程序是主动创建对象!控制权在程序员手上
  • 使用set注入后,程序不再具有主动性,而是变成了被动的接受对象。

那么我们去测试类实现一下这个方法

public class myTest {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
                //根据需求创建Dao接口
       ((UserServiceImpl) userService).setUserDao(new MysqlDaoImpl());
       userService.getUser();
    }
}

这种思想,从本质上解决了问题,程序员不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注在业务的实现上。这是IOC的原型!

移交主动权(程序—->用户)

IOC的本质

对象的创建交给外部容器完成,这就叫做控制反转

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

3、HelloSpring

Hello.java

public class Hello {

    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
}

beans.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">

    <!--使用Spring来创建对象,在Spring中这些都成为Bean

        类型 变量名 = new 类型();
        Hello hello = new Hello();

        id = 变量名
        class = new的对象
        property 相当于给对象中的属性设置一个值
        bean = 对象  new Hello();
    -->
    <bean id="hello" class="com.roderick.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>

</beans>

myTest.java

public class myTest {
    public static void main(String[] args) {
        //获取Spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //我们的对象都在Spring中管理了,我们要使用,直接去里面取出来就可以!
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}
输出:
Hello{str='Spring'}

思考:

  • Hello 对象是谁创建的 ?
    • hello 对象是由Spring创建的
  • Hello 对象的属性是怎么设置的 ?
    • hello 对象的属性是由Spring容器设置的

这个过程就叫控制反转 :

  • 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
  • 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .

依赖注入 : 就是利用set方法来进行注入的.

IOC是一种编程思想,由主动的编程变成被动的接收

到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,就是:对象由Spring来创建,管理,装配!

4、IOC创建对象的方式

  1. 使用无参构造创建对象,默认!

    <bean id="hello" class="com.roderick.pojo.Hello">
    <property name="name" value="无参构造"/>
    </bean>
    
  2. 假设我们要使用有参构造方法

    1. 下标赋值(例如有参构造函数中有多个变量,按照顺序下标从0开始

      <!--第一种,下标赋值-->
      <bean id="user" class="com.roderick.pojo.User">
      <constructor-arg index="0" value="雨落2"/>
      </bean>
      
    2. 类型赋值(不建议使用)

      <!--第二种,类型赋值-->
      <bean id="user" class="com.roderick.pojo.User">
      <constructor-arg type="java.lang.String" value="yuluo"/>
      </bean>
      
    3. 直接通过参数名赋值

      <bean id="user" class="com.roderick.pojo.User">
      <constructor-arg name="name" value="name赋值"/>
      </bean>
      
beans.xml
<bean id="user" class="com.roderick.pojo.User">
    <constructor-arg name="name" value="name赋值"/>
</bean>
<bean id="user2" class="com.roderick.pojo.UserT">

</bean>

public class myTest {
    public static void main(String[] args) {
        //Spring容器,
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        User user2= (User) context.getBean("user");
        user.show();
        System.out.println(user==user2);
    }
}
输出结果:
userT被创建了
User:name赋值有惨构造
true
  • 这里获取Spring容器后,只获取了user的bean对象而没有获取userT的bean对象
  • 但是在输出结果中,userT的无参构造方法也被调用了
  • 并且创建两次userBean,他们进行==判断后返回的结果是true
    • 说明在获取Spring容器后,内存中就只有一个userBean提供获取【单例模式】

总结:在配置文件加载的时候,容器中管理的对象就已经初始化了!【单例模式的对象创建时间6.4

5、Spring配置

5.1、别名

<bean id="user" class="com.roderick.pojo.User">
  <constructor-arg name="name" value="name赋值"/>
</bean>

<alias name="user" alias="NickName"/>
User user = (User) context.getBean("user");
User user2= (User) context.getBean("NickName");

System.out.println(user==user2);
输出结果:true

5.2、Bean的配置

<!--  
      id:bean的唯一标识符,也就是相当于对象名
      class:bean对象所对应的全限定名: 包名+类型
      name:相当于别名,可以同时取多个别名
     -->
<bean id="userT" class="com.roderick.pojo.UserT" name="user2,u2,userT2">
  <property name="name" value=":)"/>
</bean>

5.3、import

import一般用于团队开发,可以将多个配置文件,导入合并为一个

  • 假设,项目中有多个成员进行开发,不同成员使用了多个不同的bean.xml
  • 我们可以使用import,将所有beans.xml合并到applicationContext.xml
  • 最后我们使用applicationContext即可
<?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">

    <import resource="beans.xml"/>
    <import resource="beans2.xml"/>
      ....
</beans>

6、依赖注入

Dependency Injection

对象之间的依赖关系,其实就是给对象上的属性赋值!因为对象上有其他对象的变量,因此存在了依赖。

6.1、构造器注入

Constructor

<bean id="user" class="com.roderick.pojo.User">
    <constructor-arg name="name" value="name赋值"/>
</bean>

6.2、set方式注入【重点】

  • 依赖注入:Set注入!
    • 依赖:bean对象的创建依赖于容器
    • 注入:bean对象中的所有属性由容器来注入

【环境搭建】

  1. 复杂类型

    public class Address {
     private String address;
    
     public String getAddress() {
         return address;
     }
    
     public void setAddress(String address) {
         this.address = address;
     }
    }
    
  2. 真实测试对象

    public class Student {
     private String name;
     private Address address;
     private String[] books;
     private List<String> hobby;
     private Map<String,String> card;
     private Set<String> games;
     private Properties info;
     private String wife;
    }
    
  3. beans.xml ```xml <?xml version=”1.0” encoding=”UTF-8”?>

    《java入门到入土》 《spring》 《mybatis》 打游戏 滑板 看电影 Lol CSGO speed 1 2 3 4


4. 测试类
```java
public class myTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student.toString());

        /*
            Student{
            name='雨落',
            address=Address{address='浙江'},
            books=[《java入门到入土》, 《spring》, 《mybatis》],
            hobby=[打游戏, 滑板, 看电影],
            card={身份证=2131231332232, 学生证=201805023333},
            games=[Lol, CSGO, speed],
            info={password=4, url=2, driver=1, username=3},
            wife='null'
            }
        */
    }
}

6.3、拓展方式注入(c/p命名空间)

我们可以使用p命名空间和c命名空间进行注入

官方解释:

使用:

<?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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--p命名空间注入,可以直接注入属性值-->
<!--p命名空间对应set注入-->
<bean id="user" class="com.roderick.pojo.User"
      p:name="雨落"
      p:age="22"
      />

<!--c命名空间注入,可以通过构造器注入constructor-args-->
<!--c命名空间对应构造器注入-->
<bean id="user2" class="com.roderick.pojo.User"
      c:age="222"
      c:name="雨落"
      />

测试:

@Test
public void test2(){
  ApplicationContext context = new ClassPathXmlApplicationContext("UserBean.xml");
  //        User user = (User) context.getBean("user");
  User user = context.getBean("user2", User.class);
  System.out.println(user);
}

注意点

不能直接使用,需要导入xml约束

xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

6.4、Bean Scope

  • singleton 单例模式(Spring默认机制
    • 内存中只有一个对象被用于获取

Spring - 图3

  • prototype 原型模式
    • 每次从容器中get的时候都会产生一个新对象

Spring - 图4

  • 其余的request、session、application、websocket 这些只能在web开发中使用

Tips:

scope属性除了控制对象是单例还是多例的,还控制着对象创建的时间!

  • 当使用singleton【单例】的时候,对象在获得IOC容器之前就已经创建了
  • 当使用prototype的时候,对象在使用的时候才创建

6.5、Bean创建细节总结

 /**
   * 1) 对象创建: 单例/多例
   *  scope="singleton", 默认值, 即 默认是单例 【service/dao/工具类】
   *  scope="prototype", 多例;        【Action对象】
   * 
   * 2) 什么时候创建?
   *    scope="prototype"  在用到对象的时候,才创建对象。
   *    scope="singleton"  在启动(容器初始化之前), 就已经创建了bean,且整个应用只有一个。
   * 3)是否延迟创建
   *    lazy-init="false"  默认为false,  不延迟创建,即在启动时候就创建对象
   *    lazy-init="true"   延迟初始化, 在用到对象的时候才创建对象
   *    (只对单例有效)
   * 4) 创建对象之后,初始化/销毁
   *    init-method="init_user"       【对应对象的init_user方法,在对象创建之后执行 】
   *    destroy-method="destroy_user"  【在调用容器对象的destroy方法时候执行,(容器用实现类)】
   */

7、Bean的自动装配

  • 自动装配是Spring满足Bean依赖的一种方式!
  • Spring会在上下文中自动寻找,并自动给bean装配属性

在Spring中有三种自动装配的方式

  1. 在xml中显示的配置
  2. 在java中显示配置
  3. 隐式的自动装配bean【重要!】

7.1、测试

  • 搭建环境
    • 一个人的对象拥有两个宠物对象

7.2、ByName自动装配

bean id必须和set方法参数一致

<bean id="cat233" class="com.roderick.pojo.Cat"/>
<bean id="cat" class="com.roderick.pojo.Cat"/>
<bean id="dog233" class="com.roderick.pojo.Dog"/>
<bean id="dog" class="com.roderick.pojo.Dog"/>
<!--
        byName原理:会自动在容器上下文查找,和自己对象set方法后面的值对应的beanID
-->
<bean id="people" class="com.roderick.pojo.People" autowire="byName">
  <property name="name" value="雨落1"/>
</bean>

如上例子,通过byName自动装配,他会在配置文件中寻找id为cat和dog的bean来对应People类中的cat和dog变量

即使该配置文件中有多个class为Cat和Dog的bean,但是byName只会匹配id与cat和dog对应的bean

7.3、ByType自动装配

<!--    <bean id="cat233" class="com.roderick.pojo.Cat"/>-->
<bean id="cat" class="com.roderick.pojo.Cat"/>
<!--    <bean id="dog233" class="com.roderick.pojo.Dog"/>-->
<bean id="dog" class="com.roderick.pojo.Dog"/>
<!--
        byType原理:会自动在容器上下文寻找,和自己对象属性类型相同的Bean
-->
<bean id="people" class="com.roderick.pojo.People" autowire="byType">
  <property name="name" value="雨落1"/>
</bean>

byType自动装配会匹配与dog和cat相同class的bean,但是在配置文件中类型必须是唯一的

如果有两个class相同但是id不同的bean,就无法通过byType来自动装配

小结

  • byName的时候,需要保证所有bean的id唯一,并且beanID需要和自动注入的属性的set方法值一致
  • byType的时候,需要保证Bean的class唯一,并且bean需要和自动注入的属性的类型一致!

7.4、使用注解实现自动装配

jdk1.5支持的注解,Spring2.5支持注解

要使用注解须知:

  1. 导入约束. context约束
  2. 配置注解的支持<conntext:annotation-config/>【重点】

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

@Autowired

直接在类中的属性上使用即可!也可以在set方式上使用!

使用Autowired 我们可以不用编写Set方法,前提是这个自动装配的属性在IOC(Spring)容器中存在,且符合byName。【比如上诉的Cat和Dog,他们都在Spring中被注册为bean】

科普:

@Nullable 字段标记了这个注解,说明这个字段可以为null
public @interface Autowired {
    boolean required() default true;
}

测试代码:

//如果显示的定义了Autowired属性为false,说明这个对象可以为null。否则不允许为空
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
  • 如果有多个类型相同的bean
<bean id="dog233" class="com.roderick.pojo.Dog"/>
<bean id="dog" class="com.roderick.pojo.Dog"/>
@Autowired
@Qualifier(value = "dog233")
private Dog dog;

拓展

  • @Resource(name = xxx)注解
    • 支持byName和byType查找
    • 如果有多个bean名字不同他会寻找name符合的那个
    • 如果所有bean名字都不同,他会寻找唯一的类型符合的那个(未加(name=xxx)的情况下)
@Resource(name = "cat233")
private Cat cat;
@Autowired
@Qualifier(value = "dog233")
private Dog dog;
private String name;

小结:

@Resource和@Autowired的区别

  • 都是用来自动装配的,都可以放在属性字段上
  • @Autowired通过byType的方式实现,而且必须要求这个对象存在
  • @Resource默认通过byName方式存在,如果找不到名字,就通过byType实现!如果两个都找不到的情况下就报错!
  • 执行顺序不同:
    • @Autowired通过byType
    • @Resource通过byName再通过byType

8、使用注解开发

在Spring4之后,要使用注解开发,必须要保证aop的包导入了

使用注解需要导入context约束,增加注解的支持

<?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
        https://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.roderick.pojo"/>
    <context:annotation-config/>
</beans>
  • bean

    //等价于<bean id="user" class="com.roderick.pojo.User"/>
    @Component //组件
    public class User {
    ...
    }
    
  • 属性如何注入

    //等价于<bean id="user" class="com.roderick.pojo.User"/>
    @Component //组件
    public class User {
    
      public String name;
    
      //相当于bean标签里的<property name="name" value="Roderick"/>
      @Value("Roderick")
      public void setName(String name) {
          this.name = name;
      }
    }
    
  • 衍生的注解

    • @Component有几个衍生注解,我们在web开发中会按照mvc三层架构分层
    • 扫描包后这些注解功能等价于@Component,都是将类注入到Spring容器中装配Bean
      • dao 【@Repository】
      • service 【@Service】
      • controller 【@Controller】
  • 自动装配 ```
  • @Autowired:自动装配通过类型、名字
    如果Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value = xx)

  • Nullable 字段标记了这个注解,说明这个字段可以为null

  • @Resource : 自动装配通过名字、类型
    可添加(name = xxx)指定唯一bean ```

  • 作用域

    @Scope("singleton") //prototype...
    public class User {
    ...
    }
    
  • 小结

    • xml与注解:
      • xml更加万能,适用于任何场合!维护简单方便
      • 注解 不是自己的类使用不了,维护相对复杂
    • xml与注解的最佳实践:
      • xml用来管理bean
      • 注解只负责完成属性的注入
      • 我们在使用的过程中,只需要注意个问题:要让注解生效,必须要开启注解支持以及包的扫描
        <!--指定要扫描的包,这个包下的注解就会生效-->
        <context:component-scan base-package="com.roderick.pojo"/>
        <context:annotation-config/>
        

9、使用Java的方式配置Spring

我们现在要完全不使用Spring的xml配置,全权交给Java来做!

JavaConfig 是Spring的一个子项目,在Spring4之后,成为了核心功能

  • 实体类
//这个注解的意思就是说明这个类被Spring接管了,注入到容器中
@Component
public class User {
    private String name;

    public String getName() {
        return name;
    }

    @Value("雨落") //属性注入值
    public void setName(String name) {
        this.name = name;
    }

    toString...
}
  • 配置文件
//相当于<beans>... </beans>标签
@Configuration //也会被Spring容器托管,注册到容器中,因为他本身就带有@Component
@ComponentScan("com.roderick.pojo")
@Import(myConfig2.class)
public class myConfig {
    //注册一个bean,就相当于我们之前写的一个bean标签
    //这个方法的名字就相当于 bean标签的 id属性
    //这个方法的返回值就相当于 bean标签的 class属性
    @Bean
    public User user(){
        return new User;  //返回要注入到bean的对象!
    }
}
  • 测试类
public class myTest {
    public static void main(String[] args) {
        //如果完全使用了配置类方式去做,我们就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象来加载
        ApplicationContext context = new AnnotationConfigApplicationContext(myConfig.class);
        User getUser = context.getBean("user", User.class);
        System.out.println(getUser.getName());
    }
}

总结:

  • 使用Java方式配置有两种途径
    • 一是创建一个Config.java,在里面添加@Bean的方法获取对象
    • 二是在User类中加上@Component注解,然后在Config.java中加上扫描包的注解@ComponentScan(“com.roderick.pojo”)
  • 这种纯java的配置方式,在SpringBoot中随处可见!

10、代理模式

为什么要学习代理模式?因为这就是SpringAOP的底层

代理模式的分类:

  • 静态代理
  • 动态代理

image-20210130154644923.png

10.1、静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,一般会做一些附属操作
  • 客户:访问代理对象的人

代码步骤

  • 接口

    //租房
    public interface Rent {
    
      public void rent();
    }
    
  • 真实角色

    //房东
    public class Host implements Rent {
    
      public void rent() {
          System.out.println("房东要出租房子");
      }
    }
    
  • 代理角色

    public class Proxy implements Rent {
      private Host host;
    
      public  Proxy(){
      }
    
      public Proxy(Host host) {
          this.host = host;
      }
    
      public void rent() {
          seeHouse();
          host.rent();
          fee();
      }
    
      //看房
      public void seeHouse(){
          System.out.println("中介带你看房");
      }
    
      //收中介费
      public void fee(){
          System.out.println("收中介费");
      }
    }
    
  • 客户端访问代理角色

    public class Client {
      public static void main(String[] args) {
          //房东要出租房子
          Host host=new Host();
          //代理(中介帮房东租房子)(代理角色一般会有一些附属操作)
          Proxy proxy = new Proxy(host);
    
          //不用面对房东,直接找中介租房即可
          proxy.rent();
      }
    }
    

优缺点

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公业业务就交给了代理角色!实现了业务的分工
  • 公共业务发生扩展,方便集中管理!

缺点:

  • 一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率会变低

详解

聊聊aop

image-20210130161521908.png
代码:

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("add");
    }

    public void delete() {
        System.out.println("delete");
    }

    public void update() {
        System.out.println("update");
    }

    public void query() {
        System.out.println("query");
    }
}
public class UserServiceProxy implements UserService {
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    public void add() {
        log("add");
        userService.add();
    }

    public void delete() {
        log("delete");
        userService.delete();
    }

    public void update() {
        log("update");
        userService.update();
    }

    public void query() {
        log("query");
        userService.query();
    }

    //日志方法
    public void log(String msg){
        System.out.println("[Debug]执行了:"+msg);
    }
}
public class Client {
    public static void main(String[] args) {
        UserServiceImpl service = new UserServiceImpl();

        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(service);

        proxy.add();
    }
}

10.2、动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是我们直接写好的!
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口——JDK动态代理 【这里使用】
    • 基于类:cglib
    • java字节码实现:JAVAssit

需要了解两个类: Proxy(代理)、InvocationHandler(调用处理程序)

InvocationHandler

java.lang.reflect. [Interface]

  • InvocationHandler 是由代理实例的

Proxy

java.lang.reflect

  • Proxy提供了一个静态方法用来创建动态代理类和实例

实现:

//用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成得到代理类
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    //处理代理实例并返回结果
    //method是指在Client中使用的方法,args获取他传递的参数,proxy获取调用方法的代理类
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName()); //从这里可以看出代理模式是基于反射的
        //动态代理的本质,就是使用反射机制实现
        Object result = method.invoke(target, args);//执行被代理的接口的方法

        return result;
    }

    public void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}

测试:

  • 在测试类中用到了UserServiceImpl和UserService
  • 他们的关系是,UserServiceImpl是实现UserService接口的实现类
  • pih.getProxy方法就是根据真实角色实现的接口来得到代理类的!!
public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userService = new UserServiceImpl();
        //代理处理类
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        //获得代理角色
        pih.setTarget(userService); //设置要代理的对象
        /*
        通过pih.setTarget(userService)传入的userService设置target
        */
        //动态生成代理类
        UserService proxy = (UserService) pih.getProxy();
        // return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(), this);
        // pih中的getProxy方法return类上面的代码,其中参数通过target.getClass().getInterfaces()获取到被代理的接口

        proxy.add();
    }
}

动态代理的好处

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公业业务就交给了代理角色!实现了业务的分工
  • 公共业务发生扩展,方便集中管理!
  • 一个动态代理类代理的是一个接口,一般就对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可

11、AOP

11.1、什么是AOP

AOP可以简单理解为:在执行某些代码前,执行另外的代码

11.2、Aop在Spring中的作用

提供声明式事务;允许用户自定义切面

11.3、使用Spring实现Aop

【重点】使用AOP织入,需要导入一个依赖包

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.4</version>
</dependency>
  • 方式一:使用Spring的API接口【主要是SpringAPI接口实现

切入方法

import org.springframework.aop.MethodBeforeAdvice;
public class Log implements MethodBeforeAdvice {

  //method:要执行的目标对象的方法
  //args:参数
  //target:目标对象
  public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println("【log】"+target.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {

//returnValue: 返回值
public void afterReturning(Object returnValue, Method method, Object[] objects, Object o1) throws Throwable {
    System.out.println("【AfterLog】执行了"+method.getName()+"方法,返回结果为:"+returnValue);
  }
}

applicationContext.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: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/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--注册bean-->
    <bean id="userServiceImpl" class="com.roderick.service.UserServiceImpl"/>
    <bean id="log" class="com.roderick.log.Log"/>
    <bean id="afterLog" class="com.roderick.log.AfterLog"/>

    <!--方式一:使用衍生Spring API接口-->
    <!--配置aop:需要导入aop的约束-->
    <aop:config>
        <!--切入点:  expression:表达式
        execution(要执行的位置! * * * * *)->修饰词 返回值 类名 方法名 参数
        -->
        <aop:pointcut id="pointcut" expression="execution(* com.roderick.service.UserServiceImpl.*(..))"/>

        <!--执行环绕增强!-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

测试类

public class myText {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    //aop动态代理代理的是接口,
    UserService userService = (UserService) context.getBean("userServiceImpl");
    userService.add();
  }
}
输出结果:
【log】com.roderick.service.UserServiceImpl的add被执行了
add...
【AfterLog】执行了add方法,返回结果为:null
  • 方式二:用自定义类来实现aop【主要是切面定义
public class DiyPointCut {
    public void before(){
        System.out.println("=====方法执行前=====");
    }

    public void after(){
        System.out.println("=====方法执行后=====");
    }
}
<!--方式二:自定义类-->
<bean id="diy" class="com.roderick.diy.DiyPointCut"/>

<aop:config>
  <!--自定义切面: ref 要引用的类-->
  <aop:aspect ref="diy">
    <!--切入点pointcut (需要切入的位置)-->
    <aop:pointcut id="point" expression="execution(* com.roderick.service.UserServiceImpl.*(..))"/>
    <!--通知(执行的方法)-->
    <aop:before method="before" pointcut-ref="point"/>
    <aop:after method="after" pointcut-ref="point"/>
  </aop:aspect>
</aop:config>
  • 方式三:使用注解实现
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//方式三: 使用注解方式实现AOP
@Aspect  //标注这个类为一个切面
public class AnnotationPointCut {
    @Before("execution(* com.roderick.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("===方法执行前===");
    }

    @After("execution(* com.roderick.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("===方法执行后===");
    }

    //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* com.roderick.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");

        System.out.println(jp.getSignature());//获得签名 输出
        //执行方法
        Object proceed = jp.proceed();

        System.out.println("环绕后");
    }
}
 <!--方式三-->
<bean id="annotationPointCut" class="com.roderick.diy.AnnotationPointCut"/>
<!--开启注解支持     JDK(默认)[proxy-target-class="false"]  cglib[proxy-target-class="true"]-->
<aop:aspectj-autoproxy />

注意:

  • 注解实现aop同样需要在配置文件中注册bean,并且需要开启注解支持
  • 注解实现aop需要在切面类上面添加@Aspect注解
  • 注解实现aop每一个方法都需要进行execution【缺点

小记:

execution的参数

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

  • 修饰符匹配(modifier-pattern?)
  • 返回值匹配(ret-type-pattern)可以为*表示任何返回值,全路径的类名等
  • 类路径匹配(declaring-type-pattern?)
  • 方法名匹配(name-pattern)可以指定方法名 或者 代表所有, set 代表以set开头的所有方法
  • 参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(..)表示零个或多个任意参数
  • 异常类型匹配(throws-pattern?)
  • 其中后面跟着“?”的是可选项
execution(public * com.roderick.service.UserService.*(..))  
//表示匹配com.roderick.server.UserService中所有的公有方法

12、整合Mybatis

步骤

  • 导入相关jar包

    • junit
    • mybatis
    • mysql数据库
    • spring相关
    • aop织入
    • Mybatis-spring【新知识】
      <dependencies>
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
      </dependency>
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.20</version>
      </dependency>
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.2</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.9.RELEASE</version>
      </dependency>
      <!--Spring操作数据库还需要一个Spring-jdbc-->
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.1.9.RELEASE</version>
      </dependency>
      <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.4</version>
      </dependency>
      <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.3</version>
      </dependency>
      </dependencies>
      
  • 编写配置文件

  • 测试

12.1、回忆mybatis

  • 编写工具类
  • 编写实体类
  • 编写核心配置文件
  • 编写接口
  • 编写Mapper.xml
  • 测试

12.2、Mybatis-Spring

MyBatis-Spring能将Mybatis代码无缝的整合到Spring中,允许Mybatis参与到Spring的事务管理中

方式一

  • 编写数据源
<!--DataSource:使用Spring的数据源替换Mybatis的配置 (c3p0,dbcp druid)
      这里使用Spring提供的JDBC  org.springframework.jdbc.datasource
    -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
  <property name="username" value="root"/>
  <property name="password" value="luoyu-3-233"/>
</bean>
  • sqlSessionFactory
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <!--拓展:绑定Mybatis配置文件-->
  <property name="configLocation" value="classpath:mybatis-config.xml"/>
  <property name="mapperLocations" value="classpath:com/roderick/mapper/*.xml"/>
</bean>
  • sqlSessionTemplate
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
  <!--构造器注入sqlSessionFactory,因为没有set方法,只能构造器注入-->
  <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
  • 给接口加实现类
public class UserMapperImpl implements UserMapper {

  //相当于将原先测试类中new一个sqlsession然后getMapper的操作放到该实现类中了
  //以前我们所有的操作都使用SqlSession来操作,现在都使用sqlSessionTemplate
  private SqlSessionTemplate sqlSession;

  public void setSqlSession(SqlSessionTemplate sqlSession) {
    this.sqlSession = sqlSession;
  }

  public List<User> selectUser() {
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    return mapper.selectUser();
  }
}
  • 将实现类注入到Spring中【applicationContext.xml】
<!--通过该文件来注册Mapper实现类-->
<bean id="userMapper" class="com.roderick.mapper.UserMapperImpl">
  <property name="sqlSession" ref="sqlSession"/>
</bean>
  • get实现类的bean来执行方法
public static void main(String[] args) throws IOException {

  ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
  UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
  for (User user : userMapper.selectUser()) {
    System.out.println(user);
  }
}

注意:

Spring-dao.xml 数据源、sqlSessionFactory、sqlSessionTemplate的注入在此xml文件中进行【myBatis配置】

applicationContext.xml 【Mapper接口实现类的注册在此文件中进行】

<!--通过import方法导入dao的配置-->
<!--这样就可以实现dao的配置专注于配置mybatis-->
<import resource="spring-dao.xml"/>
<!--    <import resource="sprint-mvc.xml"/>-->

<!--通过该文件来注册Mapper-->
<bean id="userMapper" class="com.roderick.mapper.UserMapperImpl">
  <property name="sqlSession" ref="sqlSession"/>
</bean>

方式二

SqlSessionDaoSupport

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectUser() {
    //        SqlSession sqlSession = getSqlSession();
    //        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //        return mapper.selectUser();
    return getSqlSession().getMapper(UserMapper.class).selectUser();
  }
}
<bean id="userMapper2" class="com.roderick.mapper.UserMapperImpl2">
  <!--该方法实现类没有sqlSession的成员变量,但是他继承的方法需要sqlSessionFactory-->
  <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

新建一个实现类实现Mapper接口,该实现类继承SqlSessionDaoSupport类

这样就不需要在实现类中创建一个私有成员sqlSession然后set他了,可以直接用getSqlSession()方法来得到,

但是SqlSessionDaoSupport需要SqlSessionFactory

所以在Spring配置文件中写该实现类的bean时要配置变量传入sqlSessionFactory

13、声明式事务

13.1、回顾事务

  • 把一组业务当成一个业务来做;要么都成功,要么都失败
  • 事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能马虎!
  • 确保完整性和一致性

事务的ACID原则:

  • 原子性
  • 一致性
  • 隔离性
    • 多个业务可能操作同一个资源,防止数据损坏
  • 持久性
    • 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中!

13.2、Spring中的事务管理

  • 声明式事务: AOP
  • 编程式事务:需要在代码中进行事务的管理(try catch回滚

步骤:

  • 首先在Spring中配置声明式事务
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>
  • 配置事务的通知(Advice) —>给哪些方法配置事务
<!--配置事务通知:-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
  <!--给哪些方法配置事务-->
  <!--配置事务的传播特性:new-->
  <tx:attributes>
    <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>
  • 注意要使用tx需要导入约束
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
  • 配置事务的切入
<!--配置事务切入-->
<aop:config>
  <!--切入点-->
  <aop:pointcut id="txPointCut" expression="execution(* com.roderick.mapper.*.*(..))"/>
  <!--切入-->
  <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

为什么需要事务?

  • 如果不配置事务,可能存在数据提交不一致的情况;
  • 如果我们不在Spring中去配置声明式事务,我们就需要在代码中手动配置事务
  • 事务在项目的开发中十分重要,涉及到数据的一致性和完整性问题,不容马虎。