You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

49 KiB

title author top cover toc mathjax summary tags categories reprintPolicy abbrlink date coverImg img password
Spring5 TianZD true true true false SSM中的Spring5框架学习笔记,粗略学了一下,没有参考价值 [Spring java 学习笔记] [java] cc_by 7f09f5da 2022-04-29 11:06:51 <nil> <nil> <nil>

[toc]

Spring

1、概述

概述

Spring框架是一个开放源代码的J2EE应用程序框架,由Rod Johnson发起,是针对bean的生命周期进行管理的轻量级容器(lightweight container)。 Spring解决了开发者在J2EE开发中遇到的许多常见的问题,提供了功能强大IOC、AOP及Web MVC等功能。Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用,并且可以与 Swing等桌面应用程序AP组合。因此, Spring不仅仅能应用于J2EE应用程序之中,也可以应用于桌面应用程序以及小应用程序之中。

理念

使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架

SSH和SSM

SSH:Struct2+Spring+Hibernate

SSM:SpringMVC+Spring+Mybatis

优点

  • 开放源代码的免费的J2EE应用程序框架(容器)
  • 轻量级、非入侵式的框架
  • 是针对bean的生命周期进行管理的轻量级容器
  • 提供了功能强大IOC、AOP及Web MVC等功能
  • 支持事务的处理
  • Spring可以单独应用于构筑应用程序,也可以和Struts、Webwork、Tapestry等众多Web框架组合使用

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

缺点

  • 配置十分繁琐,人称“配置地狱”

组成

查看源图像

Spring框架主要由七部分组成,分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 Spring Web MVC。

扩展

现代化的java开发,就是基于spring的开发

image-20210808065217942

  • Spring Boot

快速开发的脚手架,基于springboot可以快速开发单个微服务

约定大于配置

  • Spring Cloud

基于Spring Boot实现

现在大多数公司都在使用springboot进行快速开发

学习springboot需要完全掌握spring 和springmvc

2、IOC理论推导

原来方式

基础代码

  1. 编写Dao层UserDao接口
public interface UserDao {
   void getUser();
}
  1. 编写Dao层UserDaoImpl实现类和UserDaoMysqlImpl实现类
public class UserDaoImpl implements UserDao {
   @Override
   public void getUser() {
      System.out.println("默认获取用户数据");
   }
}
public class UserDaoMysqlImpl implements UserDao{
   @Override
   public void getUser() {
      System.out.println("Mysql获取用户数据");
   }
}
  1. 编写Service层UserService接口
public interface UserService {
   void getUser();
}
  1. 编写Service层UserServiceImpl实现类
public class UserServiceImpl implements UserService {
   //创建dao层对应接口的实现类对象,从而调用dao层的方法
   //弊端:新增或者改变需求时,dao层实现类增加或改变,需要更改这里的代码
   private UserDao userDao = new UserDaoImpl();
   @Override
   public void getUser() {
      //调用dao层对应方法
      userDao.getUser();
   }
}
  1. 编写用户代码MyTest类
public class MyTest {
   public static void main(String[] args) {
      //用户实际调用的是业务层,不接触dao层
      UserService userService = new UserServiceImpl();
      userService.getUser();
   }
}

分析

用户实际调用的是业务层,不接触dao层,通过创建userservice接口引用指向一个UserServiceImpl实现类对象,调用UserServiceImpl中的getUser()方法。由于UserServiceImpl中创建dao层对应接口的实现类对象,从而调用dao层的getUser()方法

弊端

在我们之前的业务中,用户的需求可能会影响我们原来的代码,需要根据用户的需求去修改源代码,如果程序代码量大,修改的成本十分昂贵

  • 新增或者改变需求时,dao层实现类增加或改变,需要更改这里的代码,比如要调用UserDaoMysqlImpl中的方法,需要改变UserServiceImpl中的代码

改进-IOC原型

代码

  1. 改变Service层UserServiceImpl实现类
public class UserServiceImpl implements UserService {
   private UserDao userDao;
   //通过set进行动态实现值的注入
   public void setUserDao(UserDao userDao) {
      this.userDao = userDao;
   }
   @Override
   public void getUser() {
      //调用dao层对应方法
      userDao.getUser();
   }
}
  1. 改变用户代码MyTest类
