spring框架的控制反转(IOC)和依赖注入(DI)的笔记整理

   Spring:

一、优点:

Spring是一个轻量级控制反转(IoC)和面向切面(AOP)的容器框架。

Spring是一个开源的免费的框架(容器)

Spring是一个轻量级的、非入侵式的框架!

控制反转(IOC)

面向切面(AOP)

支持事务的处理,对框架整合的框架

 

二、Spring 组成:

 

 

 组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

核心容器:

核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

Spring 上下文:

Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。

Spring AOP:

通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。

Spring DAO:

JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

Spring ORM:

Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。

Spring Web 模块:

Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。

Spring MVC 框架:

MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

三、IOC理论:

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

2、IOC是Spring框架的核心内容,使用多种方式完美的实现了IOC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC。

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

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

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

四、一个使用spring创建对象的简单 demo:

1、在POM.xml文件中导入依赖:

1  
2         
3             org.springframework
4             spring-webmvc
5             5.3.4
6         
7 

2、创建Hello类

@Data
public class Hello {
    private String str;
}

3、创建Spring配置文件ApplicationContext.xml

 1  2        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3        xsi:schemaLocation="http://www.springframework.org/schema/beans
 4         https://www.springframework.org/schema/beans/spring-beans.xsd">
 5 
 6     
14    class="cn.tony.pojo.Hello">
15        
16    
17 
18 

4、编写测试类: 这个测试类 ClassPathXmlApplicationContext 需要这个类来获取 Spring上下文对象即在配置文件中注入的对象。

 1 import cn.tony.pojo.Hello;
 2 import org.springframework.context.ApplicationContext;
 3 import org.springframework.context.support.ClassPathXmlApplicationContext;
 4 
 5 /**
 6  * @author Tu_Yooo
 7  * @Date 2021/3/16 14:13
 8  */
 9 public class MyTest {
10     public static void main(String[] args) {
11         //获取Spring上下文对象
12         ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
13         //从ioc容器中获取bean
14         Hello hello =(Hello) context.getBean("hello");
15 
16         System.out.println(hello.toString());
17     }
18 }

5、总结:

Hello对象的创建与属性赋值是由Spring来完成的

这个过程就叫控制反转:

控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的.

反转:程序本身不创建对象,而变成被动的接收对象.

依赖注入:就是利用set方法来进行注入的.
IOC是一种编程思想,由主动的编程变成被动的接收

IOC创建对象的方式
Spring默认 使用无参构造方法创建对象,如果不提供无参构造,又没有指定创建对象的方式则初始化时会报错

五、不提供无参构造时,可以通过以下三种方式,实例化bean

方式一:通过下标,可以使用该index属性来明确指定构造函数参数的索引

 

 

 示例:

1、修改Hello类,不提供无参构造方式(给定有参构造,他的有参构构造参数有几个就使用下标 index进行指定)

 1 public class Hello {
 2 
 3     private String str;
 4     
 5     public Hello(String str) {
 6         this.str = str;
 7     }
 8 
 9     public String getStr() {
10         return str;
11     }
12 
13     public void setStr(String str) {
14         this.str = str;
15     }
16 
17     @Override
18     public String toString() {
19         return "Hello{" +
20                 "str='" + str + '\'' +
21                 '}';
22     }
23 }

2、修改配置文件(该下标对应的就是有参构造的参数位置)

 1  2        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3        xsi:schemaLocation="http://www.springframework.org/schema/beans
 4         https://www.springframework.org/schema/beans/spring-beans.xsd">
 5 
 6   
 7    class="cn.tony.pojo.Hello">
 8        
 9    
10 
11 

方式二:通过类型(不建议使用)【也是在有参构造的情况下使用,type指定的就是有参构造中的参数的类型】

通过使用type属性显示指定构造函数的类型,则容器可以使用简单类型的类型匹配。如:

 

 

 修改配置文件 使用type

 1  2        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3        xsi:schemaLocation="http://www.springframework.org/schema/beans
 4         https://www.springframework.org/schema/beans/spring-beans.xsd">
 5 
 6    class="cn.tony.pojo.Hello">
 7        
 8    
 9 
10 

方式三、通过参数名

修改配置文件:

 1  2        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3        xsi:schemaLocation="http://www.springframework.org/schema/beans
 4         https://www.springframework.org/schema/beans/spring-beans.xsd">
 5 
 6     
11    class="cn.tony.pojo.Hello">
12        
13    
14 
15 

五、Spring配置说明:

1、在Spring中需要配置的极少,以下是需要了解掌握的

 

 

2、alias:(给bean的对应id取别名,这样在通过spring的 ClassPathXmlApplicationContext 获取上下文获取对应的对象的时候就可以使用别名了,下面会有代码示例)

1 class="cn.tony.pojo.Hello">
2        
3  
4     
5 

 

3、使用别名:(获取向spring容器中注入的对象)

 

4、bean的结构参数说明:

 1 
11    class="cn.tony.pojo.Hello" name="hello3,hello4">
12        
13    

5、import关键字的使用:

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

 

 

 

 

DI依赖注入:

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

