Java学习笔记11-2——Spring5

7.Bean的自动装配

自动装配说明

自动装配是使用spring满足bean依赖的一种方法。

spring会在应用上下文中为某个bean寻找其依赖的bean。

Spring中bean有三种装配机制,分别是:

  • 在xml中显式配置;
  • 在java中显式配置;
  • 隐式的bean发现机制和自动装配。

Spring的自动装配需要从两个角度来实现,或者说是两个操作:

  • 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
  • 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;

组件扫描和自动装配组合发挥巨大威力,使得显式的配置降低到最少。

在后面的开发中,推荐不使用自动装配和xml配置 , 而使用注解

测试环境搭建

1.新建一个项目
2.新建两个实体类Cat和Dog。都有一个叫的方法

public class Cat {
    public void shout() {
        System.out.println("miao~");
    }
}
public class Dog {
    public void shout() {
        System.out.println("wang~");
    }
}

3.新建一个Person类

public class Person {
    private Cat cat;
    private Dog dog;
    private String pname;

    @Override
    public String toString() {
        return "Person{" +
                "cat=" + cat +
                ", dog=" + dog +
                ", pname='" + pname + '\'' +
                '}';
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public Cat getCat() {
        return cat;
    }

    public Dog getDog() {
        return dog;
    }

    public String getPname() {
        return pname;
    }
}

4.编写Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dog" class="pojo.Dog"/>
    <bean id="cat" class="pojo.Cat"/>

    <bean id="person" class="pojo.Person">
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
        <property name="pname" value="张三"/>
    </bean>
</beans>

5.测试

    @Test
    public void testMethodAutowire() {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Person person =context.getBean("person",Person.class);
        person.getCat().shout();
        person.getDog().shout();
    }

如果测试没有问题,进入下一步的学习

byName(按名称自动装配)

由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。

采用自动装配将避免这些错误,并且使配置简单化。

测试

1、修改bean配置,增加一个属性 autowire=“byName”

    <bean id="person" class="pojo.Person" autowire="byName">
        <property name="pname" value="张三"/>
    </bean>

2、再次测试,结果依旧成功输出!

3、我们将 cat 的bean id修改为 cat123

4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。

5、但如果将Person类下的方法setCat(Cat cat)改为setCat123(Cat cat)的话又能正常运行,再次印证第4点说明

byName运行机制小结:

当一个bean节点带有 autowire byName的属性时。

  1. 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
  2. 去spring容器中寻找是否有此字符串名称id的对象。
  3. 如果有,就取出注入;如果没有,就报空指针异常。

byType(按类型自动装配)

使用byType自动装配时首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报NoUniqueBeanDefinitionException(bean定义不唯一异常)。

测试

1、将user的bean配置修改一下 : autowire=“byType”

    <bean id="person" class="pojo.Person" autowire="byType">
        <property name="pname" value="张三"/>
    </bean>

2、测试,正常输出

3、在注册一个cat 的bean对象!

	<bean id="dog" class="pojo.Dog"/>
    <bean id="cat" class="pojo.Cat"/>
    <bean id="cat2" class="pojo.Cat"/>

4、测试,报错:NoUniqueBeanDefinitionException

5、删掉cat2,将cat的bean名称改掉,测试。

	<bean id="dog" class="pojo.Dog"/>
    <bean id="cat123" class="pojo.Cat"/>

因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。

	<bean id="dog" class="pojo.Dog"/>
    <bean  class="pojo.Cat"/>

这就是按照类型自动装配。

使用注解进行自动装配

jdk1.5开始支持注解,spring2.5开始全面支持注解。可以利用注解的方式注入属性。

1、在spring配置文件中引入context文件头约束

xmlns:context="http://www.springframework.org/schema/context"

http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd

2、开启属性注解支持!

<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
        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

该注解和下面的@Qualifier注解属于org.springframework.beans.factory.annotation

@Autowired是按类型自动转配的,不支持id匹配。
需要导入 spring-aop的包!
测试:

1、将Person类中的set方法去掉,使用@Autowired注解

public class Person {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    private String pname;