public class MyTest {
   public static void main(String[] args) {
      //用户实际调用的是业务层,不接触dao层
      UserService userService = new UserServiceImpl();
      // 实现UserDaoImpl类中的方法
      ((UserServiceImpl) userService).setUserDao(new UserDaoImpl());
      userService.getUser();
      //实现UserDaoMysqlImpl方法
      ((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl());
      userService.getUser();
   }
}

分析

在新增或者改变需求时,只需要更改MyTest类中的代码

使用一个set接口实现,实现了革命性的变化

   private UserDao userDao;
   //通过set进行动态实现值的注入
   public void setUserDao(UserDao userDao) {
      this.userDao = userDao;
   }
  • 之前程序是主动创建对象,控制权在程序员手上,用户访问业务层,由程序员代码决定用户调用什么
  • 使用set注入后,程序不具有主动性,通过把接口给用户,用户访问业务层,主动权在用户,由用户选择调用什么,程序变成了被动接收对象(来自用户)

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

IOC本质

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

  • 控制反转IOC(inversion of control)是一种设计思想,DI(依赖注入)是实现IOC的一种方式。

  • 在没有IOC的程序中,使用面向对象编程,对象的创建和对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制

  • 控制反转后,对象的创建转移给了第三方

  • 控制反转就是获得依赖对象的方式反转了

IOC是Spring框架的核心内容,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC。采用XML方式配置Bean的时候,Bean的的定义信息和实现是分离的,采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建于组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象

3、HelloSpring

代码

创建实体类Hello.class

该类为后续要生成的对象的类,必须要有set方法(依赖注入要用)

public class Hello {
   private String str;

   public String getStr() {
      return str;
   }

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

   public Hello() {
   }

   public Hello(String str) {
      this.str = str;
   }

   public void helloSpring() {
      System.out.println("hello " + str);
   }
}

Spring配置文件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创建对象
                类名  变量名 = new 类
    正常创建对象:Hello hello = new Hello();
    bean = 对象
    property相当于给对象中的属性设置值
    id = 变量名
    class = new 的对象
    -->
    <bean id="hello" class="com.tian.pojo.Hello">
        <property name="str" value="Spring"/>
    </bean>

</beans>

业务代码MyTest.class

import com.tian.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
   public static void main(String[] args) {
      //获取Spring的上下文对象,固定语句
      ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
      //对象都在Spring中进行管理,要使用的化直接去里面取
      Hello hello = (Hello) context.getBean("hello");
      hello.helloSpring();
   }
}

分析

对象由spring进行创建,对象的属性由Spring容器设置,这个过程就叫控制反转IOC,一句话说就是对象由Spring来创建、管理、装配

  • 控制:传统应用程序的对象由程序本身控制创建,使用SPring后,对象由Spring进行创建

  • 反转:程序本身不创建对象,变为被动的接收对象

  • 依赖注入:利用set方法进行注入,对象必须要有set方法

4、IOC创建对象的方式

在xml中使用<bean>后,不管有没有使用,都会创建

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

必须有无参构造函数

<bean id="hello" class="com.tian.pojo.Hello">
    <property name="str" value="Spring"/>
</bean>

有参构造函数——参数名

重点

<bean id="hello" class="com.tian.pojo.Hello">
    <constructor-arg name="str" value="直接通过参数名"/>
</bean>

通过有参构造函数——参数下标

<bean id="hello" class="com.tian.pojo.Hello">
    <constructor-arg index="0" value="参数下标"/>
</bean>

有参构造函数——参数类型

不建议使用:当类中有两个或以上属性类型一样时,会导致错误

<bean id="hello" class="com.tian.pojo.Hello">
   <constructor-arg type="java.lang.String" value="有参类型"/>
</bean>

总结

在配置文件加载的时候,容器中管理的对象就已经初始化了

5、Spring配置

别名alias

起一个小名,两个名字hello和hello2都能用来创建对象

<alias name="hello" alias="hello2"/>

Bean的配置