这里讲解的是set 集合(list,map) 对象 等属性的注入 (之前讲的是基本数据类型和引用类型的属性的注入 并且使用的是构造器方 1 public class Address {

 2 
 3     private String address;
 4 
 5     public String getAddress() {
 6         return address;
 7     }
 8 
 9     public void setAddress(String address) {
10         this.address = address;
11     }

编写学生类(该类中含有各种数据类型的属性)

 1 public class Student {
 2 
 3     private String name;
 4     private Address address;
 5     private String[] books;
 6     private List hubbys;
 7     private Map<String,String> card;
 8     private Set games;
 9     private String wife;
10     private Properties properties;
11 
12    //TODO 自己生成 get set toString方法
13 }

构造器方式的注入上面已经有说明,这里注重说明其他数据类型的属性的注入.

编写xml配置文件

 1 
 2  3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xsi:schemaLocation="http://www.springframework.org/schema/beans
 5         https://www.springframework.org/schema/beans/spring-beans.xsd">
 6 
 7 
 8     class="cn.tony.pojo.Address"/>
 9 
10     class="cn.tony.pojo.Student">
11         
12         
13         
14         
15         
16         
17             
18                 王根基
19                 郑在稿
20             
21         
22         
23         
24             
25                 111
26                 222
27             
28         
29         
30         
31             
32333435
36         
37         
38         
39             
40                 LOL
41                 COC
42             
43         
44         
45         
46             <null/>
47         
48         
50         
51             
52                 111111
5354                 12
55             
56         
57     
58 
59 

其他方式的注入:

测试环境:

1 public class User {
2     
3     private String name;
4     private int age;
5     
6     //TODO 自己生成get set ToString方法 无参构造 有参构造
7 }

P命名空间注入

P命名空间注入对应set属性注入

使用P命名空间注入需要在配置文件头标签中导入约束

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

 

 

 

C命名空间注入

C命名空间注入对应构造器注入

使用C命名空间注入需要在配置文件头标签中导入约束

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

Bean作用域

在Spring中一共有六种作用域

 

 

 单例模式是Spring中的默认实现
可以通过scope="singleton"设置单例作用域

 

 

 

自动装配(使用自动装配就是不需要在主bean中使用 property 属性去 ref 该类中的其他属性啦。)

 

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

 

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

 

1、XML中显示配置

 

2、.在Java中进行显示配置

 

3、.隐式(注解)自动装配Bean(重点)

搭建测试环境

编写测试类Cat

1 public class Cat {
2 
3     public void shout(){
4         System.out.println("跟我一起学喵叫!");
5     }
6 }

测试类Dog

1 public class Dog {
2 
3     public void shout(){
4         System.out.println("跟我一起学汪叫");
5     }
6 }

测试类User

1 public class User {
2 
3     private String name;
4     private Cat cat;
5     private Dog dog;
6     //TODO 自己生成get set toString
7 }

编写xml配置文件

 1 
 2  3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:c="http://www.springframework.org/schema/c"
 5        xsi:schemaLocation="http://www.springframework.org/schema/beans
 6         https://www.springframework.org/schema/beans/spring-beans.xsd">
 7 
 8 
 9     class="cn.tony.pojo.Cat"/>
10     class="cn.tony.pojo.Dog"/>
11 
12     class="cn.tony.pojo.User">
13         
14         
15         
16     
17 
18 

测试打印值:

1 //获取Spring上下文对象
2 ApplicationContext context = new ClassPathXmlApplicationContext("UserBean.xml");
3 
4 User user =context.getBean("user", User.class);
5 
6 System.out.println(user.toString());

XML自动装配 Byname

在原有的XML配置文件中,属性名与bean实例id相同

 

 

 可以通过autowire="byName"定义自动装配

 

 

 byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的beanid ! (即找上面单独定义好的bean 的id cat dog 他和User 实体类中的 set 方法后面的值的名称一致)

注意点:
使用byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!,如果遇到以下情况,则会注入失败

(即: 这个注入的id 和User 类中的属性名的set方法后的名字一致,这个是byName,

当使用byType 是 bean id 和User 类中的属性名可以不一致 他是通过class 去找对应的类类型

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

 

 

 

 

 

 

XML自动装配 Bytype

在原有的XML配置文件中,属性名与bean实例id不同时

 

 

可以通过autowire="byType"定义自动装配

 

 

注意点:
使用byTypeb的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!,如果遇到以下情况则会自动装配失败

 

 

 

 

 

使用注解完成自动装配

要使用注解需要完成以下几步:

1.导入约束

2.配置注解支持

@Autowired

直接在属性使用即可

编辑测试类

 

使用Autowired甚至可以不提供Set方法,前提是注入的值在IOC容器中存在,并且属性名与bean实例id相同

 

 注解额外的知识:

//如果定义required为false说明这个属性可以为null,否则不允许为空
@Autowired(required = false)
private Cat cat;
//这个注解标记的属性 说明这个字段可以为null
@Nullable
private String name;
//如果@Autowired自动装配的环境比较复杂,
//自动装配无法通过一个注解【@Autowired】完成的时候、
//我们可以使用@Qualifier(value="dog222")配合使用
@Autowired
@Qualifier("dog22")
private Dog dog;

使用注解开发

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

 

 

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

 

 

 1 
 2  3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:context="http://www.springframework.org/schema/context"
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans
 6         https://www.springframework.org/schema/beans/spring-beans.xsd
 7         http://www.springframework.org/schema/context
 8         https://www.springframework.org/schema/context/spring-context.xsd">
 9 
10      
11     package="cn.tony.pojo"/>
12     
13     
14 
15 

 

Bean–@Component

我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!

 

 

 1、配置扫描哪些包下的注解

 1 2 package="com.kuang.pojo"/> 

2、在指定包下编写类,增加注解

1 @Component("user")
2 // 相当于配置文件中 
3 public class User {
4    public String name = "秦疆";
5 }

3、测试

1 @Test
2 public void test(){
3    ApplicationContext applicationContext =
4        new ClassPathXmlApplicationContext("beans.xml");
5    User user = (User) applicationContext.getBean("user");
6    System.out.println(user.name);
7 }

@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。

@Controller:web层

@Service:service层

@Repository:dao层

写上这些注解,就相当于将这个类交给Spring管理装配了!

属性-- @Value(“黄豪”)

使用注解注入属性

1、可以不用提供set方法,直接在直接名上添加@value(“值”)

1 @Component("user")
2 // 相当于配置文件中 
3 public class User {
4    @Value("黄豪")
5    // 相当于配置文件中 
6    public String name;
7 }

2、如果提供了set方法,在set方法上添加@value(“值”);

 1 @Component("user")
 2 public class User {
 3 
 4    public String name;
 5 
 6    @Value("黄豪")
 7    public void setName(String name) {
 8        this.name = name;
 9   }
10 }

作用域–@scope

@scope

singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。

prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收

1 @Controller("user")
2 @Scope("prototype")
3 public class User {
4    @Value("黄豪")
5    public String name;
6 }

Java方式配置Spring–@Configuration

JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。

通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id

(这里使用这个注解,这个类就是一个配置类,在这个类中可以使用 @Bean 注解注册一个 Bean 就相当于在xml中注册了一个 Bean 一样,

如果完全使用了配置类去做 就只能通过ApplicationContext去获取上下文)

@Configuration注解代码
1 @Configuration  //代表这是一个配置类
2 @ComponentScan("cn.tony.pojo")//扫描指定的包
3 public class MyConfig {
4 
5    @Bean 
6    public Dog dog(){
7        return new Dog();//返回要注入到bean的对象
8   }
9 }

Dog实体类代码:

1 @Component  //将这个类标注为Spring的一个组件,放到容器中!
2 public class Dog {
3    public String name = "dog";
4 }

测试:

1 @Test
2 public void test2(){
3 //如果完全使用了配置类去做 就只能通过ApplicationContext去获取上下文
4    ApplicationContext applicationContext =
5            new AnnotationConfigApplicationContext(MyConfig.class);
6    Dog dog = (Dog) applicationContext.getBean("dog");
7    System.out.println(dog.name);
8 }

@Configuration //标注这是一个Spring配置类
@ComponentScan(“cn.tony.pojo”) //扫描指定包下的包
@Import(xxx.class) //多个配置类合并成一个类

 

AOP代理模式[动态代理,静态代理]

静态代理

现实生活示例1、

代理(抽象接口 定义租房方法   房东 中介 租客 ,

房东实现抽象接口 类中定义一个租房方法,

然后中介类中也实现抽象接口 ,

在中介类中使用构造方法注入抽象接口对象 ,

在调用的时候实例化代理人 使用多态向下转型为房东,

调用房东的租房方法 达到代理的效果)

现实生活示例2、

编写一个User接口 一个User实现类 在实现类中实现增删改查功能
当需要在方法中加入打印日志功能的时候(横向切入代码,打印日志)
那就再编写一个Prox实现类实现这个接口 然后重写这里面的方法,然后在这个类中注入User抽象接口的实例 使用set方法注入
然后使用多态向下转型 user实现类去调用方法执行的还是User实现类的方法
然后在 Prox实现类 封装打印日志的方法 在每一个重写的方法中调用

 

代理模式是SpringAOP的底层实现

代理模式的定义:
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式的分类:
1.静态代理
2.动态代理

 

静态代理模式

角色分析

抽象角色 : 一般使用接口或者抽象类来实现【定义一个抽象类或接口,在里面执行相关方法,代理角色和被代理角色都实现这个接口】

真实角色 : 被代理的角色(房东)

代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .(中介)

客户 : 使用代理角色来进行一些操作 .(client实例化代理角色)

代码实现:(租房案例)

Rent 即抽象角色 定义行为(抽象接口)

1 //抽象角色:租房
2 public interface Rent {
3    public void rent();
4 }

Host (房东)即真实角色 实现Rent接口

/**
 * 房东
 * @author huanghao
 * @Date 2021/9/30 14:08
 */
public class Host implements Rent{

    public void hostRent() {
        System.out.println("租房子");
    }
}

Proxy 即代理角色 实现Rent接口 帮助真实角色完成行为【在该类中注入房东的实体,代理房东做一些事情。使用房东实例调用房东的方法】

 1 /**
 2  * 房产中介
 3  * @author huanghao
 4  * @Date 2021/9/30 14:31
 5  */
 6 public class Proxy implements Rent{
 7     //定义接口 
 8     private Rent rent;
 9     public Proxy(){ }
10     //向上造型 注入真实角色
11     public Proxy(Rent rent){
12         this.rent=rent;
13     }
14 
15     public void hostRent() {
16         System.out.println("找到客户");
17         rent.hostRent();
18         System.out.println("结束订单");
19     }
20 }

客户使用:调用

1 public static void main(String[] args) {
2         //找代理角色 
3         Proxy proxy = new Proxy(new Host());
4         proxy.hostRent();
5 }

运行结果:

 

 

 

静态代理的好处:

1、可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .

2、公共的业务由代理来完成 . 实现了业务的分工 ,

3、公共业务发生扩展时变得更加集中和方便 .

缺点 :

1、类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .
2、我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

动态代理模式

简介

动态代理的角色和静态代理的一样

动态代理的代理类是动态生成的,静态代理的代理类是我们提前写好的

动态代理分为两类 :

基于接口动态代理----JDK动态代理(原则:真实对象和代理对象实现相同的接口)
基于类的动态代理–cglib(原则:代理对象继承真实对象)
现在用的比较多的是 javasist 来生成动态代理

JDK的动态代理类位于java.lang.reflect包下,需要了解两个类:

InvocationHandler:调用处理程序

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

当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。

Proxy:提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。

两者的关系:

【使用Proxy 类提供了创建动态代理类和实例的静态方法 在创建完动态代理类和实例后 会自动的执行 InvocationHandler中的invoke方法,

这个invoke方法会利用反射机制自动执行被代理中的方法,这个被代理的对象因为是动态生成的,所以会自动执行抽象接口中的方法

为什么会自动执行抽象接口中的方法 因为在使用 Proxy 时会调用 getProxy() 方法获取代理对象,方法的参数就是抽象接口的对象.class. 下面会有代码示例:】

代码实现:

动态代理步骤:

创建一个实现接口InvocationHandler的类,它必须实现invoke方法
创建被代理的类以及接口(即上述的Rent抽象接口)
通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
通过代理调用方法
1、编写 ProxyInvocationHandler 类实现 InvocationHandler 接口 该接口就一个invoke方法 

 1 /**
 2  * 房产中介
 3  * @author huanghao
 4  * @Date 2021/9/30 14:31
 5  */
 6 public class ProxyInvocationHandler implements InvocationHandler {
 7 
 8     //被代理的接口
 9     private Rent rent;
10 
11     public void setRent(Rent rent){
12         this.rent=rent;
13     }
14 
15     //生成得到代理类
16     public Object getProxy(){
17         Object o = Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
18         return o;
19     }
20 
21     //处理代理实例,并返回结果  proxy :代理类 method : 代理类的调用处理程序的方法对象
22     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
23         System.out.println("找客户");
24         //执行目标方法获得结果 核心:本质利用反射实现!
25         Object result = method.invoke(rent, args);
26         System.out.println("结束订单");
27         return result;
28     }
29 }

客户端调用:

 1 /**
 2  * 客户端
 3  * @author huanghao
 4  * @Date 2021/9/30 15:44
 5  */
 6 public class Client {
 7 
 8 
 9     public static void main(String[] args) {
10         //真实角色
11         Host host = new Host();
12         // 代理实例的调用处理程序
13         ProxyInvocationHandler pro = new ProxyInvocationHandler();
14         pro.setRent(host);  //将真实角色放置进去!
15         Rent proxy = (Rent) pro.getProxy(); //动态生成对应的代理类!
16         proxy.hostRent();
17     }
18 }

编写JDK动态代理工具类:

编写一个通用的动态代理实现的类!可以为任意真实对象生成代理类

 1 /**
 2  *  JDK动态代理工具类
 3  * @author huanghao
 4  * @Date 2021/9/30 14:31
 5  */
 6 public class ProxyInvocationHandler implements InvocationHandler {
 7 
 8     //被代理的接口
 9     private Object target;
10 
11     public void setRent(Rent rent){
12         this.target=rent;
13     }
14 
15     //生成得到代理类
16     public Object getProxy(){
17         Object o = Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
18         return o;
19     }
20 
21     //处理代理实例,并返回结果  proxy :代理类 method : 代理类的调用处理程序的方法对象
22     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
23         //执行目标方法获得结果 核心:本质利用反射实现!
24         Object result = method.invoke(target, args);
25         return result;
26     }
27 }

优点:

1.可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .

2.公共的业务由代理来完成 . 实现了业务的分工 ,

3.公共业务发生扩展时变得更加集中和方便 .

4.一个动态代理 , 一般代理某一类业务

5.一个动态代理可以代理多个类,代理的是接口!

疑问:在 ProxyInvocationHandler  类中实现的invoke 方法什么时候被调用?

博文地址 : https://blog.csdn.net/zcc_0015/article/details/22695647

 

横线中说明   :  ProxyInvocationHandler  和 Proxy 两者之间的关系及 实现接口中的invoke方法什么时候被调用了  


 

一、动态代理与静态代理的区别。
(1)Proxy类的代码被固定下来,不会因为业务的逐渐庞大而庞大;
(2)可以实现AOP编程,这是静态代理无法实现的;
(3)解耦,如果用在web业务下,可以实现数据层和业务层的分离。
(4)动态代理的优势就是实现无侵入式的代码扩展。
静态代理这个模式本身有个大问题,如果类方法数量越来越多的时候,代理类的代码量是十分庞大的。所以引入动态代理来解决此类问题
二、动态代理

Java中动态代理的实现,关键就是这两个东西:Proxy、InvocationHandler,下面从InvocationHandler接口中的invoke方法入手,简单说明一下Java如何实现动态代理的。
首先,invoke方法的完整形式如下:

1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable  
2     {  
3   
4         method.invoke(obj, args);  
5   
6         return null;  
7     } 

 

首先猜测一下,method是调用的方法,即需要执行的方法;args是方法的参数;proxy,这个参数是什么?

以上invoke()方法的实现即是比较标准的形式,我们看到,这里并没有用到proxy参数。查看JDK文档中Proxy的说明,如下: 

 

 

 由此可以知道以上的猜测是正确的,同时也知道,proxy参数传递的即是代理类的实例。 
为了方便说明,这里写一个简单的例子来实现动态代理。

1 //抽象角色(动态代理只能代理接口)  
2 public interface Subject {  
3       
4     public void request();  
5 }

 

1 //真实角色:实现了Subject的request()方法  
2 public class RealSubject implements Subject{  
3       
4     public void request(){  
5         System.out.println("From real subject.");  
6     }  
7 }  

 

 1 //实现了InvocationHandler  
 2 public class DynamicSubject implements InvocationHandler  
 3 {  
 4     private Object obj;//这是动态代理的好处,被封装的对象是Object类型,接受任意类型的对象  
 5   
 6     public DynamicSubject()  
 7     {  
 8     }  
 9   
10     public DynamicSubject(Object obj)  
11     {  
12         this.obj = obj;  
13     }  
14   
15     //这个方法不是我们显示的去调用  
16     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable  
17     {  
18         System.out.println("before calling " + method);  
19   
20         method.invoke(obj, args);  
21   
22         System.out.println("after calling " + method);  
23   
24         return null;  
25     }  
26   
27 } 

 

 1 //客户端:生成代理实例,并调用了request()方法  
 2 public class Client {  
 3   
 4     public static void main(String[] args) throws Throwable{  
 5         // TODO Auto-generated method stub  
 6   
 7         Subject rs=new RealSubject();//这里指定被代理类  
 8         InvocationHandler ds=new DynamicSubject(rs);  
 9         Class<?> cls=rs.getClass();  
10           
11         //以下是一次性生成代理  
12           
13         Subject subject=(Subject) Proxy.newProxyInstance(  
14                 cls.getClassLoader(),cls.getInterfaces(), ds);  
15           
16         //这里可以通过运行结果证明subject是Proxy的一个实例,这个实例实现了Subject接口  
17         System.out.println(subject instanceof Proxy);  
18           
19         //这里可以看出subject的Class类是$Proxy0,这个$Proxy0类继承了Proxy,实现了Subject接口  
20         System.out.println("subject的Class类是:"+subject.getClass().toString());  
21           
22         System.out.print("subject中的属性有:");  
23           
24         Field[] field=subject.getClass().getDeclaredFields();  
25         for(Field f:field){  
26             System.out.print(f.getName()+", ");  
27         }  
28           
29         System.out.print("\n"+"subject中的方法有:");  
30           
31         Method[] method=subject.getClass().getDeclaredMethods();  
32           
33         for(Method m:method){  
34             System.out.print(m.getName()+", ");  
35         }  
36           
37         System.out.println("\n"+"subject的父类是:"+subject.getClass().getSuperclass());  
38           
39         System.out.print("\n"+"subject实现的接口是:");  
40           
41         Class<?>[] interfaces=subject.getClass().getInterfaces();  
42           
43         for(Class<?> i:interfaces){  
44             System.out.print(i.getName()+", ");  
45         }  
46   
47         System.out.println("\n\n"+"运行结果为:");  
48         subject.request();  
49     }  
50 }  

 

 1 运行结果如下:此处省略了包名,***代替  
 2 true  
 3 subject的Class类是:class $Proxy0  
 4 subject中的属性有:m1, m3, m0, m2,   
 5 subject中的方法有:request, hashCode, equals, toString,   
 6 subject的父类是:class java.lang.reflect.Proxy  
 7 subject实现的接口是:cn.edu.ustc.dynamicproxy.Subject,   
 8   
 9 运行结果为:  
10 before calling public abstract void ***.Subject.request()  
11 From real subject.  
12 after calling public abstract void ***.Subject.request()  

 

PS:这个结果的信息非常重要,至少对我来说。因为我在动态代理犯晕的根源就在于将上面的subject.request()理解错了,至少是被表面所迷惑,没有发现这个subject和Proxy之间的联系,一度纠结于最后调用的这个request()是怎么和invoke()联系上的,而invoke又是怎么知道request存在的。其实上面的true和class $Proxy0就能解决很多的疑问,再加上下面将要说的$Proxy0的源码,完全可以解决动态代理的疑惑了。

从以上代码和结果可以看出,我们并没有显示的调用invoke()方法,但是这个方法确实执行了。下面就整个的过程进行分析一下:

从Client中的代码看,可以从newProxyInstance这个方法作为突破口,我们先来看一下Proxy类中newProxyInstance方法的源代码:

 1 public static Object newProxyInstance(ClassLoader loader,  
 2         Class<?>[] interfaces,  
 3         InvocationHandler h)  
 4 throws IllegalArgumentException  
 5 {  
 6     if (h == null) {  
 7         throw new NullPointerException();  
 8     }  
 9   
10     /* 
11      * Look up or generate the designated proxy class. 
12      */  
13     Class cl = getProxyClass(loader, interfaces);  
14   
15     /* 
16      * Invoke its constructor with the designated invocation handler. 
17      */  
18     try {  
19            /* 
20             * Proxy源码开始有这样的定义: 
21             * private final static Class[] constructorParams = { InvocationHandler.class }; 
22             * cons即是形参为InvocationHandler类型的构造方法 
23            */  
24         Constructor cons = cl.getConstructor(constructorParams);  
25         return (Object) cons.newInstance(new Object[] { h });  
26     } catch (NoSuchMethodException e) {  
27         throw new InternalError(e.toString());  
28     } catch (IllegalAccessException e) {  
29         throw new InternalError(e.toString());  
30     } catch (InstantiationException e) {  
31         throw new InternalError(e.toString());  
32     } catch (InvocationTargetException e) {  
33         throw new InternalError(e.toString());  
34     }  
35 } 

Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)做了以下几件事.
(1)根据参数loader和interfaces调用方法 getProxyClass(loader, interfaces)创建代理类$Proxy0.$Proxy0类 实现了interfaces的接口,并继承了Proxy类.
(2)实例化$Proxy0并在构造方法中把DynamicSubject传过去,接着$Proxy0调用父类Proxy的构造器,为h赋值,如下:

1 class Proxy{  
2     InvocationHandler h=null;  
3     protected Proxy(InvocationHandler h) {  
4         this.h = h;  
5     }  
6     ...  
7 } 

 

   来看一下这个继承了Proxy的$Proxy0的源代码: 这里在  request 方法中 调用了invoke 方法

 1 public final class $Proxy0 extends Proxy implements Subject {  
 2     private static Method m1;  
 3     private static Method m0;  
 4     private static Method m3;  
 5     private static Method m2;  
 6   
 7     static {  
 8         try {  
 9             m1 = Class.forName("java.lang.Object").getMethod("equals",  
10                     new Class[] { Class.forName("java.lang.Object") });  
11   
12             m0 = Class.forName("java.lang.Object").getMethod("hashCode",  
13                     new Class[0]);  
14   
15             m3 = Class.forName("***.RealSubject").getMethod("request",  
16                     new Class[0]);  
17   
18             m2 = Class.forName("java.lang.Object").getMethod("toString",  
19                     new Class[0]);  
20   
21         } catch (NoSuchMethodException nosuchmethodexception) {  
22             throw new NoSuchMethodError(nosuchmethodexception.getMessage());  
23         } catch (ClassNotFoundException classnotfoundexception) {  
24             throw new NoClassDefFoundError(classnotfoundexception.getMessage());  
25         }  
26     } //static  
27   
28     public $Proxy0(InvocationHandler invocationhandler) {  
29         super(invocationhandler);  
30     }  
31   
32     @Override  
33     public final boolean equals(Object obj) {  
34         try {  
35             return ((Boolean) super.h.invoke(this, m1, new Object[] { obj })) .booleanValue();  
36         } catch (Throwable throwable) {  
37             throw new UndeclaredThrowableException(throwable);  
38         }  
39     }  
40   
41     @Override  
42     public final int hashCode() {  
43         try {  
44             return ((Integer) super.h.invoke(this, m0, null)).intValue();  
45         } catch (Throwable throwable) {  
46             throw new UndeclaredThrowableException(throwable);  
47         }  
48     }  
49   
50     public final void request() {  
51         try {  
52             super.h.invoke(this, m3, null);  
53             return;  
54         } catch (Error e) {  
55         } catch (Throwable throwable) {  
56             throw new UndeclaredThrowableException(throwable);  
57         }  
58     }  
59   
60     @Override  
61     public final String toString() {  
62         try {  
63             return (String) super.h.invoke(this, m2, null);  
64         } catch (Throwable throwable) {  
65             throw new UndeclaredThrowableException(throwable);  
66         }  
67     }  
68 }  

 

 

 