    @Override
    public String toString() {
        return "Person{" +
                "cat=" + cat +
                ", dog=" + dog +
                ", pname='" + pname + '\'' +
                '}';
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public Cat getCat() {
        return cat;
    }

    public Dog getDog() {
        return dog;
    }
}

此时配置文件内容(因为是按类型自动装配的,所以cat的bean id改为啥都行)

...
    <context:annotation-config/>

    <bean id="dog" class="pojo.Dog"/>
    <bean id="cat123" class="pojo.Cat"/>
    ...

2、测试成功运行

另外,当@Autowired(required=false)对象可以为null;
当@Autowired(required=true),对象必须存对象,不能为null。

@Qualifier

@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
@Qualifier不能单独使用。

测试实验步骤:

1、配置文件修改内容,保证类型存在对象。且名字不为类的默认名字

    <bean id="dog1" class="pojo.Dog"/>
    <bean id="dog2" class="pojo.Dog"/>
    <bean id="cat1" class="pojo.Cat"/>
    <bean id="cat2" class="pojo.Cat"/>

2、没有加Qualifier测试,直接报错

3、在Person属性上添加Qualifier注解

    @Autowired
    @Qualifier(value = "cat2")
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog2")
    private Dog dog;

4、测试,成功输出!

@Resource

该注解属于javax.annotation的

  1. @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
  2. 其次再进行默认的byName方式进行装配;
  3. 如果以上都不成功,则按byType的方式自动装配。
  4. 都不成功,则报异常。

实验测试:
实验1:(使之既按名称查找,也按类型查找)

    @Resource(name = "cat2")
    private Cat cat;
    @Resource
    private Dog dog;
    <bean id="dog" class="pojo.Dog"/>
    <bean id="cat1" class="pojo.Cat"/>
    <bean id="cat2" class="pojo.Cat"/>

成功输出结果

实验2:(使之按类型查找)

    <bean id="dog" class="pojo.Dog"/>
    <bean id="cat1" class="pojo.Cat"/>
    @Resource
    private Cat cat;
    @Resource
    private Dog dog;

同样成功输出结果

@Autowired与@Resource异同

1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。

2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用

3、@Resource默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

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

</beans>

配置扫描哪些包下的注解

<!--指定注解扫描包-->
<context:component-scan base-package="pojo"/>

@Component(Bean的实现)

@Component:意为组件,该注解放在类上,说明这个类被Spring管理了,就是bean

<?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="pojo"/>
    
</beans>
@Component
// 或者@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
    public String name = "张三";
}
    @Test
    public void test(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) applicationContext.getBean("user");
        System.out.println(user.name);
    }

@Value属性注入

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

	@Value("张三")
	// 相当于<property name="name" value="张三"/>
    public String name;

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

    @Value("张三")
    // 相当于<property name="name" value="张三"/>
    public void setName(String name) {
        this.name = name;
    }

@Component的衍生注解

@Component有几个衍生注解,我们在Web开发中,会按照MVC三层架构进行分层

  • DAO层:@Repository
  • Service层:@Service
  • Controller层:@Controller

这四个注解的功能是一样的,都是代表将某个类注册到Spring中,装配bean

注解实现自动装配

上面已讲解过

@scope设置作用域

  • singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
  • prototype:原型(多例)模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收

该注解放在类上

@Controller("user")
@Scope("prototype")
public class User {
   @Value("张三")
   public String name;
}

注解开发小结

XML与注解比较

  • XML可以适用任何场景 ,结构清晰,维护方便
  • 注解不是自己提供的类使用不了,开发简单方便

xml与注解整合开发 【推荐】:

  • xml管理Bean

  • 注解完成属性注入

作用:

  • 进行注解驱动注册<context:annotation-config/>,从而使注解生效
  • 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显式地向Spring注册
  • 如果不扫描包,就需要手动配置bean
  • 如果不加注解驱动,则注入的值为null

9.使用Java的方式配置Spring

即完全不使用xml配置

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

测试:

1、编写一个实体类,Dog

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

2、新建一个config配置包,编写一个MyConfig配置类

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

@Configuration  //代表这是一个配置类
public class MyConfig {

   @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id
   public Dog dog(){
       return new Dog();
  }
}

3、测试

可以发现这里是通过new AnnotationConfigApplicationContext来读取Java配置类,对比之前的new ClassPathXmlApplicationContext,之前的是读取XML配置文件来获取ApplicationContext