<!--
    id:bean的唯一标识符,也就是相当于对象名
    class:bean对象对应的全限定名:包名+类型
    name:也是别名,而且name可以取多个别名,可以用空格、分号;、逗号,分隔
    -->
<bean id="hello" class="com.tian.pojo.Hello" name="hello3,hello4">
    <constructor-arg name="str" value="直接通过参数名"/>
</bean>

import

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

项目中有多个人开发,三个人负责不同的类的开发,不同的类需要注册在不同的bean中,可以利用import将所有的bean.xml合成一个总的。

<import resource="Beans.xml"></import>

6、DI依赖注入

构造器注入

4、IOC创建对象的方式

set方式注入【重点】

依赖注入:set注入

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

环境搭建

  1. 复杂类型
package com.tian.pojo;
public class Address {
   private String address;
//get set tostring省略
}
  1. 真实类型
public class Student {
   private String name;
   private Address address;
   private String[] books;
   private List<String> hobbies;
   private Map<String, String> card;
   private Set<String> games;
   private String wife;
   private Properties info;
   //省略了get 和set tostring
}
  1. 配置文件
<bean id="student" class="com.tian.pojo.Student">
    <!--第一种注入,普通值注入,value=   -->
    <property name="name" value="tian"></property>
</bean>
  1. 测试代码
public class MyTest {
   public static void main(String[] args) {
      ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
      Student student = (Student) context.getBean("student");
      System.out.println(student.getName());
   }
}

注入方式

  • 普通值注入value

  • bean注入ref

  • 数组注入

  • list

  • map

  • properties

  • null

	<bean id="address" class="com.tian.pojo.Address">
        <property name="address" value="上海"/>
    </bean>
    <bean id="student" class="com.tian.pojo.Student">
        <!--普通值注入,value=   -->
        <property name="name" value="tian"></property>
        <!--bean注入,ref =    -->
        <property name="address" ref="address"/>
        <!--数组注入 <array><value>-->
        <property name="books">
            <array>
                <value>book1</value>
                <value>book2</value>
                <value>book3</value>
            </array>
        </property>
        <!--list注入,<list><value>-->
        <property name="hobbies">
            <list>
                <value>听歌</value>
                <value>电影</value>
            </list>
        </property>
        <!--map注入,<map><entry>-->
        <property name="card">
            <map>
                <entry key="key1" value="val1"/>
                <entry key="key2" value="val2"/>
            </map>
        </property>
        <!--set注入,<set><value>-->
        <property name="games">
            <set>
                <value>LOL</value>
                <value>COD</value>
            </set>
        </property>
        <!--null注入,<null>
        空字符串的话,直接name,value=“”即可-->
        <property name="wife">
            <null></null>
        </property>
        <!--properties注入-->
        <property name="info">
            <props>
                <prop key="driver">com.mysql.driver</prop>
                <prop key="url">ual</prop>
                <prop key="name">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
    </bean>

扩展C\P命名空间注入

这个的使用要在bean.xml文件的头目录里面加上两行语句

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

<!--p命名空间注入,可以直接设置注入属性的值:property-->
<bean id="address1" class="com.baidu.pojo.Address" p:name="陕西省"/>
<!--c命名空间注入,通过构造器注入:construct-args-->
<bean id="address2" class="com.baidu.pojo.Address" c:name="宝鸡市"/>

7、Bean的作用域scope

单例模式scope="singleton"

默认就是单例,也可以手动设置

<bean id="address" class="com.tian.pojo.Address" scope="singleton">
    <property name="address" value="上海"/>
</bean>

原型模式scope="prototype"

每次从容器中get的时候,都会产生一个新对象

<bean id="address" class="com.tian.pojo.Address" scope="prototype">
    <property name="address" value="上海"/>
</bean>

其余的request、session、application

只能在web开发中使用

8、Bean的自动装配

自动装配:Spring会在上下文中自动寻找、装配属性

装配方式:

  • 在xml中显式的配置
  • 在java中显式的配置
  • 隐式的自动装配Bean【重要】

搭建环境

一个人有两个从宠物,dog和cat

xml配置

