【JAVA】Spring 框架

13,719 阅读19分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

本博文专用于软件创新实验室 Spring 框架课堂,由于课堂时间有限,选取了 Spring 框架中比较重点的几个来介绍。 Spring 框架的诞生是为了使开发更加高效简洁,同时减少耦合程度,主要还是思想上的一个转变,想要深入了解 Spring 框架的,可以查阅 Spring 官方文档,也推荐一下狂神老师的视频教学,值得一看。  

初识Spring

简介

2002年,Rod Jahnson首次推出了 Spring 框架雏形 interface21 框架,

2004年3月24日,Spring 框架以 interface21 框架为基础,经过重新设计,发布了1.0正式版,

Spring理念 : 使现有技术更加实用 , 本身就是一个大杂烩 , 整合现有的框架技术,

官网:spring.io/

官方下载地址:repo.spring.io/libs-releas…

GitHub:github.com/spring-proj…

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.3</version>
</dependency>

 

组成

Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 。

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

组成

核心容器(Spring Core)

  核心容器提供 Spring 框架的基本功能。Spring 以 bean 的方式组织和管理 Java 应用中的各个组件及其关系。Spring 使用 BeanFactory 来产生和管理 Bean,它是工厂模式的实现。BeanFactory 使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。


应用上下文(Spring Context)

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


Spring面向切面编程(Spring AOP)

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


JDBC和DAO模块(Spring DAO)

  JDBC、DAO 的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。


对象实体映射(Spring ORM)

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


Web模块(Spring Web)

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


MVC模块(Spring Web MVC)

  MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的。MVC 容纳了大量视图技术,其中包括 JSP、POI 等,模型来有 JavaBean 来构成,存放于 m 当中,而视图是一个街口,负责实现模型,控制器表示逻辑代码,由 c 的事情。Spring 框架的功能可以用在任何 J2EE 服务器当中,大多数功能也适用于不受管理的环境。Spring 的核心要点就是支持不绑定到特定 J2EE 服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的 J2EE 环境,独立应用程序和测试环境之间重用。  

创建

新建一个 Maven 项目,在 pom.xml 中进行配置,

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.3</version>
</dependency>

目录 先编写一个 User 实体类,User.java

package com.idiot.pojo;

public class User{
    private String name;

    public String getName() {
        return name;
    }

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

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

再编写我们的Spring文件,这里命名为beans.xml

配置文件可在官方文档中获取,

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--使用Spring来创建对象,在Spring中这些都称为Bean-->
    <!--bean就是java对象 , 由Spring创建和管理-->
    <bean id="helloSpring" class="com.idiot.pojo.User">
        <property name="name" value="Spring"/>
    </bean>
</beans>

最后进行测试,MyTest.java

import com.idiot.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        //获取Spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        
        //我们的对象现在都在Spring中管理,我们要使用的话,直接从里面取出来
        User helloSpring = (User) context.getBean("helloSpring");
        System.out.println(helloSpring.toString());
    }
}

运行结果 在这里插入图片描述 

配置说明

别名

如果添加了别名,我们也可以使用别名获取这个对象,原名也是可以使用的,在 beans.xml 中进行配置,

<alias name="helloSpring" alias="helloAlias"/>
<bean id="helloSpring" class="com.idiot.pojo.User">
    <property name="name" value="Spring"/>
</bean>

在这里插入图片描述 

Bean的配置

bean 就是 java 对象,由 Spring 创建和管理,

  • idbean 的唯一标识符,也就相当于对象名,
  • classbean 对象所对应的全限定名:包名+类型,
  • name:也是别名,而且 name 可以同时取多个别名,可以用逗号,分号,空格隔开,

如果没有配置 idname 就是默认标识符,

如果配置了 id,又配置了 name,那么 name 是别名,

如果不配置 idname,可以根据 applicationContext.getBean(.class) 获取对象,  

import

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