@Test
public void test2(){
   ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
   Dog dog = (Dog) applicationContext.getBean("dog");
   System.out.println(dog.name);
}

4、成功输出结果

如何导入其他配置类
1、我们再编写一个配置类!

@Configuration  //代表这是一个配置类
public class MyConfig2 {
}

2、在之前的配置类中我们来选择导入这个配置类

@Configuration
@Import(MyConfig2.class)  //导入合并其他配置类,类似于配置文件中的 inculde 标签
public class MyConfig {

   @Bean
   public Dog dog(){
       return new Dog();
  }
}

关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可

10.代理模式

为什么要学习代理模式,因为AOP的底层机制就是动态代理!【SpringAOP和SpringMVC】

代理模式包括静态代理和动态代理

学习AOP之前 , 我们要先了解一下代理模式

静态代理

以租房为例子
在这里插入图片描述

静态代理角色分析

  • 抽象业务:一般使用接口或者抽象类来实现
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后 , 一般会做一些附属的操作
  • 客户端:使用代理角色来进行一些操作

代码实现

Rent接口 即租房这一抽象业务

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

Host 即真实角色: 房东,房东要出租房子

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

Proxy 即代理角色:中介

//代理角色:中介
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("收中介费");
  }
}

Client 即客户端

//客户类,一般客户都会去找代理!
public class Client {
   public static void main(String[] args) {
       //房东要租房
       Host host = new Host();
       //中介帮助房东
       Proxy proxy = new Proxy(host);

       //你去找中介
       proxy.rent();
  }
}

静态代理的好处:

  • 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情
  • 公共的业务由代理来完成,实现了业务的分工
  • 公共业务发生扩展时变得更加集中和方便

缺点 :

  • 类多了,多了代理类,工作量变大了,开发效率降低

我们想要静态代理的好处,又不想要静态代理的缺点,所以,就有了动态代理

加深理解静态代理

1、创建一个抽象业务接口(例如UserService),比如平时做的业务,抽象起来就是增删改查
UserService类似上一例子的租房业务

//抽象角色:增删改查业务
public interface UserService {
   void add();
   void delete();
   void update();
   void query();
}

2、我们需要一个真实对象来完成这些增删改查操作
真实对象,完成增删改查操作的人,UserServiceImpl类似上一例子的房东

//真实对象,完成增删改查操作的人(类似上一例子的房东)
public class UserServiceImpl implements UserService {

   public void add() {
       System.out.println("增加了一个用户");
  }

   public void delete() {
       System.out.println("删除了一个用户");
  }

   public void update() {
       System.out.println("更新了一个用户");
  }

   public void query() {
       System.out.println("查询了一个用户");
  }
}

3、在创建代理角色之前,先想一想,现在我们需要增加一个日志功能,怎么实现?

  • 思路1 :在实现类上增加代码 【工作量大,效率低下,而且修改公司项目源代码是大忌
  • 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了

4、设置一个代理类进行代理,并实现新增功能
UserServiceProxy相当于上一例子的房屋中介

//代理角色,在这里面增加日志的实现
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+"方法");
  }
}

5、测试访问类:

public class Client {
   public static void main(String[] args) {
       //真实业务
       UserServiceImpl userService = new UserServiceImpl();
       //代理类
       UserServiceProxy proxy = new UserServiceProxy();
       //使用代理类实现日志功能!
       proxy.setUserService(userService);

       proxy.add();
  }
}

与AOP的关系

我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想

聊聊AOP:纵向开发,横向开发
在这里插入图片描述

动态代理

  • 动态代理的角色和静态代理的一样
  • 动态代理的代理类是动态生成的,静态代理的代理类是我们提前写好的
  • 动态代理分为两类:一类是基于接口动态代理,一类是基于类的动态代理
    基于接口的动态代理:JDK动态代理
    基于类的动态代理:cglib
    现在用的比较多的是 javassist 来生成动态代理 . 百度一下javassist
    我们这里使用JDK的原生代码来实现,其余的道理都是一样的

JDK的动态代理需要了解两个类

核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看

【InvocationHandler:调用处理程序】
在这里插入图片描述

