spring

Spring

spring概述

Spring是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以IoC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架

spring官网:http://spring.io/

Spring的发展历程

1997年IBM提出了EJB的思想

1998年,SUN制定开发标准规范EJB1.0

1999年,EJB1.1发布

2001年,EJB2.0发布

2003年,EJB2.1发布

2006年,EJB3.0发布

Rod Johnson(spring之父)

2017年9月份发布了spring的最新版本spring5.0通用版

框架的定义:拥有一整套解决方案

1、spring是一个容器,这个容器会管理很多的组件(java bean)

2、spring可以很好的解决程序间的耦合

3、spring可以集成很多第三方框架,可以充当众多技术栈之间的一个粘合剂

Spring的优势

方便解耦,简化开发

通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合,用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于业务层上的应用

AOP编程的支持

通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付

声明式事务的支持

可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明方式灵活的进行事务的管理,提高开发效率和质量

方便程序的测试

可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情

方便集成各种优秀框架

Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持

降低 JavaEE API 的使用难度

Spring 对 JavaEE API (如JDBC、JavaMail、远程调用等)进行了薄薄的封装,使这些API的使用难度大为降低

Java源码是经典学习范例

Spring 的源代码设计精妙,结构清晰,匠心独用,处处体现着大师对Java设计模式灵活运用以及对 Java 技术的高深造诣,它的源代码无疑是Java技术的最佳实践范例

Spring的体系结构

Core Container (spring核心容器)

为其它模块提供支持

Data Access/Integration(持久层相关)

Test单元测试模块

程序间的耦合

  • 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量,耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过页面传送数据的多少

  • 模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系,数据传递关系,模块间联系越多,其耦合性越强,同时表明其独立性越差(降低耦合性,可以提高其独立性)

  • 耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合

工厂模式解耦

  • 在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来,在接下来的使用的时候,直接拿过来用就好了

  • 那么,这个读取配置文件,创建和获取三层对象的类就是工厂

spring中工厂的类结构图

控制反转 IoC

  • 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度,其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)

  • 还有一种方式叫依赖查找(Dependency Lookup),通过控制反转,对象在被创建的时候,依赖被注入到对象中

明确IoC的作用:削减计算机程序的耦合(解除代码中的依赖关系)

IOC是解耦的一种思想:解耦的途径有很多:比如依赖注入DI(只是解耦的一种解决方案)

程序中核心业务代码由主动创建对象变成被动的接收对象

★创建对象的控制权发生了转变(控制反转)

依赖注入:不在类中直接new对象,在外部需要使用本类时提供对象(可以通过工厂获取对象) (set注入,构造注入)

例如在业务层不直接提供持久层的实例

public class TestServiceImpl implements TestService {

    private TestDao testDao;

    //提供一个构造方法覆盖掉原有构造
    public TestServiceImpl(TestDao testDao) {
        this.testDao = testDao;
    }

    @Override
    public void findData() {

        testDao.findData();

    }
}

★IOC

IOC — spring的IOC解决程序耦合

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

创建一个xml文件,导入spring核心容器相关的约束 ==> 创建核心容器对象 ==> 从容器中去取出实例

1、spring是一个容器,里面存放了很多的实例

2、可以把spring理解成一个工厂,我们需要从工厂中获取实例

3、创建一个xml文件,导入spring核心容器相关的约束,可以使用spring提供的相关标签(标签里面是存放数据的)

4、spring会解析这个xml标签中的数据

ApplicationContext 接口的实现类

  • ClassPathXmlApplicationContext:

    它是从类的根路径下加载配置文件 推荐使用这种

  • FileSystemXmlApplicationContext:

    它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置

  • AnnotationConfigApplicationContext:

    当我们使用注解配置容器对象时,需要使用此类来创建spring容器,它用来读取注解,需要提供核心配置类的字节码

在spring中,如何将一个类的实例装载到spring容器中?

基于xml的方式来创建spring核心容器对象

bean标签的作用:将一个类的实例通过反射机制创建出来,装载进核心容器中,默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功

id属性:唯一标识,不能重复的,给对象在容器中提供唯一标识,用于获取对象
class属性:指定类的全限定类名,用于反射创建对象,默认情况下调用无参构造函数

scope属性:可以去设置该实例的作用范围

 <bean id="aaa" class="com.beans.User"></bean>

从容器中取出实例

public static void main(String[] args) {

    //创建核心容器对象
    ApplicationContext applicationContext =

            new ClassPathXmlApplicationContext("xml/beans.xml");

    //要从容器中取出对应的实例
    Object bean = applicationContext.getBean("aaa");

    if (bean instanceof User){

        User user = (User)bean;

        //com.beans.User@42d80b78
        System.out.println(user);

    }

}

sping在获取实例时的执行流程:

会解析xml文件,去读取每一个标签,如果发现有bean标签,那么会解析bean标签对应的属性,会去读取class属性的属性值,然后通过反射机制创建实例,保存到容器中,会将id属性作为该实例的标识

问:如果将一个实例存放进核心容器中,在程序中多次获取该实例,取得的实例是不是单例的?

默认情况下是单例的,但是我们可以将其修改为多例的

在标签下修改scope属性

singleton:默认值,单例的

prototype:多例的

request:WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中

session:WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中

global session:WEB项目中,应用在Portlet环境,如果没有Porlet环境那么globalSession相当于session

<!--和默认情况相同,是单例的-->
<bean id="user" class="com.beans.User" scope="singleton"></bean>

<!--修改为多例的-->
<bean id="user" class="com.beans.User" scope="prototype"></bean>

依赖注入 Dependency Injection

  • DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中

  • 依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台

  • 通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现

基于XML配置的依赖注入(set方式)

property标签的作用

要通过set方法在xml配置文件中实现依赖注入,在类中必须实现set方法

name属性:类的属性名

value属性:类的属性值

ref属性:可以去引入容器中别的实例,属性值就是需要传入的实例的唯一标识

<bean id="user" class="com.beans.User" scope="prototype">

    <!--给id属性赋值-->
    <property name="id" value="1001"></property>
    <property name="name" value="root"></property>
    <property name="password" value="123456"></property>
    
</bean>

给数组赋值

<!--给数组注入数据-->
<property name="objects">
    <array value-type="java.lang.String">
        <value>a</value>
        <value>b</value>
        <value>c</value>
    </array>