假设现在项目中有多个人开发,其中三个人负责不同的类的开发,不同的类需要注册在不同的bean配置文件中,我们可以利用import将所有人的beans.xml合并成一个总的,即applicationContext.xml

<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>

使用的时候直接使用总配置applicationContext.xml即可。  

思考

  • User对象是谁创建的?
    • User对象是由Spring创建的,

      <bean id="helloSpring" class="com.idiot.pojo.User">
          <property name="name" value="Spring"/>
      </bean>
      
      类型 变量名 = new 类型();
      User user = new User();
      
      id = 变量名
      class = new的对象
      property相当于给对象中的属性设值
      
  • User 对象的属性是怎么设置的?
    • User 对象的属性是由Spring容器设置的,

这个过程就叫控制反转:

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

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

IOC是一种编程思想,由主动的编程变成被动的接收,可以通过newClassPathXmlApplicationContext去浏览一下底层源码,  

IOC

本质

控制反转IoC(Inversion of Control),是一种设计思想,是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式,在 Spring 中实现控制反转的是 IoC 容器,其实现方法是依赖注入(Dependency Injection,DI), 也有人认为 DI 只是 IoC 的另一种说法。没有 IoC 的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

在这里插入图片描述

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

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

理论推导

首先创建一个Maven项目,然后创建接口和实现类,目录总览如下, 在这里插入图片描述

UserDao.java 接口,

package com.idiot.dao;

public interface UserDao {
    public void getUser();
}

Dao 的实现类,UserDaoImpl.java

package com.idiot.dao;

public class UserDaoImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("获取用户数据");
    }
}

UserService 的接口,UserService.java

package com.idiot.service;

public interface UserService {
    public void getUser();
}

Service 的实现类,UserServiceImpl.java

package com.idiot.service;

import com.idiot.dao.UserDao;
import com.idiot.dao.UserDaoImpl;

public class UserServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl();

    @Override
    public void getUser() {
        userDao.getUser();
    }
}

测试一下,MyTest.java

import com.idiot.service.UserService;
import com.idiot.service.UserServiceImpl;
import org.junit.Test;

public class MyTest {
    @Test
    public void test(){
        //用户实际调用的是业务层,Dao层他们不需要接触!
        UserService service = new UserServiceImpl();
        service.getUser();
    }
}

这里要使用 @Test,需要导入 junit.jar

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

运行结果如下, 在这里插入图片描述

现在增加一个 Dao 的实现类,UserDaoOracleImpl.java

package com.idiot.dao;

public class UserDaoOracleImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("Oracle获取用户数据");
    }
}

如果用户想去调用 Oracle 这个实现类,则我们必须去源代码去修改代码,即在 service 实现类里面修改对应的实现,

public class UserServiceImpl implements UserService {
   private UserDao userDao = new UserDaoOracleImpl();

   @Override
   public void getUser() {
       userDao.getUser();
  }
}

再加一个 Mysql 的实现类也是一样的,UserDaoSqlserverImpl.java

package com.idiot.dao;

public class UserDaoSqlserverImpl implements UserDao {
    @Override
    public void getUser() {
        System.out.println("MySql获取用户数据");
    }
}

现在我们这个程序的代码少,修改起来是不麻烦的,但假设修改需求非常大 , 修改源码这种方式就根本不适用了, 每次变动都需要修改大量代码,这样子的成本代价是十分昂贵的。

如何解决这样的问题?

我们将使用一个 set 接口实现,使之发生革命性的变化!

UserServiceImpl.java

package com.idiot.service;

import com.idiot.dao.UserDao;

public class UserServiceImpl implements UserService {
    private UserDao userDao;
    // 利用set实现
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public void getUser() {
        userDao.getUser();
    }
}

MyTest.java

@Test
public void test(){
    UserServiceImpl service = new UserServiceImpl();
    //Mysql实现
    service.setUserDao( new UserDaoSqlserverImpl() );
    service.getUser();
    
    //Oracle实现
    service.setUserDao( new UserDaoOracleImpl() );
    service.getUser();
}