Object invoke(Object proxy, Method method, Object[] args);
  • proxy 调用该方法的代理实例
  • method 所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的父类接口。
  • args包含的方法调用传递代理实例的参数值的对象的阵列

【Proxy : 代理】
在这里插入图片描述
动态代理类 (以下简称为代理类 )是一个实现在类创建时在运行时指定的接口列表的类,具有如下所述的行为。

  • 代理接口是由代理类实现的接口。

  • 代理实例是代理类的一个实例。

  • 每个代理实例都有一个关联的调用处理程序对象,它实现了接口InvocationHandler 。

  • 通过其代理接口之一的代理实例上的方法调用将被分派到实例调用处理程序的invoke方法,传递代理实例,java.lang.reflect.Method被调用方法的java.lang.reflect.Method对象以及包含参数的类型Object Object的数组。

  • 调用处理程序适当地处理编码方法调用,并且返回的结果将作为方法在代理实例上调用的结果返回。

在这里插入图片描述

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

例子

抽象业务和真实角色和之前的一样

Rent 即抽象业务

//抽象业务:租房
public interface Rent {
   public void rent();
}

Host 即真实角色

//真实角色: 房东,房东要出租房子
public class Host implements Rent{
   public void rent() {
       System.out.println("房屋出租");
  }
}

ProxyInvocationHandler 即代理角色

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import 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);
    }

    // proxy:代理类  method:代理类的调用处理程序的方法对象
    // 处理代理实例上的方法调用并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        //核心:本质利用反射实现
        Object result = method.invoke(target, args);
        fee();
        return result;
    }

    //看房
    public void seeHouse(){
        System.out.println("带房客看房");
    }
    //收中介费
    public void fee(){
        System.out.println("收中介费");
    }
}

Client

//租客
public class Client {
   public static void main(String[] args) {
       //真实角色
       Host host = new Host();
       //代理实例的调用处理程序
       ProxyInvocationHandler pih = new ProxyInvocationHandler();
       pih.setTarget(host); //将真实角色放置进去
       Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类
       proxy.rent();
  }
}

一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口

加深理解

我们来使用动态代理实现代理我们后面写的UserService!

我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

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);
  }

   // proxy : 代理类
   // method : 代理类的调用处理程序的方法对象.
   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 methodName){
       System.out.println("执行了"+methodName+"方法");
  }

}

测试!

public class Test {
   public static void main(String[] args) {
       //真实对象
       UserServiceImpl userService = new UserServiceImpl();
       //代理对象的调用处理程序
       ProxyInvocationHandler pih = new ProxyInvocationHandler();
       pih.setTarget(userService); //设置要代理的对象
       UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
       proxy.delete();
  }
}

测试,增删改查,查看结果!

动态代理的好处

静态代理有的它都有,静态代理没有的,它也有

  • 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情
  • 公共的业务由代理来完成,实现了业务的分工
  • 公共业务发生扩展时变得更加集中和方便
  • 一个动态代理,一般代理某一类业务
  • 一个动态代理可以代理多个类,代理的是接口

11.面向切面编程(AOP)

什么是AOP

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

AOP在Spring中的作用

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

以下名词需要了解下:

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等
  • 切面(Aspect):横切关注点 被模块化 的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知 执行的 “地点”的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。
    在这里插入图片描述
    SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
    在这里插入图片描述
    即 AOP在 不改变原有代码的情况下,去增加新的功能

使用Spring实现Aop

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

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.4</version>
</dependency>

方式一:通过 Spring API 实现

1、首先编写我们的业务接口和实现类

public interface UserService {

   public void add();

   public void delete();

   public void update();

   public void search();

}
public class UserServiceImpl implements UserService{

   @Override
   public void add() {
       System.out.println("增加用户");
  }

   @Override
   public void delete() {
       System.out.println("删除用户");
  }

   @Override
   public void update() {
       System.out.println("更新用户");
  }

   @Override
   public void search() {
       System.out.println("查询用户");
  }
}

2、然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强

public class Log implements MethodBeforeAdvice {