</property>

给自定义引用数据类型赋值

<!--注入自定义的引用数据类型-->
<property name="student" ref="student"></property>
<!--将学生实例装载进核心容器中-->
<bean id="student" class="com.beans.Student"></bean>

List

<!--给list类型的属性注入数据-->
<property name="list">
    <list>
        <value>123456</value>
        <value>ture</value>
        <value>false</value>
        <value>1100</value>
    </list>
</property>

Map

<!--给map类型的属性注入数据-->
<property name="map">
    <map>
        <entry key="key1" value="value1"></entry>
        <entry key="key2" value="value2"></entry>
        <entry key="key3" value="value3"></entry>
        <entry key="key4" value="value4"></entry>
    </map>
</property>

Date

<!--注入日期类型数据-->
<property name="date" ref="date"></property>

<!--注册一个日期实例-->
<bean id="date" class="java.util.Date"></bean>

基于XML配置的依赖注入(构造方式)

基于构造方法完成对值的注入

constructor-arg

根据有参构造来创建实例

<!--注册User实例-->
<bean id="user" class="com.os467.beans.User">

    <!--通过构造方法来为属性完成注入-->
    <constructor-arg name="username" value="zs001"></constructor-arg>

</bean>

多个参数构造创建实例

<!--注册User实例-->
<bean id="user" class="com.os467.beans.User">

    <!--通过构造方法来为属性完成注入-->
    <constructor-arg name="username" value="zs001"></constructor-arg>
    <constructor-arg name="date" ref="date"></constructor-arg>
</bean>

<!--注册Date实例-->
<bean name="date" class="java.util.Date"></bean>

关于组件的生命周期

组件的作用范围是单例的:

  • 组件会随着容器的销毁而销毁

init-method属性:对象在初始化时会执行的对应方法

destroy-method属性:对象在销毁时会执行的对应方法

<bean id="user" class="com.os467.beans.User" scope="singleton" init-method="initMethod" destroy-method="destroyMethod">

需要在类中定义方法

public void initMethod(){

    System.out.println("初始化的方法执行了");

}

public void destroyMethod(){

    System.out.println("销毁的方法执行了");

}

关闭容器对象的方法

但是要用ClassPathXmlApplicationContext去接收才能调用此方法

//关闭容器对象
applicationContext.close();

关闭后,容器中的实例会被销毁

组件的作用范围是多例的:

  • 容器销毁时,组件不会销毁,会等着垃圾回收机制去回收

BeanFactory和ApplicationContext的区别

BeanFactory才是Spring容器中的顶层接口

ApplicationContext是它的子接口

创建对象的时间点不一样

  • ApplicationContext:只要一读取配置文件,默认情况下就会创建对象

  • BeanFactory:什么使用什么时候创建对象

通过BeanFactory创建的容器,其中的实例在被用到的时候才会创建

//通过BeanFactory创建容器
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("xml/beans.xml"));

//此行执行User类实例的初始化函数
User user = beanFactory.getBean("user", User.class);

三层架构通过xml配置进行依赖注入

<!--注册表现层实例-->
<bean id="accountServlet" class="com.os467.account.servlet.AccountServlet">

    <!--注入业务层的引用-->
    <property name="accountService" ref="accountService"></property>

</bean>

<!--注册业务层引用-->
<bean id="accountService" class="com.os467.account.service.AccountServiceImpl">

    <!--注入持久层的引用-->
    <property name="accountDao" ref="accountDao"></property>

</bean>

<!--注册持久层引用-->
<bean id="accountDao" class="com.os467.account.dao.AccountDaoImpl"></bean>

模拟springIOC底层基于工厂模式解耦

首先程序会去读取配置文件,然后获取文件中的全类名数据,根据反射创建对象(单例的),保存在容器里面,后面如果有用到实例的地方,我们从容器中去获取

import java.io.FileReader;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 工厂类
 */
public class BeanFactory {

    //模拟一个容器
    private static Map beansMap;

    private static Properties properties;

    //在静态代码块中去创建容器对象,为了保证存入容器中的实例是单例的,
    //所有的存放实例的步骤也要在类加载的时候去完成
    static {

        //实例化容器
        beansMap = new HashMap();

        //实例化properties集合
        properties = new Properties();

        FileReader fileReader = null;

        try {

            //创建流对象
            fileReader = new FileReader("src/beans");

            //读取数据到集合中
            properties.load(fileReader);

            //获取properties集合中所有的key
            Enumeration keys = properties.keys();

            //遍历所有的key
            while (keys.hasMoreElements()){

                //先获取对应key的字符串
                String key = keys.nextElement().toString();

                //根据key来获取value
                String propertyClass = properties.getProperty(key);

                //根据反射机制创建对象
                Object value = Class.forName(propertyClass).newInstance();

                //将实例存到容器里
                beansMap.put(key,value);

            }

        } catch (Exception e) {

            e.printStackTrace();

        }finally {

            try {

                if (fileReader != null){

                    //关闭流对象
                    fileReader.close();

                }

            } catch (IOException e) {
                e.printStackTrace();
            }

        }


    }


    /**
     * 定义一个获取实例的方法
     */
    public static Object getBean(String beanId){

        //从集合中获取实例
        return beansMap.get(beanId);

    }

}

通过工厂获得业务层的实例

package com.os467.factory.servlet;

import com.os467.factory.BeanFactory2;
import com.os467.factory.service.AccountService;

public class AccountServlet {

    //聚合业务层引用
    private AccountService accountService;

    public void saveAccount() {

        accountService = (AccountService) BeanFactory2.getBean("accountService");

        accountService.saveAccount();

    }

}
基于注解的IOC配置
注解 作用
@Component 把资源让spring来管理,相当于在xml中配置一个bean
@Controller 一般用于表现层的注解
@Service 一般用于业务层的注解
@Repository 一般用于持久层的注解

1、在配置文件开启spring对注解的支持

2、直接在类上加对应的注解即可

在使用注解的IOC配置前需要先导入springAOP的jar包底层需要

配置文件约束

名称空间:xmlns: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>

开启spring对注解的支持,其实就是一个扫描包的过程

spring程序在执行的时候会去解析配置文件的数据,然后找到对应的包结构,并且扫描该包下对应的子包,然后会去检查每一个类上面有没有spring提供的注解,如果有,会将该类的实例装载进spring核心容器中

