spring

一、了解spring

​ Spring 使创建 Java 企业应用程序变得容易。它提供了在企业环境中使用 Java 语言所需的一切,并支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并且可以根据应用程序的需求灵活地创建多种体系结构。从 Spring Framework 5.0 开始,Spring 需要 JDK 8(Java SE 8),并且已经为 JDK 9 提供了现成的支持。

​ Spring 框架分为多个模块。应用程序可以选择所需的模块。核心容器的模块是核心,包括配置模型和依赖项注入机制。除此之外,Spring 框架为不同的应用程序体系结构提供了基础支持,包括消息传递,事务性数据和持久性以及 Web。它还包括基于 Servlet 的 Spring MVC Web 框架,以及并行的 Spring WebFlux 反应式 Web 框架。

二、spring核心(CORE)

2.1IOC容器 依赖注入

2.1.1 Spring IoC 容器和 Bean 简介

org.springframework.beansorg.springframework.context软件包是 Spring Framework 的 IoC 容器的基础。 BeanFactory接口提供了一种高级配置机制,能够 Management 任何类型的对象。 ApplicationContextBeanFactory的子接口。它增加了:

  • 与 Spring 的 AOP 功能轻松集成
  • 消息资源处理(用于国际化)
  • Event publication
  • 特定于应用程序层的上下文,例如用于 Web 应用程序的WebApplicationContext

简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能。 ApplicationContextBeanFactory的完整超集,在本章中仅使用 Spring 的 IoC 容器描述。

2.1.2容器简介

1.准备spring核心依赖