<bean id="cat" class="com.tian.pojo.Cat"/>
<bean id="dog" class="com.tian.pojo.Dog"/>
<bean id="person" class="com.tian.pojo.Person">
    <property name="name" value="tian"/>
    <property name="cat" ref="cat"/>
    <property name="dog" ref="dog"/>
</bean>

byName、byType自动装配

byName

byname:会自动在容器上下文中查找和自己对象中set方法后面中的值对应的bean id,如果dog改成dog222,则不会成功

<bean id="cat" class="com.tian.pojo.Cat"/>
<bean id="dog" class="com.tian.pojo.Dog"/>
<!--byname:会自动在容器上下文中查找和自己对象中set方法后面中的值对应的bean id-->
<bean id="person" class="com.tian.pojo.Person" autowire="byName">
    <property name="name" value="tian"/>
</bean>

byType

byType:会自动在容器上下文中查找和自己对象属性类型相同的bean

如果dog改成dog222 ,依然会成功,甚至注册dog时不需要id属性<bean class="com.tian.pojo.Dog"/>

注意,如果dog注册了多个对象,即同一个类型有两个对象,则bytype不可以使用

<bean id="cat" class="com.tian.pojo.Cat"/>
<bean id="dog222" class="com.tian.pojo.Dog"/>
<!--byType:会自动在容器上下文中查找和自己对象属性类型相同的bean-->
<bean id="person" class="com.tian.pojo.Person" autowire="byType">
    <property name="name" value="tian"/>
</bean>

总结

  • byname:需要保证所有bean的id唯一,并且bean需要和自动注入的属性的set方法的值一致
  • byType:需要保证所有bean的class唯一,并且bean需要和自动注入的属性类型一致

使用注解实现自动装配

Spring2.5开始支持注解

使用注解:

  • 导入约束:context约束xmlns:context="http://www.springframework.org/schema/context"
  • 配置注解的支持: <context: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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>
  • xml配置
<bean id="cat" class="com.tian.pojo.Cat"/>
<bean id="dog" class="com.tian.pojo.Dog"/>
<bean id="person" class="com.tian.pojo.Person"/>

@Autowired

  • 默认为byType方式

  • 直接在属性上使用或者在set方法上使用

  • 可以不需要set方法

public class Person {
   @Autowired
   private Cat cat;
   @Autowired
   private Dog dog;
   private String name;
}
  • 扩展:autowired有一个属性,required,默认为true,如果显式的定义了其为false,说明这个对象允许为null
 @Autowired(required = false)

@Qualifier

Autowired默认为byType方式,如果IOC容器中同一个类型注册了多个对象,那么会出现问题,需要配置Qualifier使用,指定具体的对象

<bean id="cat1" class="com.tian.pojo.Cat"/>
<bean id="cat2" class="com.tian.pojo.Cat"/>
public class Person {
   @Autowired
   @Qualifier(value = "cat1")
   private Cat cat;
}

@Resource

Resource是java自带的注解

通过byName方式

public class Person {
	//@Autowired
	//@Qualifier(value = "cat1")
	@Resource(name="cat1")
	private Cat cat;
}

9、使用注解开发

Bean

  1. 导包,必须有aop的包,pop.xml
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.9</version>
    </dependency>
</dependencies>
  1. 导入context约束,增加注解支持,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:context="http://www.springframework.org/schema/context"
       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">
    <!--指定要扫描的包,该包下的注解就会生效-->
    <context:component-scan base-package="com.tian.pojo"/>
    <!--注解支持-->
    <context:annotation-config/>
</beans>
  1. 实体类@Component

在实体类上使用@Component注解等价于<bean id="user" class="com.tian.pojo.User"/>,id默认为类的名字小写

//等价于<bean id="user" class="com.tian.pojo.User"/>
//	id默认为类的名字小写
@Component
public class User {
	private String name;
	//get set    
}

属性的注入@Value

适用于简单的,复杂的还是通过配置文件

//等价于<bean id="user" class="com.tian.pojo.User"/>
//	id默认为类的名字小写
@Component
public class User {
    @Value("tianzd")
	private String name;
	//get set    
}

衍生的注解