base-package:指定的包结构

<!--开启spring对注解的支持-->
<context:component-scan base-package="com.os467"></context:component-scan>

在取出实例的时候,要注意,默认的标识是类的类名(首字符要小写)

//创建核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("xml/beans02.xml");

//取出实例
Student student = applicationContext.getBean("student", Student.class);

System.out.println(student);

类注解中的value值代表该类在获取时需要提供的id,给Student类自定义的提供一个id值

@Component("sss")
public class Student {

}

改变作用范围的注解

@Scope 指定bean 的作用范围

取值:

singleton

prototype

request

session

globalession

生命周期相关

@PostConstruct 用于指定初始化的方法

@PreDestroy 用于指定销毁的方法

public class Student {

    @PostConstruct
    public void initMethod(){

        System.out.println("初始化的方法执行了");

    }

    @PreDestroy
    public void destroyMethod(){

        System.out.println("销毁的方法执行了");

    }

}
基于注解的方式注入属性的值

用于注入数据的注解

注解 作用
@Autowired 根据类型注入
@Qualifier 按名称注入,不能单独使用
@Resource 根据id注入的
@Value 用于注入基本数据类型和String类型

@Autowired注解

注入流程:首先spring在扫描到该属性上有Autowired注解之后,会去容器中找到该属性对应的类型与之注入

如果spring容器中与该属性类型匹配的实例有多个,那么究竟该注入哪个实例,这种情况下程序会报错

给这个类的实例起标识,起的标识一定要跟被注入属性的属性名称一致

@Qualifier注解

跟Autowired结合使用,指由Qualifier来决定究竟要注入哪个实例,需要提供注入实例的标识

Qualifier不能单独使用,只能和Autowried配合使用

@Resource注解

根据id去注入,相当于Autowired和Qualifier的结合

//指定id
@Resource(name = "accountService")

@Value注解

给属性赋值,能赋值基本数据类型和String类型

@Value("jack")
String name;

模拟springIoC底层基于注解的方式进行注入

通过设计模式+反射机制来模拟

1、spring的配置文件:beans.xml(开启扫描包的过程)spring会去扫描该包下所有子包的类,为了检查有没有spring提供的注解

2、要在指定的类上加注解,如果spring检查到该类上有注解的话,就会将该类的实例通过反射机制创建出来,装进spring中

3、检查该类中的属性上有没有spring提供的注解,如果有的话,会通过反射的方式去注入实例


修饰持久层的注解

package com.os467.ioc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}

修饰业务层的注解

package com.os467.ioc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}

修饰表现层的注解

package com.os467.ioc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}

根据属性类型注入属性实例的注解

package com.os467.ioc.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWired {
}

工厂类,用于储存实例

package com.os467.ioc.factory;

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

/**
 * 工厂类,用于存放组件的实例(相当于容器)
 */
public class BeanFactory {

    //创建Map集合对象
    private static Map beansMap = new HashMap();

    public static Map getBeansMap() {
        return beansMap;
    }

    /**
     * 获取实例的方法
     */

    public static Object getBean(String beanId){

        return beansMap.get(beanId);

    }

}

模拟spring底层基于注解Ioc解耦(扫描指定包结构下的类,创建所需要的实例)

package com.os467.ioc.scan;

import com.os467.ioc.annotation.AutoWired;
import com.os467.ioc.annotation.Controller;
import com.os467.ioc.annotation.Repository;
import com.os467.ioc.annotation.Service;
import com.os467.ioc.factory.BeanFactory;

import java.io.File;
import java.io.FileFilter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 模拟spring底层基于注解的Ioc解耦
 */
public class ComponentScan {

    //创建一个集合对象,用于存放全类名
    private static List<String> classNameList = new ArrayList();

    /**
     * 模拟装配实例,注入实例的过程
     * @param pathName
     */
    public static void getComponentScan(String pathName){

        //把"."替换成"/"
        pathName = pathName.replace(".","/");

        //获取当前工程的绝对路径
        String path = ClassLoader.getSystemResource("").getPath() + pathName;

        //创建一个File对象
        File file = new File(path);

        //过滤指定路径下的文件
        addFiles(file);

        //遍历集合
        for (String className : classNameList) {

            try {

                //创建字节码对象
                Class aClass = Class.forName(className);

                //根据反射检测类上有没有自定义的注解
                Controller controller = (Controller)aClass.getAnnotation(Controller.class);
                Service service = (Service)aClass.getAnnotation(Service.class);
                Repository repository = (Repository)aClass.getAnnotation(Repository.class);

                //如果类上有以上任意一个注解,我们就把该类添加到容器里面
                if (controller != null || service != null || repository != null){

                    //根据反射创建实例
                    Object obj = aClass.newInstance();

                    //获取类的简类名
                    String simpleName = aClass.getSimpleName();

                    //将实例添加到工厂
                    BeanFactory.getBeansMap().put(simpleName,obj);

                }


            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }

        }

        //继续遍历集合,将需要注入的属性进行注入
        for (String className : classNameList) {

            try {

                //创建字节码对象
                Class aClass = Class.forName(className);

                //根据反射检测类上有没有自定义的注解
                Controller controller = (Controller) aClass.getAnnotation(Controller.class);
                Service service = (Service) aClass.getAnnotation(Service.class);
                Repository repository = (Repository) aClass.getAnnotation(Repository.class);

                //如果类上有以上任意一个注解,继续检查类中的属性有没有自定义的注解
                if (controller != null || service != null || repository != null) {

                    //获取属性字节码对象
                    Field[] declaredFields = aClass.getDeclaredFields();

                    //遍历属性数组
                    for (Field declaredField : declaredFields) {

                        //检查属性上有没有自定义的注解
                        AutoWired autoWired = declaredField.getAnnotation(AutoWired.class);

                        //如果这个注解不为空,就意味着该属性需要注入
                        if (autoWired != null){

                            //获取容器对象
                            Map beansMap = BeanFactory.getBeansMap();

                            //将容器对象转成set集合
                            Set<Map.Entry> entrySet = beansMap.entrySet();

                            //遍历set集合
                            for (Map.Entry entry : entrySet) {

                                //获取每个实例实现的接口
                                Class[] interfaces = entry.getValue().getClass().getInterfaces();

                                // 遍历接口数组,因为AutoWired是根据类型注入的,我们要保证注入的实例跟聚合的属性是同一类型
                                for (Class anInterface : interfaces) {

                                    if (anInterface == declaredField.getType()){

                                        //打破封装
                                        declaredField.setAccessible(true);

                                        //给属性赋值
                                        declaredField.set(BeanFactory.getBean(aClass.getSimpleName()),entry.getValue());

                                    }

                                }

                            }

                        }

                    }

                }


            } catch (Exception e) {
                e.printStackTrace();
            }

        }

    }