在这里插入图片描述

与使用 set 前的代码进行对比,这已经发生了根本性的变化,以前所有东西都是由程序主动去进行控制创建 , 而使用了 set 注入之后,程序不再具有主动性,而是变成了被动接受的对象,即把主动权交给了调用者,程序则只负责提供一个接口,

在这里插入图片描述 这种思想,从本质上解决了问题,我们程序员不再去管理对象的创建,而是更多的去关注业务的实现,耦合性大大降低,这也就是IOC的原型!  

创建对象方式

1. 使用无参构造创建对象(默认)

目录

User.java

package com.idiot.pojo;

public class User {
    private String name;

    public User() {
        System.out.println("User无参构造");
    }

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

    public void show(){
        System.out.println("Name:"+ name );
    }
}

beans.xml

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

    <bean id="user" class="com.idiot.pojo.User">
        <property name="name" value="Hello Spring!"/>
    </bean>
</beans>

MyTest.java

import com.idiot.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        User user = (User) context.getBean("user");
        user.show();
    }
}

运行结果 在这里插入图片描述 此时,将无参构造改成有参构造再运行,

public User(String name) {
    System.out.println("User无参构造");
}

发现程序报错,没有办法进行初始化,Bean 初始化失败,

 

2. 使用有参构造创建对象

有参构造器,

public User(String name) {
    this.name = name;
}

 

①下标赋值

<bean id="user" class="com.idiot.pojo.User">
    <constructor-arg index="0" value="idiot"/>
</bean>

②类型赋值

如果有两个及以上的相同类型,就无法判断把值给谁了,因此不建议使用

<bean id="user" class="com.idiot.pojo.User">
    <constructor-arg type="java.lang.String" value="idiot.."/>
</bean>

③参数名赋值

<bean id="user" class="com.idiot.pojo.User">
    <constructor-arg name="name" value="Idiot"/>
</bean>

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

 

DI依赖注入

构造器注入

初始Spring的创建中已讲,忘了的话回头看一下。  

Set注入

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

【环境搭建】

  1. 复杂类型
  2. 真实测试对象  

复杂类型

Address.java

package com.idiot.pojo;

public class Address {
    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

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

如果在 Address 类中不添加 toString 方法,则会导致输出的不是 String 值,而是类似于这样子的 com.idiot.pojo.Address@319b92f3

Student.java

package com.idiot.pojo;

import java.util.*;

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobby;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