接着把得到的$Proxy0实例强制转换成Subject,并将引用赋给subject。当执行subject.request()方法时,就调用了$Proxy0类中的request()方法,进而调用父类Proxy中的h的invoke()方法.即InvocationHandler.invoke()。

 

PS:1、需要说明的一点是,Proxy类中getProxyClass方法返回的是Proxy的Class类。之所以说明,是因为我一开始犯了个低级错误,以为返回的是“被代理类的Class类”- -!推荐看一下getProxyClass的源码,很长=。=
2、从$Proxy0的源码可以看出,动态代理类不仅代理了显示定义的接口中的方法,而且还代理了java的根类Object中的继承而来的equals()、hashcode()、toString()这三个方法,并且仅此三个方法。

 

Q:到现在为止,还有一个疑问,invoke方法中的第一个参数是Proxy的实例(准确说,最终用到的是$Proxy0的实例),但是有什么用呢?或者说,程序内是怎样显示出作用的?
A:就本人目前的水平看来,这个proxy参数并没有什么作用,在整个动态代理机制中,并没有用到InvocationHandler中invoke方法的proxy参数。而传入的这个参数实际是代理类的一个实例。我想可能是为了让程序员在invoke方法中使用反射来获取关于代理类的一些信息吧。

 

 


 

 