    /**
     * 定义一个递归的方法,用于过滤符合条件的文件
     * file.listFiles():将所有的文件或者是文件夹切成一个数组
     * @param file
     */
    public static void addFiles(File file){

        //获取该文件夹下面所有的子文件夹,FileFilter接口实现类用于返回符合条件的文件
        File[] files = file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {

                //判断文件的类型
                if (pathname.isDirectory()){

                    //继续调用递归的方法
                    addFiles(pathname);

                }

                //我们过滤的文件一般是以.class结尾的
                return pathname.getPath().endsWith(".class");

            }
        });

        //遍历文件的数组,将符合要求的文件路径切割成全类名的形式
        for (File f : files) {

            String path = f.getPath();

            //将所有的"\"替换成"."

            path = path.replace("\\",".");

            //将com前面的字符串删了
            path = path.substring(path.lastIndexOf("com"),path.length());

            //将".class"切割掉
            path = path.replace(".class","");

            //将切割好的全类名添加到集合里面
            classNameList.add(path);

        }

    }

}

基于注解的方式来创建核心容器对象

​ 1、之前创建核心容器对象都是基于xml的,必须得有配置文件

​ 2、spring5.0版本之后也是支持基于注解的方式创建核心容器对象,这样我们的程序就可以脱离xml配置文件

sping5.0版本后新的注解

@Configuration

声明当前的类是一个配置类,仅起到标识作用

@ComponentScan

传入一个指定的包结构路线,开启扫描包,如果被@Component或其它特定注解修饰的类就会被添加到核心容器中

@Bean

  • 更多操作类似于在xml中的bean标签,用于修饰方法,可以将修饰方法的返回值作为一份实例添加到spring容器中

  • 可以给实例起别名,默认情况下标识名称是方法名,而且修饰方法的参数实例会去容器中去匹配,匹配的规则和@Autowired是一样的,都是根据类型去匹配的

  • 如果在获取配置类中的实例时,有相同类型的方法,那么就会根据方法被Bean修饰的标识名称来匹配实例,或者用@Qualifier来修饰参数指定实例标识

注意:如果方法名称相同,即使用@Bean起了别名,有参的也会覆盖掉无参的

配置类

@Configuration
public class SpringConfig {

    @Bean
    public User getUser(Student student){
        
        User user = new User();
        
        user.setStudent(student);

        return user;

    }

    @Bean("student")
    public Student getStudent(){

        return new Student();

    }

}

测试类

public static void main(String[] args) {

    //创建核心容器对象
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);

    //从容器中取实例
    Object obj = applicationContext.getBean("getUser");

    if (obj instanceof User){

        User user = (User)obj;


    }

}

@PropertySource

引入配置文件,可以通过@Value注解读取数据

classpath: 读取的配置文件资源需要放在src文件下,可以通过el表达式取到值

@Configuration
@PropertySource("classpath:jdbc")
public class JdbcConfig {

    @Value("${name}")
    private String name;

    @Value("${password}")
    private String password;

    @Bean("user")
    public User getUser(){

        User user = new User();

        user.setName(name);
        user.setPassword(password);

        return user;

    }

}

配置文件jdbc

name=root
password=root

@Import

可以让两个核心配置类之间存在继承的关系

引入其它核心配置类,需要提供一个配置类的字节码属性

@Import(SpringConfig.class)

★AOP

AOP概述

AOP:全称是 Aspect Oriente Programming

(面向切面编程)

作用

在程序运行期间,不修改源码对已有方法进行增强

优势

  • 减少重复代码
  • 提高开发效率
  • 维护方便

Joinpoint(连接点):

​ 连接点是指那些被拦截到的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,指的就是动态代理在底层可以访问到的所有方法

Pointcut(切入点):

​ 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义,就是要对哪些连接点(方法)进行增强,即需要被增强的方法

Advice(通知/增强):

所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知的类型:

  • 前置通知:

    • 调用目标方法之前执行的内容
  • 后置通知:

    • 调用目标方法之后执行的内容
  • 异常通知:

    • 在catch语句块中织入的内容
  • 最终通知:

    • 在finally语句块中织入的内容
  • 环绕通知:

    • 整个invoke方法执行的过程叫做环绕通知

Target(目标对象):

​ 代理的目标对象

Weaving(织入):

​ 是指把增强应用到目标对象来创建新的代理对象的过程(增强的过程)

spring采用动态代理织入,而AspectJ采用编译器织入和类装载器织入

Proxy(代理):

一个类被AOP织入增强后,就产生一个结果代理类

Aspect(切面):

是切入点(要增强的方法)和通知(怎么增强)之间的关系

JDK动态代理模拟AOP底层

场景:

​ 之前学习jdbc的时候我们接触到了事务,A账户给B账户赚钱,我们需要加入事务的操作,要满足事务的一致性

如果用代理模式来完成对事务的织入,程序该如何去写?

使用JDK动态代理来为目标类创建代理类

package com.os467.proxy;

import com.os467.account.service.AccountService;
import com.os467.utils.TransactionMangerUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 为业务层做代理
 */
@Component
public class AccountProxy {

    //目标对象,需要被代理的对象
    @Autowired
    private AccountService accountService;