@Component有几个衍生注解,在web开发中会按照MVC三层架构开发

四个功能一样,都是代表将某个类注册到spring中,装配Bean

自动装配注解

@Autowired

@Resource

作用域注解@Scope

在某个类上标注,修改称单例或者多例模式

小结

xml与注解

  • xml更加万能,适用于任何场景,维护简单方便

  • 注解不是自己的类使用不了,维护复杂

最佳实践

  • xml用来管理Bean
  • 注解只负责完成属性注入@Value

10、使用java的方式配置Spring

舍弃xml配置文件,使用config类实现

实体类

package com.tian.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//这个注解的意思就是说明这个类被注册到了容器中
@Component
public class User {
   private String name;

   public String getName() {
      return name;
   }
   @Value("tianzhendong")
   public void setName(String name) {
      this.name = name;
   }
}

配置类

package com.tian.config;

import com.tian.pojo.User;
import org.springframework.context.annotation.*;
//使其成为配置类,同beans.xml
//这个也会被spring容器托管,注册到容器中,因为他也是一个@component
@Configuration
@ComponentScan("com.tian")
//@Import(MyConfig1.class) 合并配置类,相当于xml中的import
public class MyConfig {
   //注册一个bean,相当于一个bean标签
   //方法的名字相当于id属性
   //返回值类型,相当于class属性
   //返回值就是要注入到bean中的对象
   @Bean(name = "user1")
   @Scope(value = "singleton")
   public User getUser() {
      return new User();
   }
}

测试类

import com.tian.config.MyConfig;
import com.tian.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
   public static void main(String[] args) {
      ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
      User user = context.getBean("user1", User.class);
      System.out.println(user.getName());
   }
}

11、代理模式

中介

为什么学习代理模式

image-20210808200209343

在业务增删改时,为了避免更改底层的代码

代理模式是SpringAop的底层

SpringAOP的底层,面试重点

分类

  • 静态代理
  • 动态代理

静态代理

租房:中介,房东,客户,出租房接口(中介和房东的)

角色分析

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

抽象接口

package com.tian.demo1;

public interface Rent {
   public void rent();
}

实现类

package com.tian.demo1;
public class LandLord implements Rent{
   @Override
   public void rent() {
      System.out.println("房子租出去了");
   }
}

代理

package com.tian.demo1;
public class Proxy implements Rent{
   private Rent landLord;

   public Proxy(LandLord landLord) {
      this.landLord = landLord;
   }

   public Proxy() {
   }

   @Override
   public void rent() {
      landLord.rent();
   }
}

客户端

package com.tian.demo1;
public class Client {
   public static void main(String[] args) {
      Proxy proxy = new Proxy(new LandLord());
      proxy.rent();
   }
}

优点

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

缺点

  • 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
  • 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

动态代理

每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,为了弥补静态代理的缺点:一个真实角色就会产生一个代理,代码量翻倍,开发效率降低

概述

动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象

  • 角色和静态代理一样
  • 动态代理的代理类是动态生成的,不是直接写好的

分类

  • 基于接口的动态代理——JDK动态代理,这里使用这种
  • 基于类——cglib
  • java字节码——javasist

JDK动态代理

在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持

  • Proxy类:生成动态代理实例
  • InvocationHandler接口:调用处理程序并返回一个结果

InvocationHandler接口

每一个代理实例都有一个关联的调用处理程序,InvocationHandler是由代理实例的调用处理程序实现的接口

方法:invoke(object, object[])

//Object proxy:被代理的对象
//Method method:要调用的方法
//Object[] args:方法调用时所需要参数
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

Proxy类

有个静态方法,可以创建动态代理类的实例

//CLassLoader loader:类的加载器
//Class<?> interfaces:得到全部的接口
//InvocationHandler h:得到InvocationHandler接口的子类的实例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

动态创建代理对象的类

  • 动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法
  • invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类