    public String getName() {
        return name;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String[] getBooks() {
        return books;
    }

    public void setBooks(String[] books) {
        this.books = books;
    }

    public List<String> getHobby() {
        return hobby;
    }

    public void setHobby(List<String> hobby) {
        this.hobby = hobby;
    }

    public Map<String, String> getCard() {
        return card;
    }

    public void setCard(Map<String, String> card) {
        this.card = card;
    }

    public Set<String> getGames() {
        return games;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public String getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

    public Properties getInfo() {
        return info;
    }

    public void setInfo(Properties info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", \naddress=" + address +
                ", \nbooks=" + Arrays.toString(books) +
                ", \nhobby=" + hobby +
                ", \ncard=" + card +
                ", \ngames=" + games +
                ", \nwife='" + wife + '\'' +
                ", \ninfo=" + info +
                '}';
    }
}

 

真实测试对象

1. 常量注入

<bean id="student" class="com.idiot.pojo.Student">
    <property name="name" value="idiot"/>
</bean>

2. Bean注入

注意:这里的值是一个引用,ref

<bean id="addr" class="com.idiot.pojo.Address">
    <property name="address" value="浙江"/>
</bean>

<bean id="student" class="com.idiot.pojo.Student">
    <property name="name" value="idiot"/>
    <property name="address" ref="addr"/>
</bean>

3. 数组注入

<bean id="student" class="com.idiot.pojo.Student">
    <property name="name" value="idiot"/>
    <property name="address" ref="addr"/>
    <property name="books">
        <array>
            <value>C++</value>
            <value>Java</value>
            <value>Python</value>
        </array>
    </property>
</bean>

4. List注入

<property name="hobby">
    <list>
        <value>sing</value>
        <value>climb</value>
    </list>
</property>

5. Map注入

<property name="card">
    <map>
        <entry key="中国电信" value="10000"/>
        <entry key="中国移动" value="10086"/>
    </map>
</property>

6. Set注入

<property name="games">
    <set>
        <value>堡垒之夜</value>
        <value>皇室战争</value>
        <value>王者荣耀</value>
    </set>
</property>

7. Null注入

 <property name="wife"><null/></property>

8. Properties注入

<property name="info">
    <props>
        <prop key="学号">123456</prop>
        <prop key="性别">man</prop>
        <prop key="姓名">idiot</prop>
    </props>
</property>

下面是完整的beans.xml

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

    <bean id="addr" class="com.idiot.pojo.Address">
        <property name="address" value="浙江"/>
    </bean>

    <bean id="student" class="com.idiot.pojo.Student">
        <property name="name" value="idiot"/>
        <property name="address" ref="addr"/>

        <property name="books">
            <array>
                <value>C++</value>
                <value>Java</value>
                <value>Python</value>
            </array>
        </property>

        <property name="hobby">
            <list>
                <value>sing</value>
                <value>climb</value>
            </list>
        </property>

        <property name="card">
            <map>
                <entry key="中国电信" value="10000"/>
                <entry key="中国移动" value="10086"/>
            </map>
        </property>

        <property name="games">
            <set>
                <value>堡垒之夜</value>
                <value>皇室战争</value>
                <value>王者荣耀</value>
            </set>
        </property>

        <property name="wife"><null/></property>

        <property name="info">
            <props>
                <prop key="学号">123456</prop>
                <prop key="性别">man</prop>
                <prop key="姓名">idiot</prop>
            </props>
        </property>
    </bean>
</beans>

以及测试MyTest.java

@Test
public void test01(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

    Student student = (Student) context.getBean("student");

    System.out.println(student.toString());
}

运行结果

 

其他注入

P命名空间注入

需要在头文件中加入约束文件xmlns:p="http://www.springframework.org/schema/p",且实体类中要存在无参构造器,

<!--p命名空间注入,可以直接注入属性的值:property-->
<bean id="userP" class="com.idiot.pojo.User" p:name="idiot" p:age="3"/>

MyTest.java

@Test
public void test02(){
    ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
    User user = context.getBean("userP", User.class);
    System.out.println(user);
}

 

C命名空间注入

雷同于P命名空间注入,

需要在头文件中加入约束文件xmlns:c="http://www.springframework.org/schema/c",且实体类中要存在有参构造器,

<!--c命名空间注入,通过构造器注入:construct-args-->
<bean id="userC" class="com.idiot.pojo.User" c:age="3" c:name="idiot"/>

AOP

什么是AOP?

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

在这里插入图片描述

 

AOP在Spring中的作用

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

  • 横切关注点:跨越应用程序多个模块的方法或功能。与业务逻辑无关的,但是需要我们关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等...
  • 切面(ASPECT):横切关注点被模块化的特殊对象。即它是一个类。
  • 通知(Advice):切面必须要完成的工作。即它是类中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知执行的“地点”的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。

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

通知类型连接点实现接口
前置通知方法前org.springframework.aop.MethodBeforeAdvice
后置通知方法后org.springframework.aop.AfterReturningAdvice
环绕通知方法前后org.aopalliance.intercept.MethodInterceptor
异常抛出通知方法抛出异常org.springframework.aop.ThrowsAdvice
引介通知类中增加新的方法属性org.springframework.aop.IntroductionInterceptor

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

编写业务接口 UserService.java

package com.idiot.service;

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

再编写实现类 UserServiceImpl.java

package com.idiot.service;

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("查询用户");
    }
}