    /**
     * 获取代理对象
     * @return
     */
    public Object getProxy(){

        return Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {

            /**
             * 完成对目标方法的增强
             *
             * 在springAop中:
             *
             * 前置通知:调用目标方法之前执行的内容
             * 后置通知:调用目标方法之后执行的内容
             * 异常通知:在catch语句块中织入的内容
             * 最终通知:在finally语句块中织入的内容
             * 环绕通知:整个invoke方法执行的过程叫做环绕通知
             *
             * @param proxy
             * @param method
             * @param args
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Object invoke = null;

                try {

                    if (method.getName().equals("transfer")){

                        //开启事务
                        TransactionMangerUtils.beginTransaction();

                    }

                    //调用目标类方法
                    invoke = method.invoke(accountService, args);

                    if (method.getName().equals("saveAccount")){

                        //提交事务
                        TransactionMangerUtils.commitTransaction();

                    }

                }catch (Exception e){

                    //回滚事务
                    TransactionMangerUtils.rollBackTransaction();

                    //关闭事务
                    TransactionMangerUtils.closeTransaction();

                    e.printStackTrace();
                }finally {

                    if (method.getName().equals("saveAccount")){

                        //关闭事务
                        TransactionMangerUtils.closeTransaction();

                    }

                }

                return invoke;

            }
        });

    }
}

测试类

public class AopTest01 {

    private static ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);


    public static void main(String[] args) {

        //从容器中取出代理对象
        AccountProxy accountProxy = applicationContext.getBean("accountProxy", AccountProxy.class);

        AccountService proxy = (AccountService)accountProxy.getProxy();

        proxy.transfer();
        proxy.saveAccount();

    }

}

基于XML的AOP配置

通过xml来配置aop(切面):我们配置的是切入点和通知之间的关系

​ 我们想在项目中去加入收集日志的功能,但是不能在核心模块中出现,会通过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">
    
</beans>

标签

aop:config:

作用:用于声明开始aop的配置

aop:aspect:

作用:配置切面

aop:pointcut:

作用:配置切入点表达式

<aop:before method="beforeAdvice" pointcut="execution(* com.os467.aop.method.AopMethod.*(..))"></aop:before>

第一个*标识当前工程,后面是资源路径,类.*标识该类下的所有方法,(..)代表匹配所有无参,(String,int)表示匹配指定参数的方法

aop:before:

作用:用于配置前置通知

aop:after-returning:

作用:用于配置后置通知


配置切面

<!--配置一个目标类的实例-->
<bean id="aopMethod" class="com.os467.aop.method.AopMethod"></bean>

<!--配置通知类的实例-->
<bean id="aopLogger" class="com.os467.aop.logger.Logger"></bean>

<!--开始配置Aop-->
<aop:config>

    <!--
        配置切面:

        要去引入一个通知类

    -->
    <aop:aspect id="idAspect" ref="aopLogger">

        <!--
            开始配置通知:
            pointcut:配置切入点表达式,就是要设置通知的作用范围
        -->

        <!--配置前置通知-->
        <aop:before method="beforeAdvice" pointcut="execution(* com.os467.aop.method.AopMethod.*(..))"></aop:before>

        <!--配置后置通知-->
        <aop:after-returning method="afterAdvice" pointcut="execution(* com.os467.aop.method.AopMethod.*(..))"></aop:after-returning>

        <!--配置异常通知-->
        <aop:after-throwing method="exceptionAdvice" pointcut="execution(* com.os467.aop.method.AopMethod.*(..))"></aop:after-throwing>

        <!--最终通知-->
        <aop:after method="endAdvice" pointcut="execution(* com.os467.aop.method.AopMethod.*(..))"></aop:after>

    </aop:aspect>

</aop:config>

外部声明切入点表达式

<!--声明一个切入点表达式-->
<aop:pointcut id="pt01" expression="execution(* com.os467.aop.method.AopMethod.*(..))"/>

<!--配置前置通知-->
<aop:before method="beforeAdvice" pointcut-ref="pt01"></aop:before>

<!--配置后置通知-->
<aop:after-returning method="afterAdvice" pointcut-ref="pt01"></aop:after-returning>

通知类

package com.os467.aop.logger;

/**
 * 日志管理类(通知类)
 */
public class Logger {

    /**
     * 前置通知
     */
    public void beforeAdvice(){

        System.out.println("前置通知开始记录日志了");

    }

    /**
     * 后置通知
     */
    public void afterAdvice(){

        System.out.println("后置通知开始记录日志了");

    }

    /**
     * 异常通知
     */
    public void exceptionAdvice(){

        System.out.println("异常通知开始记录日志了");

    }

    /**
     * 最终通知
     */
    public void endAdvice(){

        System.out.println("最终通知开始记录日志了");

    }

}

需要被增强的核心类

package com.os467.aop.method;

/**
 * 需要加入收集日志功能的核心类
 */
public class AopMethod {

    public void getMethod01(){

        System.out.println("getMethod01方法正在执行");

    }

    public void getMethod02(){

        System.out.println("getMethod02方法正在执行");

    }

    public void getMethod03(){

        System.out.println("getMethod03方法正在执行");

    }

}

测试类

public class AopTest {

    public static void main(String[] args) {

        //从容器中把目标类的实例取出来
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("xml/beans.xml");

        //取出实例
        AopMethod aopMethod = applicationContext.getBean("aopMethod", AopMethod.class);

        //调用需要增强的方法
        aopMethod.getMethod01();

    }


}

基于注解的AOP配置

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

    <!--开启spring的扫描包-->
    <context:component-scan base-package="com.os467"></context:component-scan>

    <!--开启AOP对注解支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    
</beans>

@Aspect

声明当前的类是一个切面类

@Pointcut

修饰一个方法,注解中的值为切入点表达式

@Before

前置通知

@AfterReturning

后置通知

@AfterThrowing

异常通知

@After

最终通知

通过注解配置通知类

/**
 * 日志管理类(通知类)
 */
@Component("logger")
@Aspect
public class Logger {

    /**
     * 声明一个切入点表达式
     */
    @Pointcut("execution(* com.os467.aop.method.AopMethod.*(..))")
    public void pt01(){};

    /**
     * 前置通知
     */
    @Before("pt01()")
    public void beforeAdvice(){

        System.out.println("前置通知开始记录日志了");

    }

    /**
     * 后置通知
     */
    @AfterReturning("pt01()")
    public void afterAdvice(){

        System.out.println("后置通知开始记录日志了");

    }

    /**
     * 异常通知
     */
    @AfterThrowing("pt01()")
    public void exceptionAdvice(){

        System.out.println("异常通知开始记录日志了");

    }

    /**
     * 最终通知
     */
    @After("pt01()")
    public void endAdvice(){

        System.out.println("最终通知开始记录日志了");

    }

}

测试类

public class AopTest02 {

    public static void main(String[] args) {

        ApplicationContext applicationContext = new  ClassPathXmlApplicationContext("xml/beans02.xml");

        AopMethod aopMethod = applicationContext.getBean("aopMethod", AopMethod.class);

        aopMethod.getMethod01();

    }
    
}

环绕通知

在配置环绕通知的时候可以不去配置前置,后置,异常,结束这些通知,因为可以在环绕通知中定义这些通知

xml配置环绕通知

<!--配置环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="pt01"></aop:around>

注解配置环绕通知

@Around("pt01()")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){环绕通知具体内容}

增强方法内容

/**
 * 环绕通知,需要给它一个接口参数,spring会自动提供实例
 */
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){

    //获取调用目标方法时所对应的参数
    Object[] args = proceedingJoinPoint.getArgs();

    //调用目标方法
    try {

        System.out.println("前置通知");

        //类似于invoke方法
        proceedingJoinPoint.proceed(args);

        System.out.println("后置通知");

    } catch (Throwable throwable) {

        System.out.println("异常通知");

        throwable.printStackTrace();
    }finally {

        System.out.println("结束通知");

    }


}

单元测试junit

单元测试在底层是封装了main函数的,在不写主函数的情况下,可以去让程序跑起来

通过spring来集成单元测试(可以在不创建核心容器的情况下,取到容器中的实例)

@Test

在方法上加上Test注解,该方法就会变成像主函数一样可以运行

@Test
public void test01(){

    System.out.println("成功运行了该方法");

}

在程序运行的时候动态获取核心容器对象

@RunWith

这个注解可以替换运行器,我们选用spring提供的运行器

@ContextConfiguration

指定如何创建核心容器对象,需要提供xml路径或者是配置类class

/**
 * @RunWith 这个注解可以替换运行器
 * SpringJUnit4ClassRunner 这个类实现了运行器接口,可以在spring程序运行的时候获取核心容器对象
 * @ContextConfiguration 以什么方式创建核心容器对象(xml/注解)
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:xml/beans.xml")
public class JunitTest01 {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void test01(){

        //取出实例
        AccountServiceImpl service = applicationContext.getBean("service", AccountServiceImpl.class);

        service.saveAccount();
        
    }
    
}

基于核心配置类创建容器(注解注册)

@ContextConfiguration(classes = SpringConfig.class)

jdbcTemplate模板对象

jdbcTemplate对JDBC做了封装,相当于是一个工具类

//创建jdbcTemplate模板对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();

//创建数据源的示例
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();

//设置连接地址
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/web_test?serverTimezone=GMT&characterEncoding=utf-8");

//设置驱动全类名
driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");

//设置用户名
driverManagerDataSource.setUsername("root");

//设置密码
driverManagerDataSource.setPassword("root");

//给模板对象注入数据源的实例
jdbcTemplate.setDataSource(driverManagerDataSource);

//执行一个添加的功能
int num = jdbcTemplate.update("insert into tb_user(username,password)values('tom','10010011')");

System.out.println(num == 1 ?"添加成功":"添加失败");
xml中的特殊符号
表达式 符号
&lt; <
&gt; >
&amp; &
&apos; &apos;
&quot; "

通过xml配置注入jdbcTemplate模板实例

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

    <!--开启对注解的支持-->
    <context:component-scan base-package="com.os467"></context:component-scan>

    <!--配置一个jdbcTemplate实例-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">

        <!--注入数据源的实例-->
        <property name="dataSource" ref="dataSource"></property>

    </bean>

    <!--注入一个数据源的实例-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

        <!--注入源数据-->
        <property name="url" value="jdbc:mysql://localhost:3306/web_test?serverTimezone=GMT&amp;characterEncoding=utf-8"></property>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>

    </bean>



</beans>

junit测试类

package com.os467.test;

import com.os467.account.service.AccountService;
import com.os467.account.service.AccountServiceImpl;
import com.os467.beans.User;
import com.os467.config.SpringConfig;
import com.os467.user_test.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * @RunWith 这个注解可以替换运行器
 * SpringJUnit4ClassRunner 这个类实现了运行器接口,可以在spring程序运行的时候获取核心容器对象
 * @ContextConfiguration 以什么方式创建核心容器对象(xml/注解)
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:xml/beans.xml")
public class JunitTest01 {

    @Autowired
    private AccountService accountService;

    @Autowired
    private UserService userService;

    @Test
    public void test01(){

        accountService.saveAccount();

    }

    /**
     * 通过jdbcTemplate模板连接数据库
     */
    @Test
    /**
     * 测试添加的功能
     */
    public void test02(){

        int i = userService.addUser();

        System.out.println(i == 1 ?"添加成功":"添加失败");

    }