    //method : 要执行的目标对象的方法
    //args : 被调用的方法的参数
    //target : 目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println( target.getClass().getName() + "的" + method.getName() + "方法被执行了");
    }
}
public class AfterLog implements AfterReturningAdvice {
   //returnValue 返回值
   //method被调用的方法
   //args 被调用的方法的对象的参数
   //target 被调用的目标对象
   @Override
   public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
       System.out.println("执行了" + target.getClass().getName()
       +"的"+method.getName()+"方法,"
       +"返回值:"+returnValue);
  }
}

3、最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .

<?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="userService" class="service.UserServiceImpl"/>
   <bean id="log" class="log.Log"/>
   <bean id="afterLog" class="log.AfterLog"/>

   <!--aop的配置-->
   <aop:config>
       <!--切入点 expression:表达式匹配要执行的方法-->
       <aop:pointcut id="pointcut" expression="execution(* service.UserServiceImpl.*(..))"/>
       <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
       <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
       <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
   </aop:config>
</beans>

4、测试

public class MyTest {
   @Test
   public void test(){
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
       UserService userService = (UserService) context.getBean("userService");
       userService.search();
  }
}

AOP的重要性 : 很重要。一定要理解其中的思路,主要是思想的理解这一块 .

Spring的AOP就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理

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

目标业务类不变依旧是userServiceImpl

1、写我们自己的一个切入类

public class DiyPointcut {

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

2、去spring中配置(记得注释掉前一个例子的配置)

<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="diy.DiyPointcut"/>

<!--aop的配置-->
<aop:config>
   <!--第二种方式:使用AOP的标签实现-->
   <aop:aspect ref="diy">
       <aop:pointcut id="diyPonitcut" expression="execution(* service.UserServiceImpl.*(..))"/>
       <aop:before pointcut-ref="diyPonitcut" method="before"/>
       <aop:after pointcut-ref="diyPonitcut" method="after"/>
   </aop:aspect>
</aop:config>

execution表达式
execution(* service.UserServiceImpl..*.*(..))

  • execution():表达式主体
  • 第一个 *:表达式返回值类型,*号表示所有的类型
    包名:表示需要拦截的包名,后面的两个句点表示当前包及其子包
  • 第二个*:表示类名,*表示所有类
  • *(..):最后这个星号表示 方法名,*表示所有方法,后面括号表示 方法的参数,两个句点表示所有参数

3、测试:

public class MyTest {
   @Test
   public void test(){
       ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
       UserService userService = (UserService) context.getBean("userService");
       userService.add();
  }
}

方式三:使用注解实现

1、编写一个注解实现的增强类

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;

@Aspect
public class AnnotationPointcut {
   @Before("execution(* service.UserServiceImpl.*(..))")
   public void before(){
       System.out.println("---------方法执行前---------");
  }

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

   @Around("execution(* 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);
  }
}

2、在Spring配置文件中,注册bean,并增加支持注解的配置(记得注释掉前面例子的配置)

<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="diy.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>

<aop:aspectj-autoproxy />说明:

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

<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

12.整合Mybatis

准备工作

1、导包,配置Maven资源过滤

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-09-mybatis</artifactId>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.3.RELEASE</version>
        </dependency>


        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

</project>

2、实体类User

public class User {
    private int id;  //id
    private String name;   //姓名
    private String pwd;   //密码
    
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

3、核心配置文件

<?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="pojo"/>
    </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?userSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

4、UserDao接口编写

public interface UserMapper {
    List<User> selectUser();
}

5、接口对应的Mapper映射文件

<?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="dao.UserMapper">

    <select id="selectUser" resultType="User">
        select * from user;
    </select>

</mapper>

6、测试

public class MyTest {
    @Test
    public void selectUser() throws IOException {

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> userList = mapper.selectUser();
        for (User user: userList){
            System.out.println(user);
        }

        sqlSession.close();
    }
}

MyBatis-Spring学习

Spring整合MyBatis方式一

1、引入Spring配置文件Spring-dao.xml(名字随便起)最后导入到applicationContext.xml 测试中就可以直接这个applicationContext.xml

2、配置数据源替换mybaits的数据源
mybatis-config.xml

<?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="pojo"/>
    </typeAliases>

<!--    此时不需要写环境配置,Spring配置文件写了
    同理下面的绑定UserMapper.xml,这里写了,Spring配置文件里不用写-->
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

3、配置SqlSessionFactory,关联MyBatis

4、注册sqlSessionTemplate,关联sqlSessionFactory;

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