AOP简介:

aop 面向切面编程: 其实原理就是代理模式的延续!【即aop在不改变原有代码的情况下,去增加新的功能。】

面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

 

AOP在Spring中的作用:提供声明式事务;允许用户自定义切面

横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存, 事务等等 …

 

切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。

通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。

目标(Target):被通知对象。

代理(Proxy):向目标对象应用通知之后创建的对象。

切入点(PointCut):切面通知 执行的 “地点”的定义。

连接点(JointPoint):与切入点匹配的执行点。

 

 

 

 

 SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

 

 

 

 

 

使用AOP

使用AOP需要导入依赖

1 
2 
3 
4    org.aspectj
5    aspectjweaver
6    1.9.4
7 

 

使用AOP的方式有三种:

  1. 通过 Spring API 实现
  2. 自定义类来实现Aop
  3. 使用注解实现

 

创建测试环境

方式一:实现相应的接口:

创建接口

1 public interface UserService {
2     public void  add();
3     public void  del();
4     public void  sel();
5     public void  upd();
6 }

建立实现类:

 1 public class UserServiceImpl implements UserService {
 2 
 3     @Override
 4     public void add() {
 5         System.out.println("增加了一个用户");
 6     }
 7 
 8     @Override
 9     public void del() {
10         System.out.println("删除了一个用户");
11     }
12 
13     @Override
14     public void sel() {
15         System.out.println("查询了一个用户");
16     }
17 
18     @Override
19     public void upd() {
20         System.out.println("修改了一个用户");
21     }
22 }

通过 Spring API 实现

使用Spring API 实现AOP需要去实现前置增强接口或后置增强接口

 

 

前置增强:在执行的方法前面插入 实现 MethodBeforeAdvice 接口

 1 /**
 2  * 前置增强
 3  * @Author Tu_Yooo
 4  * @create 2021/3/20 22:18
 5  */
 6 public class Log implements MethodBeforeAdvice {
 7 
 8     //method:目标对象的方法  args: 参数  target:目标对象
 9     @Override
10     public void before(Method method, Object[] args, Object target) throws Throwable {
11         System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
12     }
13 }

后置增强 在执行的方法后面插入 实现 AfterReturningAdvice 接口

 1 /**
 2  * 后置增强
 3  * @Author Tu_Yooo
 4  * @create 2021/3/20 23:08
 5  */
 6 public class AfterReturning implements AfterReturningAdvice {
 7 
 8     //returnValue:返回值
 9     @Override
10     public void AfterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
11         System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
12     }
13 }