然后开始 AOP 环节,我们编写两个通知类 , 前置通知 BeforeLog.java, 后置通知 AfterLog.java

BeforeLog.java

package com.idiot.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeLog 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() + "方法被执行了");
    }
}

AfterLog.java

package com.idiot.log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

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

最后编写配置文件进行 Spring 中的注册,并实现 aop 切入实现,注意导入约束, applicationContext.xml

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

    <!--注册bean-->
    <bean id="userService" class="com.idiot.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.idiot.log.BeforeLog"/>
    <bean id="afterLog" class="com.idiot.log.AfterLog"/>

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

expression 是一个表达式,其中 expression="execution()" 这是固定的,execution()的括号里填写的参数是要执行的位置

这是如何判断的呢?

大概是:修饰词 -> 返回值 -> 类名 -> 方法名 -> 参数值,

* com.idiot.service.UserServiceImpl.*(..),第一个*表示类名前面可以是任意的修饰词和返回值,第二个*则表示任意的方法名,..表示任意几个参数,

测试 MyTest.java

import com.idiot.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注意:动态代理代理的是接口
        UserService userService = context.getBean("userService",UserService.class);
        userService.search();
    }
}

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

法二:自定义类实现

目标业务类不变依旧是 userServiceImpl.java

  1. 自定义编写切入类 DiyPointcut.java
package com.idiot.diy;

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

    public void after(){
        System.out.println("---------方法执行后---------");
    }
}
  1. 编写配置文件 applicationContext.xml
<!--注册bean-->
<bean id="userService" class="com.idiot.service.UserServiceImpl"/>
<bean id="diy" class="com.idiot.diy.DiyPointcut"/>

<!--aop的配置-->
<aop:config>
    <!--使用AOP的标签实现-->
    <aop:aspect ref="diy">
        <aop:pointcut id="diyPointcut" expression="execution(* com.idiot.service.UserService.*(..))"/>
        <aop:before pointcut-ref="diyPointcut" method="before"/>
        <aop:after pointcut-ref="diyPointcut" method="after"/>
    </aop:aspect>
</aop:config>
  1. 测试 MyTest.java
import com.idiot.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    @Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注意:动态代理代理的是接口
        UserService userService = context.getBean("userService",UserService.class);
        userService.search();
    }
}

在这里插入图片描述

 

法三:使用注解实现

  1. 编写一个注解实现的自定义类 AnnotationPointcut.java
package com.idiot.diy;

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(* com.idiot.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法执行前---------");
    }

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

    @Around("execution(* com.idiot.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");

        //执行目标方法proceed
        Object proceed = jp.proceed();

        System.out.println("环绕后");
    }
}
  1. Spring 配置文件中注册 bean,并增加支持注解的配置,
<bean id="annotationPointcut" class="com.idiot.diy.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>

在这里插入图片描述

aop:aspectj-autoproxy 说明:

通过 aop 命名空间的 <aop:aspectj-autoproxy /> 声明自动为 Spring 容器中那些配置 @aspect 切面的 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动态代理。  

整合Mybatis

回顾Mybatis

在这里插入图片描述

先是导入依赖包,配置仓库,同时不要忘记配置 Maven 静态资源过滤, pom.xml

<?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</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-10-mybatis</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

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

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

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

        <!-- aspectJ AOP 织入器 -->
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.2</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <!--配置Maven静态资源过滤问题-->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
</project>

编写实体类 User.java

package com.idiot.pojo;

import lombok.Data;

@Data
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 + '\'' +
                '}';
    }
}

再编写接口 UserMapper.java 及其映射文件 UserMapper.xml

UserMapper.java

package com.idiot.mapper;

import com.idiot.pojo.User;