    /**
     * 测试删除功能
     */
    @Test
    public void test03(){

        int i = userService.deleteUserById();

        System.out.println(i == 1 ?"删除成功":"删除失败");

    }

    /**
     * 测试修改功能
     */
    @Test
    public void test04(){

        int i = userService.updateUserById();

        System.out.println(i == 1 ?"修改成功":"修改失败");

    }

    /**
     * 测试查询的功能
     */
    @Test
    public void test05(){

        List<User> userList = userService.findAllByUsers();

        for (User user : userList) {

            System.out.println(user);

        }

    }

}

持久层

package com.os467.user_test.dao;

import com.os467.beans.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

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

@Repository
public class UserDaoImpl implements UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public List<User> findAllByUsers() {
        return jdbcTemplate.query("select * from tb_user", new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException {

                int id = resultSet.getInt("id");

                String username = resultSet.getString("username");

                String password = resultSet.getString("password");

                //对java对象的封装,创建User实例
                User user = new User(id, username, password);

                return user;
            }
        });
    }

    @Override
    public int deleteUserById() {
        return  jdbcTemplate.update("delete from tb_user where id = 7");
    }

    /**
     * 修改
     * @return
     */
    @Override
    public int updateUserById() {
        return jdbcTemplate.update("update tb_user set username = ?,password = ? where id = ?","tom","123",6);
    }

    @Override
    public int addUser() {

        return jdbcTemplate.update("insert into tb_user(username,password)values('tom','111')");
    }
}