然后就需要在Spring配置文件中注册进行配置完成Aop面向切面插入的操作。

 1 
 2  3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:aop="http://www.springframework.org/schema/aop"
 5        xsi:schemaLocation="http://www.springframework.org/schema/beans
 6        http://www.springframework.org/schema/beans/spring-beans.xsd
 7        http://www.springframework.org/schema/aop
 8        http://www.springframework.org/schema/aop/spring-aop.xsd">
 9 
10 
11     
12     class="cn.pojo.serviceImpl.UserServiceImpl"/>
13     class="cn.pojo.log.Log"/>
14     class="cn.pojo.log.afterReturning"/>
15 
16     
17     
18     
19         
20         
21         
22         
23         
24     
25 
26 

测试:

1 public class Main {
2     public static void main(String[] args) {
3         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
4         //注意点:动态代理代理的是接口
5         UserService userService = (UserService) applicationContext.getBean("userService");
6         userService.add();
7     }
8 }

结果:

 

 

方式二: 自定义类来实现AOP

目标业务类不变依旧是userServiceImpl (需要被插入日志的类)

编写自定义切入类:

1 public class DiyPointcut {
2     public void before(){
3         System.out.println("---------方法执行前---------");
4     }
5     public void after(){
6         System.out.println("---------方法执行后---------");
7     }
8 }

在Spring中注册:

 

 1  <!--注册bean-->
 2     <bean id="userService" class="cn.pojo.serviceImpl.UserServiceImpl"/>
 3     <bean id="diy" class="cn.pojo.div.DiyPointcut"/>
 4 
 5     <!--方式二:自定义类-->
 6     <aop:config>
 7         <!--自定义切面 ref要引用的类-->
 8         <aop:aspect ref="diy">
 9             <!--切入点:pointcut  expression:表达式 execution:要执行的位置-->
10             <aop:pointcut id="pointcut" expression="execution(* cn.pojo.serviceImpl.UserServiceImpl.*(..))"/>
11             <!--通知-->
12             <aop:before method="before" pointcut-ref="pointcut"/>
13             <aop:after method="after" pointcut-ref="pointcut"/>
14          </aop:aspect>
15     </aop:config>

 

结果:

 

 

 

 

方式三: 使用注解开发

编写一个注解增强类:在类上使用注解进行声明。

 1 // @Aspect 标注这个类是一个切面
 2 @Aspect
 3 public class AnnotationPointCut {
 4 
 5 
 6     @Before("execution(* cn.pojo.serviceImpl.UserServiceImpl.*(..))")
 7     public void before(){
 8         System.out.println("---------方法执行前---------");
 9     }
10     @After("execution(* cn.pojo.serviceImpl.UserServiceImpl.*(..))")
11     public void after(){
12         System.out.println("---------方法执行后---------");
13     }
14 
15     /**
16      * 环绕增强中,给定一个参数,代表要处理切入的点
17      */
18     @Around("execution(* cn.pojo.serviceImpl.UserServiceImpl.*(..))")
19     public void around(ProceedingJoinPoint joinPoint) throws Throwable {
20         System.out.println("环绕前");
21         //执行方法
22         Object proceed = joinPoint.proceed();
23         System.out.println("环绕后");
24     }
25 
26 }

在spring中进行注册:

1     <!--注册bean-->
2     <bean id="userService" class="cn.pojo.serviceImpl.UserServiceImpl"/>
3     <bean id="annotationPointCut" class="cn.pojo.log.AnnotationPointCut"/>
4     <!--开启AOP注解支持 JDK(默认proxy-target-class="false") cglib(proxy-target-class="true")-->
5     <aop:aspectj-autoproxy proxy-target-class="false"/>

结果:

 

 aop:aspectj-autoproxy:说明:

通过aop命名空间的< aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被< aop:aspectj-autoproxy />隐藏起来了

 

 

Spring整合Mybatis:

MyBatis-Spring官方文档:http://mybatis.org/spring/zh/

 

简介

 

什么是MyBatis-Spring:

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring
的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为
Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或MyBatis-Spring。

 

知识基础
在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要——因为本手册中不会提供二者的基本内容,安装和配置教程。

MyBatis-Spring 需要以下版本:(版本需要对应 不然会出问题)

 

 

 

导入依赖:

 1        <!--junit测试-->
 2         <dependency>
 3             <groupId>junit</groupId>
 4             <artifactId>junit</artifactId>
 5             <version>4.13.2</version>
 6         </dependency>
 7         <!--mySQL驱动-->
 8         <dependency>
 9             <groupId>mysql</groupId>
10             <artifactId>mysql-connector-java</artifactId>
11             <version>8.0.17</version>
12         </dependency>
13         <!--Mybatis-->
14         <dependency>
15             <groupId>org.mybatis</groupId>
16             <artifactId>mybatis</artifactId>
17             <version>3.5.2</version>
18         </dependency>
19         <!--MVC-->
20         <dependency>
21             <groupId>org.springframework</groupId>
22             <artifactId>spring-webmvc</artifactId>
23             <version>5.3.4</version>
24         </dependency>
25         <!--Spring操作数据库的话 需要SpringJDBC包-->
26         <dependency>
27             <groupId>org.springframework</groupId>
28             <artifactId>spring-jdbc</artifactId>
29             <version>5.3.4</version>
30         </dependency>
31         <!-- AOP织入包 -->
32         <dependency>
33             <groupId>org.aspectj</groupId>
34             <artifactId>aspectjweaver</artifactId>
35             <version>1.9.4</version>
36         </dependency>
37         <!--mybatis整合Spring包-->
38         <dependency>
39             <groupId>org.mybatis</groupId>
40             <artifactId>mybatis-spring</artifactId>
41             <version>2.0.6</version>
42         </dependency>

 

配置Maven静态资源过滤:(配置这个有时候xml文件会编译不到,会报错,配置后能编译到)【但是需要注意路径 directory 】 mybatis.xml文件使用的时候还需要配置别名 用来获取对象 (及mapper的位置)

 1     <build>
 2         <resources>
 3         <resource>
 4                 <directory>src/main/resources</directory>
 5                 <includes>
 6                     <include>**/*.properties</include>
 7                     <include>**/*.xml</include>
 8                 </includes>
 9                 <filtering>true</filtering>
10             </resource>
11             <resource>
12                 <directory>src/main/java</directory>
13                 <includes>
14                     <include>**/*.properties</include>
15                     <include>**/*.xml</include>
16                 </includes>
17                 <filtering>true</filtering>
18             </resource>
19         </resources>
20     </build>

快速上手

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

 1 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 2 <property name="dataSource" ref="dataSource" /> 3 </bean> 

 