import java.util.List;

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

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="com.idiot.mapper.UserMapper">

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

</mapper>

紧接着编写核心配置文件 mybatis-config.xml 以及外部配置文件 db.properties

mybatis-config.xml

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--引入外部配置文件-->
    <properties resource="db.properties">
        <property name="username" value="root"/>
        <property name="pwd" value="123456"/>
    </properties>

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <package name="com.idiot.pojo"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${pwd}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="com.idiot.mapper.UserMapper"/>
    </mappers>

</configuration>

db.properties

driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3307/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8

最后进行测试,编写测试类 MyTest.java,这里没有像之前那样特意的去编写工具类了,然是直接在测试类中进行 SqlSession 的一系列操作,

import com.idiot.mapper.UserMapper;
import com.idiot.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

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

什么是 MyBatis-Spring

MyBatis-Spring 就是帮助你将 MyBatis 代码无缝地整合到 Spring 中。

MyBatis-Spring 需要以下版本:

MyBatis-SpringMyBatisSpring 框架Spring BatchJDK
2.03.5+5.0+4.0+Java 8+
1.33.4+3.2.2+2.1+Java 6+

因此需要在 pom.xml 中加入以下代码:

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

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

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

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

注意:SqlSessionFactory 需要一个 DataSource(数据源),且是唯一的必要属性。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。

MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session

一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings>< typeAliases> 元素。

需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置,数据源和 MyBatis 的事务管理器都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。

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

模板(Template)可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

现在这个bean就可以直接注入到Mapper bean中,

package com.idiot.mapper;

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

import java.util.List;

public class UserMapperImpl implements UserMapper{
    //sqlSession不用我们创建,Spring来管理
    private SqlSessionTemplate sqlSession;

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

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

接下来注入 SqlSessionTemplate

<bean id="userMapper" class="com.idiot.mapper.UserMapperImpl">
    <property name="sqlSession" ref="sqlSession"/>
</bean>

 

整合一

  1. 引入Spring 的配置文件 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
       http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
  1. 配置数据源替换 mybaits 的数据源,
<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3307/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>
  1. 配置 SqlSessionFactory 来关联 MyBatis
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!--关联Mybatis-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:com/idiot/mapper/*.xml"/>
</bean>
  1. 注册 sqlSessionTemplate,关联 sqlSessionFactory
<!--注册sqlSessionTemplate , 关联sqlSessionFactory-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <!--利用构造器注入-->
    <constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
  1. 增加 Mapper 接口的实现类 UserMapperImpl.java,私有化 sqlSessionTemplate
package com.idiot.mapper;

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

import java.util.List;

public class UserMapperImpl implements UserMapper{
    //sqlSession不用我们自己创建了,Spring来管理
    private SqlSessionTemplate sqlSession;

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

    public List<User> selectUser() {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
  1. 注册 bean 实现,
<bean id="userMapper" class="com.idiot.mapper.UserMapperImpl">
    <property name="sqlSession" ref="sqlSession"/>
</bean>
  1. 再来看看我们 Mybatis 的配置文件,发现所有内容都可以被 Spring 整合,
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <typeAliases>
        <package name="com.idiot.pojo"/>
    </typeAliases>
</configuration>
  1. 测试,
@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper mapper = (UserMapper) context.getBean("userMapper");
    List<User> user = mapper.selectUser();
    System.out.println(user);
}

在这里插入图片描述

 

整合二

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

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

修改 UserMapperImpl.java

package com.idiot.mapper;

import com.idiot.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

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

修改 applicationContext.xmlbean 的配置,

<bean id="userMapper" class="com.idiot.mapper.UserMapperImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

测试, 在这里插入图片描述

总结 : 整合到 Spring 以后可以完全不要 mybatis 的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现,  

后记

以上内容只是简略的有重点的描述了 Spring 框架,要真正掌握还是需要自己继续深入学习的,谨记面向百度编程!