Spring中的事务控制

  • JavaEE 体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案

  • spring框架为我们提供了一组事务控制的接口,具体在后面的第二小节介绍,这组接口是在spring-tx-5.0.2.RELEASE.jar

  • spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现,我们学习的重点是使用配置的方式实现

PlatformTransactionManager接口

此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法

管理事务的子类对象:

1**.DataSourceTransactionManager使用SpringJDBCiBatis**进行持久化数据时使用

2.HibernateTransactionManager使用Hibernate版本进行持久化数据时使用

基于XML的声明式事务控制

配置步骤

​ 1、配置事务管理器

​ 2、配置事务的通知引用事务管理器

​ 3、配置事务的属性

​ 4、配置AOP切入点表达式

​ 5、配置切入点表达式和事务通知的对应关系

引入xmlns:tx相关约束

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

<!--配置一个事务管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        
        <!--注入数据源实例-->
        <property name="dataSource" ref="dataSource"></property>
        
    </bean>

场景:银行转账的场景

TransactionDefinition事务属性

isolation

​ 获取事务的隔离级别

​ 默认值是DEFAULT,数据库级别的

propagation

​ 获取事务的传播行为,表示是否开启事务,以什么样的策略开启

​ REQUIRED表示一定会开启事务,无论是增删改查都会开启

​ SUPPORTS表示有事务就会开启事务,没有事务就不会开启,会在增删改 的场景下开启事务,查询场景不会开启

timeout

​ 获取超时时间,默认值是-1,永不超时,如果是正数的话,可以以秒为单 位设置超时时间

read-only

​ 会影响事务是否开启

​ 获取是否是只读事务(true/false),默认值是false,在查询的业务场景下,会把该属性设置成true

rollback-for:

是否开启回滚,默认值是true 自定义一个异常,除了该异常回滚,所有异常都不回滚
no-rollback-for:

自定义一个异常,除了该异常不回滚,所有异常都回滚

默认情况下,所有异常都回滚

只有在增删改的业务场景下,才会取开启事务,查询业务场景下不用开启事务,或者是只读就行

注意:spring中单个事务的生命周期只发生在在一个方法中(需要被AOP增强的每个方法)

如何配置事务管理器

<!--配置一个事务管理器-->
<bean id="dataSourceManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <!--注入数据源实例-->
    <property name="dataSource" ref="dataSource"></property>

</bean>

如何配置事务的通知

<!--配置一个事务的通知-->
<tx:advice transaction-manager="dataSourceManager">

    <!--配置事务的属性-->
    <tx:attributes>

        <!--
            <tx:method name=""/>
            name属性:用于匹配需要织入事务的方法
            设置匹配规则,匹配方法名称,*代表通配符
            isolation:事务的隔离级别
        -->

        <tx:method name="update*" isolation="DEFAULT" />

    </tx:attributes>
</tx:advice>

完整的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:aop="http://www.springframework.org/schema/aop"
       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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启对注解的支持-->
    <context:component-scan base-package="com.os467"></context:component-scan>

    <!--配置一个jdbcTemplate实例-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">

        <!--注入数据源的实例-->
        <property name="dataSource" ref="dataSource"></property>

    </bean>

    <!--注入一个数据源的实例-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">

        <!--注入源数据-->
        <property name="url" value="jdbc:mysql://localhost:3306/web_test?serverTimezone=GMT&amp;characterEncoding=utf-8"></property>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>

    </bean>
    
</beans>

基于注解的声明式事务控制

配置步骤

​ 1、配置事务管理器并注入数据源

​ 2、在业务层使用@Transactional注解

​ 3、在配置文件中开启spring对注解事务的支持

@Transactional

事务注解,可以修饰类,也可以修饰方法

修饰类:该类下所有的方法都会加入事务的支持

修饰方法:只有指定方法会加入事务支持,方法的优先级比类要高

注意:使用注解,事务管理器还需要在xml中配置,但是事务通知可以不用配置了

package com.os467.account.service;
import com.os467.account.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional(propagation = Propagation.REQUIRED)
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Override
    public void saveAccount() {

        accountDao.savAccount();

    }
}

★MVC

什么是MVC?

  • MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范

  • 是将业务逻辑、数据、显示分离的方法来组织代码

  • MVC主要作用是降低了视图与业务逻辑间的双向耦合

  • MVC不是一种设计模式,MVC是一种架构模式,不同的MVC存在差异

Model(模型):数据模型,提供前端要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao)和服务层(行为Service),也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务

View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西

Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示,也就是说控制器做了个调度员的工作

SpringMVC

Spring MVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架

Spring MVC的特点:

​ 1、轻量级,简单易学

​ 2、高效,基于请求响应的MVC框架

​ 3、与Spring兼容性好,无缝结合

​ 4、约定优于配置

​ 5、功能强大:RESTful、数据验证、格式化、本地化、主题化等

​ 6、、简洁灵活

最开始我们在处理请求和响应是基于servlet的,那servlet是一个规范,不属于框架,springMVC是一个表现层的框架,在底层封装了servlet

springMVC核心原理

  1. 用户发送请求给服务器,url:user.do
  2. 服务器收到请求,发现DispatcherServlet可以处理,于是调用DispatcherServlet
  3. DispatcherServlet内部,通过HandlerMappings(处理映射器)检查这个url有没有对应的Controller,如果有,则通HandlerAdapters(处理适配器)调用Controller
  4. Controller开始执行
  5. Controller执行完毕后,如果返回字符串,则ViewResolvers将字符串转化成相应的视图对象,如果返回ModelAndView对象,该对象本身就包含了视图对象信息
  6. DispatcherServlet通过ViewResolvers(视图解析器)将视图对象中的数据输出给服务器
  7. 服务器将数据输出给客户端