<!--spring core start-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${org.springframework.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${org.springframework.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${org.springframework.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>${org.springframework.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${org.springframework.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-indexer</artifactId>
      <version>${org.springframework.version}</version>
    </dependency>
    <!--spring core end-->

<!--spring aop start-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <!--spirng aop end-->

        <!--spring aspects start-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <!--spring aspects end-->

        <!--spring instrumentation start -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-instrument</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <!--spring instrumentation end-->

        <!--spring messaging start -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <!--spring messaging end-->

        <!--spring data access start -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <!--spring data access end-->

        <!--spring web start -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc-portlet</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webflux</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <!--spring web end -->

        <!--spring test start -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <!--spring test end -->

2.创建maven工程 建立好相应的包、文件夹、xml文件

在这里插入图片描述

3.新建一个ApplicationSpring 类

springIOC 容器的设计主要是基于BeanFactory和Application两个接口,其中ApplicationContext是BeanFactory的子接口之一。Application是其高级接口之一,并且对BeanFactory功能做了许多有用的扩展,所以在绝大部分情况下,都会使用Application作为SpringIOC容器。

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationSpring {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new  	     ClassPathXmlApplicationContext("ApplicationContext.xml");
    }
}

4.表 1. bean 定义

PropertyExplained in…
ClassInstantiating Beans
NameNaming Beans
ScopeBean Scopes
Constructor argumentsDependency Injection
PropertiesDependency Injection
Autowiring modeAutowiring Collaborators
延迟初始化模式Lazy-initialized Beans
Initialization methodInitialization Callbacks
Destruction methodDestruction Callbacks

表 2.1. Spring Framework 工件

GroupIdArtifactIdDescription
org.springframeworkspring-aop基于代理的 AOP 支持
org.springframeworkspring-aspects基于 AspectJ 的方面
org.springframeworkspring-beansBean 支持,包括 Groovy
org.springframeworkspring-context应用程序上下文运行时,包括调度和远程处理抽象
org.springframeworkspring-context-support用于将通用第三方库集成到 Spring 应用程序上下文中的支持类
org.springframeworkspring-core其他许多 Spring 模块使用的核心 Util
org.springframeworkspring-expressionSpring 表达语言(SpEL)
org.springframeworkspring-instrument用于 JVM 引导的工具代理
org.springframeworkspring-instrument-tomcatTomcat 的检测代理
org.springframeworkspring-jdbcJDBC 支持包,包括数据源设置和 JDBC 访问支持
org.springframeworkspring-jmsJMS 支持包,包括用于发送/接收 JMS 消息的帮助程序类
org.springframeworkspring-messaging支持消息传递体系结构和协议
org.springframeworkspring-orm对象/关系 Map,包括 JPA 和 Hibernate 支持
org.springframeworkspring-oxmObject/XML Mapping
org.springframeworkspring-test支持单元测试和集成测试 Spring 组件
org.springframeworkspring-tx事务基础结构,包括 DAO 支持和 JCA 集成
org.springframeworkspring-web基本的 Web 支持,包括 WebClient 端和基于 Web 的远程处理
org.springframeworkspring-webmvcServlet 堆栈的基于 HTTP 的 Model-View-Controller 和 REST 端点
org.springframeworkspring-webmvc-portlet在 Portlet 环境中使用的 MVC 实现
org.springframeworkspring-websocketWebSocket 和 SockJS 基础结构,包括 STOMP 消息传递支持

5.新建一个实体类Person

package com.spring.domain;

public class Person {
    private String name;
    private int age;

    public Person(){

    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

6.几种不同的实例化方式

用构造函数实例化
<bean id="myPerson" class="com.spring.domain.Person"></bean>
使用静态工厂方法实例化
    public static Person getInstance(){
        return new Person();
    }
<bean id="myPersons" class="com.spring.domain.Person" factory-method="getInstance"></bean>
使用实例工厂方法实例化
public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

2.1.3. 依赖注入

依赖注入(DI)是一个过程,通过该过程,对象只能通过构造函数参数,工厂方法的参数或在构造或创建对象实例后在对象实例上设置的属性来定义其依赖关系(即,与它们一起工作的其他对象)。从工厂方法返回。然后,容器在创建 bean 时注入那些依赖项。从根本上讲,此过程是通过使用类的直接构造或服务定位器模式来自己控制其依赖关系的实例化或位置的 Bean 本身的逆过程(因此称为 Control Inversion)。

使用 DI 原理,代码更简洁,当为对象提供依赖项时,去耦会更有效。该对象不查找其依赖项,也不知道依赖项的位置或类。结果,您的类变得更易于测试,尤其是当依赖项依赖于接口或抽象 Base Class 时,它们允许在单元测试中使用存根或模拟实现。

DI 存在两个主要变体:基于构造函数的依赖注入基于 Setter 的依赖注入

基于构造函数的依赖关系注入
package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}
<beans>
    <bean id="thingOne" class="x.y.ThingOne">
        <constructor-arg ref="thingTwo"/>
        <constructor-arg ref="thingThree"/>
    </bean>

    <bean id="thingTwo" class="x.y.ThingTwo"/>

    <bean id="thingThree" class="x.y.ThingThree"/>
</beans>

构造函数参数类型匹配

    <bean id="myPerson" class="com.spring.domain.Person">
        <constructor-arg name="name" value="高洋"></constructor-arg>
        <constructor-arg name="age" value="11"/>
    </bean>

构造函数参数索引
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>
构造函数参数类型匹配
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

测试代码验证

import com.spring.domain.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ApplicationSpring {

    private static Person person;
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        person = (Person) applicationContext.getBean("myPerson");
        //person = applicationContext.getBean("myPerson",Person.class);
        System.out.println(person);
    }
}
输出结果:
十一月 11, 2020 8:25:56 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4bf558aa: startup date [Wed Nov 11 20:25:56 CST 2020]; root of context hierarchy
十一月 11, 2020 8:25:56 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [ApplicationContext.xml]
Person{name='高洋', age=11}
基于 Setter 的依赖项注入
<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

注意:环形注入问题

A类通过构造函数注入需要B类的实例,B类需要A类的实例,因为循环引用会抛出BeanCurrentlyInCreationExceptionS

以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。 Spring XML 配置文件的一小部分指定了一些 bean 定义,如下所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

以下示例显示了相应的ExampleBean类:

public class ExampleBean {

    private AnotherBean beanOne;

    private YetAnotherBean beanTwo;

    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

引入数据库连接依赖:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.21</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.23</version>
</dependency>

配置数据源

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="clone">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/zydemo"/>
    <property name="username" value="root"/>
    <property name="password" value="pzh200061"/>
</bean>
Collections

<list/><set/><map/><props/>元素分别设置 Java Collection类型ListSetMapProperties的属性和参数。以下示例显示了如何使用它们:

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">[emailprotected]</prop>
            <prop key="support">[emailprotected]</prop>
            <prop key="development">[emailprotected]</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>
懒初始化bean

默认情况下,作为初始化过程的一部分,ApplicationContext实现会急于创建和配置所有singleton bean。通常,这种预初始化是可取的,因为与数小时甚至数天后相比,会立即发现配置或周围环境中的错误。如果不希望使用此行为,则可以通过将 bean 定义标记为延迟初始化来防止单例 bean 的预实例化。延迟初始化的 bean 告诉 IoC 容器在首次请求时而不是在启动时创建一个 bean 实例。

在 XML 中,此行为由<bean/>元素上的lazy-init属性控制,如以下示例所示:

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

bean范围

创建 bean 定义时,将创建一个配方来创建该 bean 定义所定义的类的实际实例。 bean 定义是配方的想法很重要,因为它意味着与类一样,您可以从一个配方中创建许多对象实例。

您不仅可以控制要插入到从特定 bean 定义创建的对象中的各种依赖项和配置值,还可以控制从特定 bean 定义创建的对象的范围。这种方法功能强大且灵活,因为您可以选择通过配置创建的对象的范围,而不必在 Java 类级别上烘烤对象的范围。可以将 Bean 定义为部署在多个范围之一中。 Spring 框架支持六个范围,其中只有在使用网络感知ApplicationContext时才可用。您也可以创建自定义范围。

下表描述了受支持的范围:

表 3. Bean 作用域

ScopeDescription
singleton(默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。
prototype将单个 bean 定义的作用域限定为任意数量的对象实例。
request将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。
session将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。

新建数据库Properties文件

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/zydemo
jdbc.username=root
jdbc.password=pzh200061
配置context名称空间
<context:property-placeholder location="classpath:datasource.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="clone">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
</bean>

2.1.4Spring IOC容器的初始化和依赖注入

1.Bean的初始化和依赖注入在Spring IOC容器中是两大步骤,在初始化之后才会进行依赖注入。

2.2基于注解的容器配置

基于xml的spring配置中包含以下标记来隐式注册他们 目前列出了所有的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-3.0.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">
    
    <!--    扫描包-->
    <context:component-scan base-package="com.spring"/>

    <context:property-placeholder location="classpath:datasource.properties"/>

    <context:annotation-config/>

    <bean id="myPerson" class="com.spring.domain.Person">
        <constructor-arg name="name" value="张三"/>
        <constructor-arg name="age" value="11"/>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="clone">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

使用@Component装配Bean

package com.spring.domain;

@Component(value = "role")
public class Role {
    @Value("1")
    private Long id;

    @Value("role_name_1")
    private String roleName;

    @Value("role_note_1")
    private String note;

    public Role(){

    }

    public Role(Long id, String roleName, String note) {
        this.id = id;
        this.roleName = roleName;
        this.note = note;
    }

    public Long getId() {
        return id;
    }

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

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", roleName='" + roleName + '\'' +
                ", note='" + note + '\'' +
                '}';
    }
}

注解Component代表Spring IOC会把这个类扫描生成Bean示例,而其中的value属性代表这个类在Spring中的id,相当于XML配置中Bean的id,如果只写写成@Component,Spring IOC容器就默认为类名,但是以首字母小写的形式作为id,为其生成对象,配置到容器中。

使用@Autowried自动装配Bean

@Component("RoleService")
public class RoleServiceImpl implements RoleService {

    @Autowired
    private Role role;

    @Override
    public void printRoleInfo(Role role) {
        System.out.println(role.getNote());
        System.out.println(role.getId());
        System.out.println(role.getRoleName());
    }
}

测试:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
RoleService roleService = applicationContext.getBean(RoleService.class);
roleService.printRoleInfo();

注解@Qualifier

针对于上面的代码,如果当存在多个Bean对象需要装配时,为了消除歧义,可以使用@Qualifier来消除。

@Autowired
@Qualifier("RoleService")
private RoleService roleService;

2.2AOP

注意!了解AOP之前首先要了解java动态代理和CGLIB动态代理

为了能够更好的理解AOP请看如下代码

public interface RoleService {
    public void printRoleInfo(Role role);
}
@Component("RoleService")//标注一个类为Spring容器的Bean
public class RoleServiceImpl implements RoleService {

    private Role role;

    @Override
    public void printRoleInfo(Role role) {
        System.out.println(role.getNote());
        System.out.println(role.getId());
        System.out.println(role.getRoleName());
    }
}
public interface Interceptor {
    public void before(Object obj);
    public void after(Object obj);
    public void afterReturning(Object obj);
    public void afterThrowing(Object obj);
}
public class RoleInterceptor implements Interceptor {
    @Override
    public void before(Object obj) {
        System.out.println("准备打印角色信息");
    }

    @Override
    public void after(Object obj) {
        System.out.println("已经完成角色信息的打印处理");
    }

    @Override
    public void afterReturning(Object obj) {
        System.out.println("刚刚完成打印功能,一切正常");
    }

    @Override
    public void afterThrowing(Object obj) {
        System.out.println("打印功能执行正常了,查看一下角色对象为空了吗?");
    }
}
public class ProxyBeanFactory {
    public static <T> T getBean(T obj, Interceptor interceptor){
        return (T) ProxyBeanUtil.getBean(obj,interceptor);
    }
}
public class ProxyBeanUtil implements InvocationHandler {

    //被代理对象 roleService
    private Object obj;

    //拦截器
    private Interceptor interceptor = null;

    public static Object getBean(Object obj,Interceptor interceptor){
        ProxyBeanUtil _this = new ProxyBeanUtil();
        //保存被代理对象
        _this.obj = obj;
        //保存拦截器
        _this.interceptor = interceptor;
        //生成代理对象,并绑定代理方法
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),_this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object retObj = null;
        //是否产生异常
        boolean exceptionFlag = false;
        //before方法
        interceptor.before(obj);
        try {
            //反射原有方法
            retObj = method.invoke(obj,args);
        }catch (Exception e){
            exceptionFlag = true;
        }finally {
            //after方法
            interceptor.after(obj);
        }
        if(exceptionFlag){
            //afterThrowing方法
            interceptor.afterThrowing(obj);
        }else{
            //afterReturning方法
            interceptor.afterReturning(obj);
        }

        return retObj;
    }
}

测试:

public static void main(String[] args) {
    RoleService roleService = new RoleServiceImpl();
    Interceptor interceptor = new RoleInterceptor();
    RoleService proxy = ProxyBeanFactory.getBean(roleService,interceptor);
    Role role = new Role(1L,"role_name","role_note");
    //相当于对printRoleInfo方法的一种增强
    proxy.printRoleInfo(role);
    System.out.println("-------测试afterthrow方法-------");
    role = null;
    proxy.printRoleInfo(role);
}

输出结果:
准备打印角色信息
role_note
1
role_name
已经完成角色信息的打印处理
刚刚完成打印功能,一切正常
-------测试afterthrow方法-------
准备打印角色信息
已经完成角色信息的打印处理
打印功能执行正常了,查看一下角色对象为空了吗?

从测试代码开始分析:

1.首先实例化了两个对象,分别为roleService、interceptor

2.获取到代理对象 proxy 对getBean方法进行分析:传入的参数是上面的两个对象,然后调用ProxyBeanUtil.getBean方法。

3.因为采用的是JDK动态代理,所以实现了InvocationHandler。

2.2.1AOP的术语

1.切面

切面就是在一个怎么样的环境下工作,可以理解为贯穿整个代码层面,就是一个切面,它能够在被代理对象的方法之前、之后,产生异常或者正常返回后切入你的代码,甚至可以代替原来被代理的方法,在动态代理中可以把它理解成一个拦截器,比如上面的RoleInterceptor就是一个切面类。

2.通知

通知是切面开启后,切面的方法。它根据在代理对象真实方法调用前、后的顺序和逻辑区分,它和约定游戏的例子里的拦截器的方法十分接近。

前置通知(before):在动态代理反射原有对象方法或者执行环绕通知前执行的通知功能。

后置通知(after):在动态代理反射原有对象方法或者执行环绕通知后执行的通知功能。无论是否抛出异常,它都会被执行。

异常通知(afterThrowing):在动态代理中反射原有对象方法或者执行环绕通知产生异常后执行的通知功能。

环绕通知(aroundThrowing):在动态代理中,它可以取代当前被拦截对象的方法,通过参数或反射调用被拦截对象的方法。上面代码中的printRole方法就是一个切点。

3.引入

引入允许我们在现有的类里添加自定义的类和方法。

4.切点

在动态代理中,被切面拦截的方法就是一个切点,切面将可以将其切点和被拦截的方法按照一定的逻辑织入到约定流程当中。

4.连接点

连接点是一个判定条件,由它可以指定哪些是切点。对于指定的切点,Spring会生成代理对象去使用对应的切面对其拦截,否则就不会拦截它。

5.织入

织入是一个生成带颗粒对象的过程。

2.2.2Spring AOP的实现

这里只讲解基于注解的实现

通过对一下代码进行分析来了解Spring AOP:

2.2.2.1创建切面

通过使用@Aspect注解一个类,那么Spring IOC容器就会认为这是一个切面了。

@Aspect
public class RoleAspect {

    @DeclareParents(value = "com.spring.service.impl.RoleServiceImpl+",defaultImpl = RoleVerifierImpl.class)
    public RoleVerifier roleVerifier;

    @Pointcut("execution(* com.spring.service.impl.RoleServiceImpl.printRoleInfo(..))")
    public void print(){

    }

    //@Before("execution(* com.spring.service.impl.RoleServiceImpl.printRoleInfo(..))")
    @Before("print()")
    public void before(){
        System.out.println("before");
    }

    //@After("execution(* com.spring.service.impl.RoleServiceImpl.printRoleInfo(..))")
    @After("print()")
    public void after(){
        System.out.println("after");
    }

    @AfterReturning("print()")
    public void afterReturning(){
        System.out.println("afterReturning");
    }

    //@AfterThrowing("execution(* com.spring.service.impl.RoleServiceImpl.printRoleInfo(..))")
    @AfterThrowing("print()")
    public void afterThrowing(){
        System.out.println("afterThrowing");
    }

//    @Around("print()")
//    public void around(){
//        System.out.println("around before");
//        try {
//            System.out.println("aa");
//        }catch (Exception e){
//            e.printStackTrace();
//        }
//        System.out.println("around after");
//    }


}

在这里插入图片描述

这里的注解与上面的代码内容一致。

2.2.2.2连接点

execution(* com.spring.service.impl.RoleServiceImpl.printRoleInfo(…))

针对这个表达式做出分析:

execution:代表执行方法的时候会触发

*:代表任意返回类型的方法

com.spring.service.impl.RoleServiceImpl:代表类的全限定名

printRoleInfo:被拦截方法名称

(…):任意的参数

在这里插入图片描述

注意:Spring只能支持上表所列出的AspectJ的指示器。如果使用了非表格中所列举的指示器,那么它将会抛出IllegalArgumentException异常

使用@Pointcut

使用这个注解后就不用再重复写正则表达式。

2.2.2.3测试
@Configuration
public class RoleConfig {

    @Bean
    public RoleAspect getRoleAspect(){
        return new RoleAspect();
    }
}

实现要编写一个配置类:@Configration:项目启动时会加载@Configration注解的类

将RoleAspect装配到IOC容器中

//AOP测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
RoleService roleService = applicationContext.getBean(RoleService.class);
Role role = new Role();
role.setId(1L);
role.setRoleName("role_name");
role.setNote("role_note");
roleService.printRoleInfo(role);
System.out.println("##########异常测试#########");
role = null;
roleService.printRoleInfo(role);
2.2.2.4引入
public interface RoleVerifier {
    public boolean verify(Role role);
}
public class RoleVerifierImpl implements RoleVerifier {
    @Override
    public boolean verify(Role role) {
        return role!=null;
    }
}

在RoleAspect类中添加下面这个属性,并通过@DeclareParents

 @DeclareParents(value = "com.spring.service.impl.RoleServiceImpl+",defaultImpl = RoleVerifierImpl.class)
    public RoleVerifier roleVerifier;

@DeclareParents分析:

value:com.spring.service.impl.RoleServiceImpl+:

表示 RoleServiceImpl中的方法进行增强

defaultImpl:表示实现增强方法接口的实现类

测试:

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
 RoleService roleService = applicationContext.getBean(RoleService.class);
 RoleVerifier roleVerifier = (RoleVerifier) applicationContext.getBean(RoleService.class);
 Role role = new Role();
 role.setId(1L);
 role.setRoleName("role_name");
 role.setNote("role_note");
 if(roleVerifier.verify(role)){
     roleService.printRoleInfo(role);
}

三、spring和数据库编程

3.1使用spring配置数据库

<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
    <property name="username" value="root"/>
    <property name="password" value="pzh200061"/>
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
</bean>

这种配置一般用于测试,因为它不是一个数据库连接池,在更多的时候,我们使用的时第三方的数据库连接。

3.2使用第三方数据库连接池

首先要导入依赖pom.xml

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-dbcp2</artifactId>
  <version>2.7.0</version>
</dependency>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="username" value="root"/>
    <property name="password" value="pzh200061"/>
    <property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
    <!--        最大连接数-->
    <property name="maxTotal" value="5"/>
    <!--        最大等待连接中的数量-->
    <property name="maxIdle" value="5"/>
    <!--        最大等待毫秒数-->
    <property name="maxWaitMillis" value="10000"/>
</bean>

这里配置的时dpcp的数据库连接池。

3.3jdbcTemplate

配置jdbcTemplate

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

测试:

首先在数据库下面新建一个account表

在这里插入图片描述

新建实体类

package com.spring.domain;

public class Account {
    private int id;
    private String name;
    private double money;

    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 double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}
public class JdbcTemplateDemo {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate",JdbcTemplate.class);
        int id = 1;
        String sql = "select id,name,money from account where id ="+id;
        Account account = jdbcTemplate.queryForObject(sql, new RowMapper<Account>() {
            @Override
            public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
                Account result = new Account();
                result.setId(rs.getInt("id"));
                result.setName(rs.getString("name"));
                result.setMoney(rs.getDouble("money"));
                return result;
            }
        });
        System.out.println(account);

    }
}
            
输出结果:
            Account{id=1, name='小明', money=999.0}

分析代码:

这里使用了jdbcTemplate的queryForObject方法、它包含两个参数,一个是SQL,一个是RowMapper接口,这里使用了匿名内部类。同时,还可以通过使用jdk1.8中的lambda表达式。

在mapRow方法中,从ResultSet对象中取出查询到的数据,组装成一个Role对象,而无需再写任何关闭数据库的代码,因为jdbcTemplate内部实现了它们,这便是Spring的模板规则。

3.4jdbcTemplate的增、删、改、差

package com.spring.dao.daoImpl;

import com.spring.domain.Account;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

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

public class JdbcTemplateDemo {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
    JdbcTemplate jdbcTemplate = applicationContext.getBean("jdbcTemplate", JdbcTemplate.class);

    /**
     * 插入账户
     * 插入成功返回1
     *
     * @return
     */
    public int insertAccount() {
        String name = "小李";
        double money = 10;
        String sql = "insert into account(name,money) values(?,?)";
        return jdbcTemplate.update(sql, name, money);
    }

    /**
     * 根据id删除账户
     * 删除成功返回1
     *
     * @param id
     * @return
     */
    public int deleteAccount(int id) {
        String sql = "delete from account where id = ?";
        return jdbcTemplate.update(sql, id);
    }

    /**
     * 根据姓名查找账户
     *
     * @param name
     * @return
     */
    public List<Account> findAccount(String name) {
        String sql = "select id,name,money from account where name like concat('%',?,'%')";
        Object[] params = {name};
        //使用RowMapper接口组织返回
        List<Account> accounts = jdbcTemplate.query(sql, params, new RowMapper<Account>() {
            @Override
            public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
                Account account = new Account();
                account.setId(rs.getInt("id"));
                account.setName(rs.getString("name"));
                account.setMoney(rs.getDouble("money"));
                return account;
            }
        });

        return accounts;
    }


    public static void main(String[] args) {
        JdbcTemplateDemo jdbcTemplateDemo = new JdbcTemplateDemo();
        //System.out.println(jdbcTemplateDemo.insertAccount());
        //System.out.println(jdbcTemplateDemo.deleteAccount(8));
        List<Account> accounts = jdbcTemplateDemo.findAccount("小");
        for (Account data : accounts) {
            System.out.println(data);
        }
    }
}

3.4jdbcTemplate源码分析

@Override
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
   Assert.notNull(action, "Callback object must not be null");
   //从数据源中获取一条连接
   Connection con = DataSourceUtils.getConnection(getDataSource());
   try {
      Connection conToUse = con;
      if (this.nativeJdbcExtractor != null) {
         // Extract native JDBC Connection, castable to OracleConnection or the like.
         conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
      }
      else {
         // Create close-suppressing Connection proxy, also preparing returned Statements.
         conToUse = createConnectionProxy(con);
      }
      return action.doInConnection(conToUse);
   }
   catch (SQLException ex) {
      // Release Connection early, to avoid potential connection pool deadlock
      // in the case when the exception translator hasn't been initialized yet.
      DataSourceUtils.releaseConnection(con, getDataSource());
      con = null;
      throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
   }
   finally {
       //释放连接
      DataSourceUtils.releaseConnection(con, getDataSource());
   }
}

## 3.4jdbcTemplate源码分析

```java
@Override
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
   Assert.notNull(action, "Callback object must not be null");
   //从数据源中获取一条连接
   Connection con = DataSourceUtils.getConnection(getDataSource());
   try {
      Connection conToUse = con;
      if (this.nativeJdbcExtractor != null) {
         // Extract native JDBC Connection, castable to OracleConnection or the like.
         conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
      }
      else {
         // Create close-suppressing Connection proxy, also preparing returned Statements.
         conToUse = createConnectionProxy(con);
      }
      return action.doInConnection(conToUse);
   }
   catch (SQLException ex) {
      // Release Connection early, to avoid potential connection pool deadlock
      // in the case when the exception translator hasn't been initialized yet.
      DataSourceUtils.releaseConnection(con, getDataSource());
      con = null;
      throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
   }
   finally {
       //释放连接
      DataSourceUtils.releaseConnection(con, getDataSource());
   }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值