//动态代理类只能代理接口(不支持抽象类),代理类都需要实现InvocationHandler类,实现invoke方法。
//invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类   
public class LogHandler implements InvocationHandler {
	// 目标对象
	private Object targetObject;
	//绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。     
	public Object newProxyInstance(Object targetObject){
		this.targetObject=targetObject;
		//该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例  
		//第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
		//第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
		//第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
		//根据传入的目标返回一个代理对象
		return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
				targetObject.getClass().getInterfaces(),this);
	}
	@Override
	//关联的这个实现类的方法被调用时将被执行
	/*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/
    //invoke方法中实现了代理类要扩展的公共功能。
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        //代理扩展逻辑,实现代理类要扩展的公共功能
        System.out.println("proxy do");
 
        return method.invoke(targetObject, args);
    } 
}

优点

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

12、AOP

简介

什么是AOP

面向切面编程,AOP是能够让我们在不影响原有功能的前提下,为软件横向扩展功能。 那么横向扩展怎么理解呢,我们在WEB项目开发中,通常都遵守三层原则,包括控制层(Controller)->业务层(Service)->数据层(dao),那么从这个结构下来的为纵向,它具体的某一层就是我们所说的横向。我们的AOP就是可以作用于这某一个横向模块当中的所有方法。

和oop区别

AOP和OOP的区别:AOP是OOP的补充,当我们需要为多个对象引入一个公共行为,比如日志,操作记录等,就需要在每个对象中引用公共行为,这样程序就产生了大量的重复代码,使用AOP可以完美解决这个问题。

AOP在Spring中的作用

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

  • 横切关注点:跨越应用程序多个模块的方法或功能,即:与我们业务逻辑无关的,但我们需要关注的部分,如日志,安全,缓存,事务等
  • 切面:拦截器类,其中会定义切点以及通知
  • 切点:具体拦截的某个业务点
  • 通知:切面当中的方法,声明通知方法在目标业务层的执行位置,通知类型如下:
    • 前置通知:@Before 在目标业务方法执行之前执行
    • 后置通知:@After 在目标业务方法执行之后执行
    • 返回通知:@AfterReturning 在目标业务方法返回结果之后执行
    • 异常通知:@AfterThrowing 在目标业务方法抛出异常之后
    • 环绕通知:@Around 功能强大,可代替以上四种通知,还可以控制目标业务方法是否执行以及何时执行

方式1:使用Spring的API接口实现AOP

导包:注意

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
</dependencies>

接口

public interface UserService {
   public void add();
   public void delete();
   public void update();
   public void selete();
}

接口实现类

public class UserServiceImpl implements UserService{
   @Override
   public void add() {
      System.out.println("add");
   }

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

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

   @Override
   public void selete() {
      System.out.println("selete");
   }
}

增强方法log:注意

package com.tian.log;

import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
@Component
public class Log implements MethodBeforeAdvice {

   @Override
   //method:要执行的目标对象的方法
   //objects: 参数
   //target:目标对象
   public void before(Method method, Object[] args, Object target) throws Throwable {
      System.out.println("目标对象为:" + target.getClass().getName());
      System.out.println("执行的方法为:" + method.getName());
      for (int i = 0; i < args.length; i++) {
         System.out.println("第"+i+"个参数为:"+args[i]);
      }
   }
}

xml配置:注意

<!--注册bean-->
<bean id="userService" class="com.tian.service.UserServiceImpl"/>
<bean id="log" class="com.tian.log.Log"/>
<!--方式1,使用原生API接口-->
<!--配置aop,需要导入aop约束-->
<aop:config>
    <!--切入点, expression是表达式,execution(要执行的位置 * * * *)-->
    <aop:pointcut id="pointcut" expression="execution(* com.tian.service.UserServiceImpl.*(..))"/>
    <!--执行环绕增加-->
    <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
</aop:config>

测试:注意

import com.tian.service.UserService;
import com.tian.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
   public static void main(String[] args) {
      ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
      //动态代理代理的是接口
      UserService userService = context.getBean("userService", UserService.class);
      userService.add();
   }
}

方式2:自定义切入类

切入类

public class DiyPointCut {

    public void before() {
        System.out.println("---------方法执行前---------");
    }

    public void after() {
        System.out.println("---------方法执行后---------");
    }
}

配置

<bean id="userservice" class="com.service.UserServiceImpl"/>
<bean id="diy" class="com.diy.DiyPointCut"></bean>
<aop:config> <!--第二种方式:使用AOP的标签实现-->
    <aop:aspect ref="diy">
        <aop:pointcut id="diyPonitcut" expression="execution(* com.service.UserServiceImpl.*(..))"/>
         <!-- pointcut-ref关联的切入点 , method切入的方法  -->
        <aop:before pointcut-ref="diyPonitcut" method="before"/>
        <aop:after pointcut-ref="diyPonitcut" method="after"/>
    </aop:aspect>
</aop:config>

方式3:注解

增强类

//声明该类是一个切面
@Aspect
public class AnnotationPointCut {

    //声明前置方法
    @Before("execution(* com.service.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("这是使用注解的前置增强");
    }

    //声明后置方法
    @After("execution(* com.service.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("使用注解的后置增强");
    }


    //环绕增强的优先级更高
    @Around("execution(* com.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:" + jp.getSignature());
        //执行目标方法proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println("proceed对象:"+proceed);
    }
}

配置

    <bean id="userservice" class="com.service.UserServiceImpl"/>
    <bean id="annotationPointcut" class="com.diy.AnnotationPointCut"/>
    <aop:aspectj-autoproxy/>

13、整合Mybatis

步骤:

  1. 导入jar包
    • junit单元测试
    • mybatis
    • mysql
    • spring
    • aop
    • mybatis-spring(新包,用于整合)
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.9</version>
    </dependency>
    <!--spring操作数据库-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.9</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.2</version>
    </dependency>
</dependencies>
  1. 编写配置文件

  2. 测试

回忆mybatis

数据:

image-20210810222253418

  1. 编写实体类
package com.tian.pojo;

/**
 * @program: SpringStudy
 * @description:
 * @author: TianZD
 * @create: 2021-08-10 21:00
 **/