同时还可以绑定myBatis原有的配置文件mybatis-config.xml和映射器userMapper.xml(即把这两个xml文件整合到spring.xml一起,如果进行完全整合的话 其实mybatis.xml文件都可以不用进行配置)

1    <!--SQLSessionFactory-->
2     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
3         <property name="dataSource" ref="dataSource" />
4         <!--绑定MyBatis配置文件-->
5         <property name="configLocation" value="classpath:mybatis-config.xml"/>
6         <!--注册映射器-->
7         <property name="mapperLocations" value="classpath:mapper/UserMapper.xml"/>
8     </bean>

 

 

注意:SqlSessionFactory 需要一个 DataSource(数据源)【这个数据源就绑定到sqlSessionFactory上去】。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

1  <!--DataSource:spring的数据源替换MyBatis的配置
2     Spring提供的JDBC: org.springframework.jdbc.datasource.DriverManagerDataSource
3     -->
4     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
5         <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
6         <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
7         <property name="username" value="root"/>
8         <property name="password" value="123456"/>
9     </bean>

得到SqlSessionFactory以后就可以创建SqlSession  【这个sqlSession就是执行sql的一个对象】 即使用 SqlSessionFactory 创建 sqlSession 如下方代码:
在spring中创建的SqlSession叫做SqlSessionTemplate

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。

1   <!--获取SQLSession-->
2     <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
3         <!--SqlSessionTemplate 只能使用构造器注入 因为它没有set方法-->
4         <constructor-arg index="0" ref="sqlSessionFactory"/>
5     </bean>

完整结构如下:

 

Mybatis整合Spring SqlSessionFactoryBean的属性介绍:

  • dataSource属性是必须指定的,它表示用于连接数据库的数据源
  • mapperLocations:它表示我们的Mapper文件存放的位置,当我们的Mapper文件跟对应的Mapper接口处于同一位置的时候可以不用指定该属性的值。
  • configLocation:用于指定Mybatis的配置文件位置。如果指定了该属性,那么会以该配置文件的内容作为配置信息构建对应的SqlSessionFactoryBuilder,但是后续属性指定的内容会覆盖该配置文件里面指定的对应内容。
  • typeAliasesPackage:它一般对应我们的实体类所在的包,这个时候会自动取对应包中不包括包名的简单类名作为包括包名的别名。多个package之间可以用逗号或者分号等来进行分隔。(value的值一定要是包的全名)
  • typeAliases:数组类型,用来指定别名的。指定了这个属性后,Mybatis会把这个类型的短名称作为这个类型的别名,前提是该类上没有标注@Alias注解,否则将使用该注解对应的值作为此种类型的别名。(value的值一定要是类的完全限定名)

 

测试:

 创建实体类

1 @Data
2 public class User {
3     private int id;
4     private String name;
5     private String pwd;
6 }

编写接口:

1 public interface UserMapper {
2 
3     //查询全部用户
4     public List<User> seleUser();
5 }

编写映射器:xml  书写sql的

 1 <?xml version="1.0" encoding="UTF8" ?>
 2 <!DOCTYPE mapper
 3         PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5 
 6 <!--namespace 命名空间 指向 定义一个Dao接口-->
 7 <mapper namespace="cn.tony.dao.UserMapper"><!--定义接口全路径名-->
 8 
 9     <!--查询语句select id指向接口中的方法名 resultType指向查询结果集封装-->
10     <select id="seleUser" resultType="user">
11      select * from user
12     </select>
13 
14 </mapper>

在Spring整合Mybatis中,需要自己编写dao层的实现类。

然后需要在这个类中获取 sqlSession 实例  这个实例在spring 初始化 这个实现类的时候 使用属性注入吧 sqlSession 这个实例给注入进去进行使用

sqlSession实例就是用来操作 jdbc 的执行sql 使用的。

 1 /**
 2  * UserMapper实现类
 3  * @Author Tu_Yooo
 4  * @create 2021/3/21 2:12
 5  */
 6 public class UserMapperImpl implements UserMapper{
 7 
 8     //在myBatis中 所有操作都由sqlSession来执行 在Spring整合Mybatis中所有操作由SqlSessionTemplate来执行
 9     private SqlSessionTemplate sqlSession;
10 
11     public void setSqlSession(SqlSessionTemplate sqlSession) {
12         this.sqlSession = sqlSession;
13     }
14 
15     @Override
16     public List<User> seleUser() {
17         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
18         return mapper.seleUser();
19     }
20 }

dao层的实现类在Spring中注册: 同时将 sqlSession 这个使用使用属性初始化 声明:

  1 <!--注册userMapper--> 2 <bean id="userMapper" class="cn.tony.dao.UserMapperImpl"> 3 <property name="sqlSession" ref="sqlSession"/> 4 </bean> 

 

 

 

 测试:

 1 public class SpringMain {
 2     public static void main(String[] args) {
 3         //获取Spring上下文对象
 4         ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
 5         UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
 6         List<User> users = userMapper.seleUser();
 7         for (User user : users) {
 8             System.out.println(user);
 9         }
10     }
11 }

 

步骤:

1、导入依赖

2、资源过滤

3、配置sqlSessionFactory

4、配置dataSource

5、将数据源 dataSource 整合到sqlSessionFactory

6、获取 sqlSession  

7、编写实体

8、编写接口

9、编写映射器

10、编写接口的实现类

11、在spring.xml中配置这个实现类的实例

12、测试 ,使用 ApplicationContext 获取bean对象 执行 接口中的方法

 

 

SqlSessionFactoryBuilder、sqlSessionFactory、sqlSession  之间的关系:

sqlSessionFactory:

1、SqlSessionFactory是MyBatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜像。SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象类获得,而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例。每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心。同时SqlSessionFactory也是线程安全的,SqlSessionFactory一旦被创建,应该在应用执行期间都存在.在应用运行期间不要重复创建多次,建议使用单例模式。SqlSessionFactory是创建SqlSession的工厂。

sqlSession  

2、SqlSession是MyBatis的关键对象,它是应用程序与持久层之间交互操作的一个单线程对象,类似于JDBC中的Connection。SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接,可以用SqlSession实例来直接执行被映射的SQL语句。每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能被共享,同时SqlSession也是线程不安全的,绝对不能讲SqlSeesion实例的引用放在一个类的静态字段甚至是实例字段中。也绝不能将SqlSession实例的引用放在任何类型的管理范围中,比如Servlet当中的HttpSession对象中。使用完SqlSeesion之后关闭Session很重要,应该确保使用finally块来关闭它。

 

SqlSessionFactory和SqlSession实现过程:

mybatis框架主要是围绕着SqlSessionFactory进行的,创建过程大概如下:
(1)、定义一个Configuration对象,其中包含数据源、事务、mapper文件资源以及影响数据库行为属性设置settings
(2)、通过配置对象,则可以创建一个SqlSessionFactoryBuilder对象
(3)、通过 SqlSessionFactoryBuilder 获得SqlSessionFactory 的实例。
(4)、SqlSessionFactory 的实例可以获得操作数据的SqlSession实例,通过这个实例对数据库进行操作

 

SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession生命周期
如果使用依赖注入框架(Spring)可不用在代码中关心作用域问题,因为依赖注入框架会为我们做好几乎一切工作。

SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳范围是方法范围(也就是局部方法变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情。

SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种bad smell。因此  SqlSessionFactory 的最佳范围是应用范围。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的范围是请求或方法范围。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理范围中,比如 Serlvet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的范围中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。

 

 第二种整合方式:

SqlSessionDaoSupport

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

 1 /**
 2  * UserMapper实现类
 3  * @Author Tu_Yooo
 4  * @create 2021/3/21 2:12
 5  */
 6 public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
 7     
 8     @Override
 9     public List<User> seleUser() {
10         SqlSession sqlSession = getSqlSession();
11         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
12         return mapper.seleUser();
13     }
14 }

需要在Spring容器中进行如下注册:

 1 <!--注册userMapper--> 2 <bean id="userMapper" class="cn.tony.dao.UserMapperImpl"> 3 <property name="sqlSessionFactory" ref="sqlSessionFactory" /> 4 </bean> 

 

 

Spring事务:

声明式事务: 事物的四大特性 (要么成功要么失败)

一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给
MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的
DataSourceTransactionManager 来实现事务管理。

Spring中的事务两种方式:
1.声明式事务:AOP
2.编程式事务:需要在代码中,进行事务的管理

 

标准配置

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

这里连接了数据源:

 1 <!--配置声明式事务--> 2 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 3 <property name="dataSource" ref="dataSource"/> 4 </bean> 

结合AOP 实现事务的织入: 这里配置事务通知 引用上面配置的spring容器创建好的管理事务的bean transactionManager 这个bean在上面配置了 他引用了配置好的数据源 dataSource

 1  <!--配置事务通知-->
 2     <tx:advice id="txAdvice" transaction-manager="transactionManager">
 3         <!--给哪些方法配置事务 为所有以add,delete..开头的方法 开启事务-->
 4         <!--配置事务的传播特性:propagation="REQUIRED"默认 支持当前事务 如果没有事务就新建一个事务-->
 5         <tx:attributes>
 6             <tx:method name="add" propagation="REQUIRED"/>
 7             <tx:method name="delete"/>
 8             <tx:method name="update"/>
 9             <tx:method name="query" read-only="true"/> <!--read-only="true"只读事务-->
10             <tx:method name="*" propagation="REQUIRED"/><!--所有方法-->
11         </tx:attributes>
12     </tx:advice>

注意:使用tx标签需要导入约束:

1 xmlns:tx="http://www.springframework.org/schema/tx"
2 http://www.springframework.org/schema/tx
3 http://www.springframework.org/schema/tx/spring-tx.xsd

结合业务实现事务的切入:  【配置aop:config 事务的切入】 【声明了在哪个包下的那个类需要进行切入事务】【然后引用了txAdvice 这里面配置了哪些单词开头的方法,然后 txAdvice 里面又引用了 transactionManager , transactionManager 又配置了数据源 

1  <!--配置事务的切入 AOP-->
2     <aop:config>
3         <aop:pointcut id="txpointcut" expression="execution(* cn.tony.dao.UserMapperImpl.*(..))"/>
4         <aop:advisor advice-ref="txAdvice" pointcut-ref="txpointcut"/>
5     </aop:config>

 

完整配置文件(参考)

mybatis-spring.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4        xmlns:aop="http://www.springframework.org/schema/aop"
 5        xmlns:tx="http://www.springframework.org/schema/tx"
 6        xsi:schemaLocation="http://www.springframework.org/schema/beans
 7        http://www.springframework.org/schema/beans/spring-beans.xsd
 8        http://www.springframework.org/schema/aop
 9        http://www.springframework.org/schema/aop/spring-aop.xsd
10        http://www.springframework.org/schema/tx
11        http://www.springframework.org/schema/tx/spring-tx.xsd">
12 
13 
14     <!--DataSource:spring的数据源替换MyBatis的配置
15     Spring提供的JDBC: org.springframework.jdbc.datasource.DriverManagerDataSource
16     -->
17     <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
18         <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
19         <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
20         <property name="username" value="root"/>
21         <property name="password" value="123456"/>
22     </bean>
23 
24     <!--SQLSessionFactory-->
25     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
26         <property name="dataSource" ref="dataSource" />
27         <!--绑定MyBatis配置文件-->
28         <property name="configLocation" value="classpath:mybatis-config.xml"/>
29         <!--注册映射器-->
30         <property name="mapperLocations" value="classpath:mapper/*.xml"/>
31     </bean>
32 
33     <!--获取SQLSession-->
34     <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
35         <!--SqlSessionTemplate 只能使用构造器注入 因为它没有set方法-->
36         <constructor-arg index="0" ref="sqlSessionFactory"/>
37     </bean>
38 
39     <!--配置声明式事务-->
40     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
41         <property name="dataSource" ref="dataSource"/>
42     </bean>
43 
44     <!--结合AOP 实现事务的织入-->
45     <!--配置事务通知-->
46     <tx:advice id="txAdvice" transaction-manager="transactionManager">
47         <!--给哪些方法配置事务 为所有以add,delete..开头的方法 开启事务-->
48         <!--配置事务的传播特性:propagation="REQUIRED"默认 支持当前事务 如果没有事务就新建一个事务-->
49         <tx:attributes>
50             <tx:method name="add" propagation="REQUIRED"/>
51             <tx:method name="delete"/>
52             <tx:method name="update"/>
53             <tx:method name="query" read-only="true"/> <!--read-only="true"只读事务-->
54             <tx:method name="*" propagation="REQUIRED"/><!--所有方法-->
55         </tx:attributes>
56     </tx:advice>
57 
58     <!--配置事务的切入-->
59     <aop:config>
60         <aop:pointcut id="txpointcut" expression="execution(* cn.tony.dao.UserMapperImpl.*(..))"/>
61         <aop:advisor advice-ref="txAdvice" pointcut-ref="txpointcut"/>
62     </aop:config>
63 
64 
65     <!--注册userMapper-->
66     <bean id="userMapper" class="cn.tony.dao.UserMapperImpl">
67         <property name="sqlSessionFactory" ref="sqlSessionFactory" />
68     </bean>
69 
70 
71 
72 </beans>

 

  

posted @ 2021-09-29 17:41  易言。  阅读(461)  评论(0)    收藏  举报