    <!--DataSource 使用Spring的数据源替换Mybatis的配置-->
    <!--配置数据源:可以使用第三方的,也可使用Spring的 这里使用Spring提供的JDBC-->
    <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/jdbcstudy?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
        <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配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <!--这个<property name="mapperLocations" value="classpath:UserMapper.xml"/>不需要写,
        因为上面那行绑定的mybatis-config.xml里面已经包含绑定了UserMapper.xml,两种只能选其一种-->
    </bean>

    <!--SqlSessionTemplate就是我们使用的sqlSession -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--只能使用构造器注入sqlSessionFactory,因为它没有set方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    
</beans>

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

    <import resource="spring-dao.xml"/>

    <bean id="userMapper" class="dao.UserMapperImpl">
        <property name="sqlSession" ref="sqlSession"/>
    </bean>
    
</beans>

5、增加Dao接口的实现类;私有化sqlSessionTemplate

public class UserMapperImpl implements UserMapper{
    // 在原来,我们所有操作都使用sqlSession来执行,现在都使用sqlSessionTemplate

    private SqlSessionTemplate sqlSession;// 之前用习惯sqlSession,故取名为sqlSession

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

    public List<User> selectUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }

}

6、测试

public class MyTest {
    @Test
    public void test2(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper mapper = (UserMapper) context.getBean("userMapper");
        List<User> user = mapper.selectUser();
        System.out.println(user);
    }
}

Spring整合MyBatis方式二

mybatis-spring1.2.3版以上的才有这个 .

官方文档截图 :
在这里插入图片描述
dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看

1、将我们上面写的UserMapperImpl修改一下

public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
    public List<User> selectUser() {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}

2、修改bean的注册

    <bean id="userMapper2" class="dao.UserMapperImpl2">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>

3、测试

    @Test
    public void test3(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper mapper = (UserMapper) context.getBean("userMapper2");
        List<User> user = mapper.selectUser();
        System.out.println(user);
    }

13.声明式事务

要么都成功,要么都失败

事务的ACID原则:

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

Spring中的事务管理

  • 声明式事务(AOP的一个应用,交由容器管理)
  • 编程式事务

在这里插入图片描述

代码演示

spring-dao.xml
需要导入tx约束

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

    <!--DataSource 使用Spring的数据源替换Mybatis的配置-->
    <!--配置数据源:可以使用第三方的,也可使用Spring的 这里使用Spring提供的JDBC-->
    <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/jdbcstudy?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true"/>
        <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配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <!--这个<property name="mapperLocations" value="classpath:UserMapper.xml"/>不需要写,
        因为上面那行绑定的mybatis-config.xml里面已经包含绑定了UserMapper.xml,两种只能选其一种-->
    </bean>

    <!--SqlSessionTemplate就是我们使用的sqlSession -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--只能使用构造器注入sqlSessionFactory,因为它没有set方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>


<!--================================================================ -->

    <!--配置声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="dataSource" />
    </bean>

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

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

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

    <import resource="spring-dao.xml"/>

    <bean id="userMapper" class="dao.UserMapperImpl">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
    </bean>

</beans>

UserMapper接口

public interface UserMapper {
    List<User> selectUser();
    // 添加一个用户
    int addUser(User user);
    // 删除一个用户
    int deleteUser(int id);
}

UserMapper.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="dao.UserMapper">

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

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

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

</mapper>

UserMapper实现类UserMapperImpl

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
    public List<User> selectUser() {
        User user = new User(10, "王五123", "213123");
        SqlSession sqlSession = getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        mapper.addUser(user);
        mapper.deleteUser(6);
        return mapper.selectUser();
    }

    public int addUser(User user) {
        return getSqlSession().getMapper(UserMapper.class).addUser(user);
    }

    @Override
    public int deleteUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
    }
    
}

测试

    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper mapper = context.getBean("userMapper",UserMapper.class);
        List<User> userList = mapper.selectUser();
        for(User user:userList){
            System.out.println(user);
        }

    }

为什么需要配置事务?

  • 如果不配置,就需要我们手动提交控制事务
  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值