public class User {
   private int id;
   private String name;
   private String password;

   @Override
   public String toString() {
      return "User{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", password='" + password + '\'' +
            '}';
   }

   public int getId() {
      return id;
   }

   public void setId(int id) {
      this.id = id;
   }

   public String getName() {
      return name;
   }

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

   public String getPassword() {
      return password;
   }

   public void setPassword(String password) {
      this.password = password;
   }

   public User() {
   }

   public User(int id, String name, String password) {
      this.id = id;
      this.name = name;
      this.password = password;
   }
}
  1. 编写核心配置文件
<?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>
    <typeAliases>
        <package name="com.tian"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSl=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/tian/dao/UserMapper.xml"/>
    </mappers>
</configuration>
  1. 编写核心配置类
package com.tian.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

/**
 * @program: SpringStudy
 * @description:
 * @author: TianZD
 * @create: 2021-08-10 21:07
 **/
public class MybatisUtils {
   public static SqlSessionFactory sqlSessionFactory;
   static {
      try {
         String resources = "mybatis-config.xml";
         InputStream inputStream = Resources.getResourceAsStream(resources);
         sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
   public static SqlSession getSqlSession() {
      return sqlSessionFactory.openSession();
   }
}
  1. 编写接口
package com.tian.dao;

import com.tian.pojo.User;

import javax.sound.midi.VoiceStatus;
import java.util.List;
import java.util.Map;

/**
 * @program: SpringStudy
 * @description:
 * @author: TianZD
 * @create: 2021-08-10 21:18
 **/
public interface UserMapper {

   public int insert(User user);

   public int delete(int id);

   public int update(User user);

   public User select(int id);
}
  1. 编写Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.tian.dao.UserMapper">
    <resultMap id="map" type="User">
        <result column="pwd" property="password"/>
    </resultMap>

    <select id="select" resultType="user">
        select *
        from mybatis.user;
    </select>

    <delete id="delete" parameterType="int">
        delete
        from mybatis.user
        where id = #{id};
    </delete>

    <update id="update" parameterType="map">
        update mybatis.user
        set name = #{name},pwd = #{password}
        where id =#{id};
    </update>

    <insert id="insert" parameterType="map">
        insert into mybatis.user (id, name, pwd)
        values (#{id},#{name},#{password});
    </insert>




</mapper>
  1. 测试

Mybatis-Spring

http://mybatis.org/spring/zh/

  • 不变内容:

实体类User.class

UserMapper.interface接口

  • 编写数据源
<!--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=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>
  • SqlSessionFactory
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!--绑定mybatis-config.xml-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:com/tian/dao/UserMapper.xml"/>
    <property name="typeAliasesPackage" value="com.tian"/>
</bean>
  • SqlSessionTemplate
<!--SqlSessionTemplate-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <!--只能通过构造函数注入-->
    <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
  • 给接口加实现类
package com.tian.dao;

import com.tian.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;

/**
 * @program: SpringStudy
 * @description:
 * @author: TianZD
 * @create: 2021-08-10 22:44
 **/
public class UserMapperImpl implements UserMapper{
   UserMapper userMapper = null;
   private SqlSessionTemplate sqlSessionTemplate;
   public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
      this.sqlSessionTemplate = sqlSessionTemplate;
   }


   @Override
   public int insert(User user) {
      userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
      return userMapper.insert(user);
   }

   @Override
   public int delete(int id) {
      userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
      return userMapper.delete(id);
   }

   @Override
   public int update(User user) {
      userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
      return userMapper.update(user);
   }

   @Override
   public User select(int id) {
      userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
      return userMapper.select(id);
   }
}
  • 将实现类注入到spring中
<bean id="userMapperImpl" class="com.tian.dao.UserMapperImpl">
    <property name="sqlSessionTemplate" ref="sqlSession"/>
</bean>
  • 测试
import com.tian.dao.UserMapper;
import com.tian.pojo.User;
import com.tian.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: SpringStudy
 * @description:
 * @author: TianZD
 * @create: 2021-08-10 21:33
 **/
public class MyTest {
   @Test
   public void userMapperTest() {
      ApplicationContext context = new ClassPathXmlApplicationContext("spring-mybatis.xml");
      UserMapper userMapper = context.getBean("userMapperImpl", UserMapper.class);
      System.out.println(userMapper.select(2));
   }
}

进一步简化:SqlSessionDaoSupport

SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法

package com.tian.dao;

import com.tian.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;

/**
 * @program: SpringStudy
 * @description:
 * @author: TianZD
 * @create: 2021-08-10 23:17
 **/
public class UserMapperImpl_2 extends SqlSessionDaoSupport implements UserMapper{
   SqlSession sqlSession = getSqlSession();
   UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

   @Override
   public int insert(User user) {
      return userMapper.insert(user);
   }

   @Override
   public int delete(int id) {
      return userMapper.delete(id);
   }

   @Override
   public int update(User user) {
      return userMapper.update(user);
   }

   @Override
   public User select(int id) {
      return userMapper.select(id);
   }

}

注册:略去了sqlsessiontemplate注册

需要注册userMapperimpl2,SqlSessionDaoSupport需要通过属性设置一个sqlSessionFactorySqlSessionTemplate

<!--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=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!--绑定mybatis-config.xml-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:com/tian/dao/UserMapper.xml"/>
    <property name="typeAliasesPackage" value="com.tian"/>
</bean>

<bean id="userMapperImpl2" class="com.tian.dao.UserMapperImpl_2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

14、声明式事务

事务分为:

  • 声明式事务:AOP实现
  • 编程式事务

使用声明式事务

配置声明式事务

要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg name="dataSource" ref="dataSource" />
</bean>

aop实现事务

<!--结合AOP实现事务的注入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--给哪些方法配置事务-->
    <!--配置事务的传播特性:new propagation-->
    <tx:attributes>
        <tx:method name="update" propagation="REQUIRED"/>
        <tx:method name="delete" propagation="REQUIRED"/>
        <tx:method name="insert" propagation="REQUIRED"/>
        <tx:method name="select" read-only="true"/>
    </tx:attributes>
</tx:advice>

<!--配置事务切入-->
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* com.tian.dao.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

事务的传播特性

事务传播特性,就是多个事务方法调用时如何定义方法间事务的传播。Spring 定义了 7 种传播行为:

  • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
  • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
  • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
  • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
  • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
  • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。