springMVC整体调度流程

  • 客户端发送请求给服务端,springMVC框架会通过DispatcherServlet来做一个资源的拦截(http://localhost:8080/servlet/test)
  • DispatcherServlet会调用处理映射器HandlerMappings来完成对地址的解析,然后把解析的信息告诉DispacherServlet
  • DispacherServlet调用处理适配器HandlerAdapters从容器中找到对应的资源(会找到controller1对应的资源)
  • HandlerAdapters找到对应资源之后,会去执行具体的Controller(然后会去封装数据,设置视图名称)
  • 执行完逻辑之后会将视图和模块(ModelAndView)返回给DispacherServlet
  • 继续调用视图解析器ViewResolvers视图解析器来解析ModelAndView里面视图名称,然后根据视图解析器内部的匹配规则来定位到具体的前端资源(jsp)
  • ViewResolvers在解析完之后会将解析的信息告诉DispacherServlet,然后会去做具体的页面跳转,数据渲染等工作

配置springMVC

springMVC框架如何去使用,如何去配置?

使用控制器

创建一个TestController类,实现Controller接口

package com.os467.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {

        //创建一个模型视图对象
        ModelAndView modelAndView = new ModelAndView();

        //封装数据
        modelAndView.addObject("name","Hello spring MVC");

        //设置视图名称
        modelAndView.setViewName("test02");

        return modelAndView;

    }

}

我们在定义了一个Controller之后如何通过mvc去访问这个资源?

1、在web.xml中配置DispatcherServlet:mvc框架前端总调度器

具体会去完成web交互时的各自调度工作

  • Spring的web框架围绕DispatcherServlet调度Servlet设计
  • DispatcherServlet的作用是将请求分发到不同的处理器,从Spring2.5开始,使用Java5或者以上版本的用户可以采用基于注解形式进行开发

web.xml中配置前端总调度器,所有的访问请求都会进入此资源

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
   <!--配置前端总调度器-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        
        <!--拦截所有的客户端的请求-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

2、由于MVC框架属于spring的功能模块,所以必须得有核心容器的支持,要去创建spring配置文件springMVC.xml

创建springMVC.xml配置文件,引入springMVC的xml约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
</beans>

web.xml中注册配置文件位置,核心容器对象可以交给DispacherServlet去创建,需要指定配置文件的位置,以及设置启动级别

<!--配置前端总调度器-->
 <servlet>
     <servlet-name>dispatcherServlet</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

     <!--绑定springMVC的配置文件-->
     <init-param>
         <param-name>contextConfigLocation</param-name>
         <!--配置文件的路径-->
         <param-value>classpath:xml/springMVC.xml</param-value>
     </init-param>

     <!--设置启动级别,需要在tomcat服务器启动的时候就去加载核心容器对象-->
     <load-on-startup>1</load-on-startup>

 </servlet>

继续配置springMVC.xml,注册controller组件,视图解析器实例

<!--注册controller组件-->
<bean id="/controller1" class="com.os467.controller.TestController"></bean>

<!--处理映射器和适配器可以不做注册,这里仅作演示-->
<!--注册一个处理映射器-->
<bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>

<!--注册一个处理适配器-->
<bean id="handlerAdapter" class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>

<!--必须要配置视图解析器-->
<!--注册一个视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">

    <!--设置视图的前缀-->
    <property name="prefix" value="/jsp/"></property>

    <!--设置视图的后缀-->
    <property name="suffix" value=".jsp"></property>

</bean>

SpringMVC基于注解

<mvc:default-servlet-handler/>

让SpringMVC不处理静态资源

<mvc:annotation-driven/>

支持mvc注解驱动

InternalResourceViewResolver

视图解析器

配置xml,开启对注解的支持

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--开启扫描包-->
    <context:component-scan base-package="com.os467"></context:component-scan>

    <!--过滤静态资源-->
    <mvc:default-servlet-handler></mvc:default-servlet-handler>

    <!--支持mvc注解的驱动-->
    <mvc:annotation-driven></mvc:annotation-driven>

    <!--配置视图解析器-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">

        <!--设置前缀-->
        <property name="prefix" value="/WEB-INF/jsp/"></property>

        <!--设置后缀-->
        <property name="suffix" value=".jsp"></property>

    </bean>

</beans>

@Controller注解

这个注解如果只是在spring框架中使用的话,就是将当前类的实例存到容器中
但是如果在springMVC中去使用,它不仅可以存实例到容器,还能够将该类下任意一个方法的返回值是String
的方法,返回值会作为视图名称,用于视图跳转

@RequestMapping注解

用于地址的映射,可以修饰类也可以修饰方法,也可以只在方法上使用,能够体现层级关系

@Controller
@RequestMapping("user")
public class UserController {

    @RequestMapping("test")
    public String getTest(){

        return "test";

    }

}

此时在前端访问:http://localhost:8080/springMVC/user/test 测试资源

@ResponseBody注解

让当前视图失效,并且会直接返回数据

springMVC如何去接收前端传递过来的值

​ springMVC可以支持参数接值,你在前端需要传递的数据,后端mvc可以使用参数进行接收

可以在接收参数的时候接收HttpServletrequest,再接收Parameter值

也可以在参数中根据对应参数名接值

//前端测试:http://localhost:8080/springMVC/user/test03?username=jack001&password=123456&i=10&d=3.14
@RequestMapping("test03")
public String getTest03(String username,String password,int i,double d){

    System.out.println(username+" "+password+" "+i+" "+d);

    return "test";

}

springMVC在参数列表中可以通过实体进行接值

mvc会根据接收实例的属性名称来接收对应的前端数据,并且封装成java对象

@RequestMapping("test04")
public String getTest04(MvcVo mvcVo){

    System.out.println(mvcVo);

    return "test";

}

springMVC如何将封装好的数据存到Model中

在参数中接收一个Model接口的实例,是由springMVC提供的,底层封装了request域

通过返回字符串的方式

@RequestMapping("test05")
public String getTest05(MvcVo mvcVo, Model model){

    //把实体存到Model中,底层封装了一个request域
    model.addAttribute("mcvVo",mvcVo);

    return "test02";

}

通过返回ModelAndView的方式

@RequestMapping("list")
public ModelAndView getEmpList(){

    ModelAndView modelAndView = new ModelAndView();

    List<Emp> empList = empService.getEmpList();

    //访问持久层查询数据
    modelAndView.addObject("empList",empList);

    //设置视图名称
    modelAndView.setViewName("list");

    return modelAndView;
}

通过SpringMVC来实现转发和重定向

forward

redirect

项目

通过spring + springMVC + jdbcTemplate 来搭建一个web项目,完成员工列表


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com

文章标题:spring

字数:14.6k

本文作者:Os467

发布时间:2022-07-26, 21:54:22

最后更新:2022-09-05, 00:09:15

原始链接:https://os467.github.io/2022/07/26/spring/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

喜欢就点赞,疼爱就打赏