Spring学习笔记

Spring学习笔记

一、框架简述

1.Spring轻量级的开源JAVA EE框架。体积小,不需要额外依赖其他组件,可单独使用。免费,方便

2.主要核心模块IOC(控制反转/依赖注入)和AOP(面向切面编程)两大技术:

​ (1)IOC:控制反转,把创建和销毁对象的权利交给Spring进行管理。

​ (2)AOP:面向切面,不修改源代码进行功能加强(advice)。

Spring IOC(控制反转/依赖注入)

Spring AOP(面向切面编程)

Spring JDBC+事务

Spring Task 定时调度

Spring Mail 邮件发送

3.Spring框架的特点:

  • 方便解耦 简化开发
  • AOP支持
  • 方便集成各种框架

4.Spring作用

1.Dao层

​ JDBC操作

​ 对应的框架 :Mybatis

2.Service层

​ Spring框架不是针对service层的业务逻辑 service没有适合的框架

2.Controller层

​ Servlet - 接受请求 响应数据 地址配置 页面转发

​ 对应框架:SpringMVC

5.Spring基于分布式的应用程序

​ (1)基于轻量级的框架

​ 配置管理

​ Bean对象的实例化-IOC

​ (2)集成第三方的框架

​ Mybatis、Hibernate(持久层框架)

​ SpringMVC

​ Spring Security 权限控制

​ Quartz时钟框架(定时任务处理)

​ Elasticsearch 搜索引擎

​ (3)自带服务

​ Mail邮件发送

​ Task定时任务处理-定时调度(定时短信、定时任务)

​ 消息处理(异步处理)

6.Spring模块划分

​ Spring IOC模块:Bean对象的实例化 Bean的创建

​ Spring AOP模块:动态代理面向切面编程

​ Spring JDBC+事务模块

​ Spring Web模块

二、Spring IOC

  1. 主要内容
    在这里插入图片描述

2.2源码架构

  • 核心容器: Spring-beans和Spring-core模块是Spring框架的核心模块,包含控制反转(Inversion of Control,loC)和依赖注入(Dependency Injection, DI) ,核心容器提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,工厂模式的实现。BeanFactory使用控制反转((IOc)思想将位用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring上下文Spring Context: Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,例如JNDI、EJB、电子邮件、国际化、校验和调度功能。
    Spring-Expression模块是统一表达式语言(unified EL)的扩展模块,可以查询、管理运行中的对象,同时也方便的可以调用对象方法、操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。
  • Spring-AOP: Spring-aop是Spring的另一个核心模块,在Spring中,他是以JVM的动态代理技术为基础,然后设计出了一系列的Aop横切实现,比如前置通知、返回通知、异常通知等。通过其配置管理特性,Spring AOP模块直接将面向切面的编程功能集成到了Spring框架中。所以,可以很容易地使Spring框架管理的任何对象支持AOP。

2.3官方文档

org.springframework.context.ApplicationContext接口代表 Spring IoC 容器**,负责实例化、配置和组装 bean。**

ApplicationContext的实现类通常有ClassPathXmlApplicationContextFileSystemXmlApplicationContext

三、SpringIOC容器Bean对象实例化模拟

思路:

  • 定义Bean工厂接口,提供获取bean方法
  • 定义Bean工厂接口实现类,解析配置文件,实例化Bean对象
  • 实现获取Bean方法

略…

四、Spring IOC配置文件加载

4.1spring配置文件加载

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">
    
      <bean id="userService" class="com.xp.service.UserService"/>
</beans>

4.2 使用相对路径加载配置文件

BeanFactory factory = new ClassPathXmlApplicationContext("spring.xml")

4.3 使用绝对路径加载配置文件

BeanFactory factory = new FileSystemXmlApplicationContext("C:\ideaworkspace\spring01\src\main\resources\spring.xml");

4.4 多个配置文件加载

  • 通过可边长参数 多个配置文件加载
 new ClassPathXmlApplicationContext("配置文件1","配置文件2")
  • 设置一个总配置文件,在总配置文件中导入需要加载的配置文件
<?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">
        
<import resource="spring.xml"/>
<import resource="beans.xml"/>

</beans>

五、Spring IOC容器 Bean对象实例化

5.1 构造器实例化

注:通过默认构造器创建 空构造方法必须存在 否则创建失败

默认有无参构造

如果出现有参构造,必须手动写无参构造。!!!

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

<bean id="userService" class="com.xp.service.UserService"/>

</beans>

5.2静态工厂实例化

/*
* 定义静态工厂类
* */
public class StaticFactory {
    /*
    * 定义对应的静态方法,返回实例化对象
    * */
    public static UserService createUserService() {
        return new UserService();
    }
}

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">
        
<!--静态工厂-->
<bean id="userService" class="com.xp.factory.StaticFactory" factor-method="createUserService"/>

</beans>

​ 当我们指定Spring使用静态工厂方法来创建Bean实例时,Spring将先解析配置文件,并根据配置文件指定的信息,通过反射调用静态工厂类的静态工厂方法,并将该静态工厂方法的返回值作为Bean实例,在这个过程中,Spring不再负责创建Bean实例,Bean实例是由用户提供的静态工厂方法提供的。

5.3 实例化工厂实例化

注:

  • 工厂方法为非静态方法
  • 需要配置工厂bean,并在业务bean中配置factory-bean,factory-method属性

定义工厂类

/*
 * 定义工厂类
 * */
public class InstanceFactory {
    /*
     * 定义普通方法,返回实例化的Bean对象
     * */
    public UserService createUserService() {
        return new UserService();
    }
}

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">      
<!--实例化工厂
	1.先实例化工厂类
	2.定义bean对象的bean标签,设置id属性值,指定对应工厂类及类中对应的方法

	factory-bean:对应的工厂类的bean标签的id属性值
    factory-method:工厂类中的方法
-->
    <bean id="instanceFactory" class="com.xp.factory.InstanceFactory"/>
    <bean id="userService" factory-bean="instanceFactory" factory-method="createUserService"/>
</beans>

5.4 三种实例化Bean的比较

  • 方式一:通过bean的缺省构造函数创建,当各个bean的业务逻辑相互比较独立的时候或者和外界关联较少的时候可以使用。

  • 方式二:利用静态factory方法创建,可以统一管理各个bean的创建,如各个bean在创建之前需要相同的初始化处理,则可用这个factory方法进行统一的处理等等。

  • 方式三:利用实例化factory方法创建,即将factory方法也作为了业务bean来控制,

    ​ 1.可用于集成其他框架的bean创建管理方法,

    ​ 2.能够使bean和factory的角色互换。

开发中项目一般使用一种方式实例化bean,项目开发基本采用第一种方式,交给spring托管,使用时直接拿来使用即可。另外两种了解

5.5 Spring FactoryBean和BeanFactory 区别

区别:

BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似.


 BeanFactory是个bean 工厂,是一个工厂类(接口), 它负责生产和管理bean的一个工厂
 是ioc 容器最底层的接口,是个ioc容器,是spring用来管理和装配普通bean的ioc容器(这些bean成为普通bean)。

 FactoryBean 是spirng提供的工厂bean的一个接口,FactoryBean是个bean,在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式,是一个可以生产对象和装饰对象的工厂bean,由spring管理后,生产的对象是由getObject()方法决定的

六、Spring IOC注入

手动实例化

public class UserService {

	UserDao dao = newUserDao();
}

外部引入

public class UserService {

private UserDao userDao;

public UserService(UserDao userDao){
	this.userDao = userDao;
}
}

对比发现:图二中对于UserDao对象的创建并没有像图一那样主动的去实例化,而是通过带参方法形式将UserDao传入过来,从而实现UserService对 UserDao类的依赖。
而实际创建对象的幕后对象即是交给了外部来创建

6.1Spring IOC手动装配(注入)

Spring支持的注入方式共有四种:set注入(推荐)、构造器注入、静态工厂注入、实例化工厂注入。

6.1.1 set注入
  • 属性字段需要有set方法
6.1.1.1业务对象JavaBean
public class UserService {
	//bean对象
    private UserDao userDao;
	//字符串类型
    private String address;
    //整型
    private Integer age;
    
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    
    public void setAddress(String address){
        this.address = address;
    }
    
     public void setAge(Integer age){
        this.age = age;
    }
}

spring.xml中bean标签设置property标签

 <bean id="userDao" class="com.xp.dao.UserDao"/>
    <bean id="userService" class="com.xp.service.UserService">
        <!--业务对象 注入
		name:bean对象中属性字段的名称
		ref:指定bean标签的id属性值
		value:具体的值
-->
        <property name="userDao"  ref="userDao"/>
        
        <property name="address"  value="余杭区"/>
        <property name="age" 	  value=12/>
     </bean>
6.1.2构造器注入
  • 提供有参构造
6.1.2.1 单个(多个)bean对象作为参数
	private UserDao userDao;
	/*
	*构造器注入:通过构造器的形参设置属性字段的值
	*
	*/
    public UserService(UserDao userDao,OrderDao,orderDao,StudentDao studentDao) {
        this.userDao = userDao;
    }

spring.xml

<bean id="userService" class="com.xp.service.UserService">
        <!--
		构造器注入
            通过constructor-arg标签进行注入
                name:属性名称
                ref:指定bean标签的id属性值
				index:指定顺序(对应构造器形参顺序)
        -->
        <constructor-arg name="userDao" ref="userDao" index="0"></constructor-arg>
   		<constructor-arg name="orderDao" ref="orderDao" index="1"></constructor-arg>
   		<constructor-arg name="studentDao" ref="studentDao" index="2"></constructor-arg>
     </bean>
6.1.2.2构造器注入可能会存在循环依赖问题

导致原因:两个bean对象互相注入

解决方案: 使用set注入——无须等待另一个实例化好

6.1.3 静态工厂注入

​ 静态工厂注入也是借助与set注入,只是被注入的bean对象的实例化是通过静态工厂实例化的

  • 定义静态工厂类
public class StaticFactory {
    /*
    * 定义对应的静态方法,返回实例化对象
    * */
    public static UserDao createUserDao() {
        return new UserDao();
    }
}
  • 业务类
public class UserService {
	//bean对象
    private UserDao userDao;
 
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
   
}
  • XML配置

在配置文件总设置bean标签,指定工厂对象并设置对应的方法。

<bean id="userService" class="com.xp.service.UserService">
	<property name="userDao" ref="userDao"/>
</bean>

<bean id="userDao" class="com.xp.factory.StaticFactory" factory-method="createUserDao">
</bean>
6.1.4 实例化工厂注入
  • 定义工厂类
public class InstanceFactory {
    /*
     * 定义普通方法,返回实例化的Bean对象
     * */
    public UserDao createUserDao() {
        return new UserDao();
    }

  • 业务类
public class UserService {
	//bean对象
    private UserDao userDao;
 
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
   
}
  • XML配置

声明工厂bean标签,声明bean对象,指明工厂对象和工厂方法

<bean id="userService" class="com.xp.service.UserService">
	<property name="userDao" ref="userDao"/>
</bean>

<bean id="instanceFactory" class="com.xp.factory.InstanceFactory"></bean>
<bean id="userDao" factory-bean="instanceFactory" factory-method="createUserDao">
</bean>
6.1.5注入方式选择

开发中首选set方式注入

P名称空间的使用

在配置文件spring.xml引入p名称空间

xmlns:p="http://www.springframework.org/schema/p"
<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
	p:属性名="xxx"           引入常量值
    p:属性名-ref="xxx"		  引入其他bean对象的id属性值
-->
<bean id="userDao" class="com.xp.dao.UserDao"/>
<bean id="userService" class="com.xp.service.UserService" p:userDao-ref="userDao"p:age="20"/>

</beans>

6.2Spring IOC自动装配(注入)

注解方式注入 Bean
对于bean的注入,除了使用xml配置以外,可以使用注解配置。注解的配置,可以简化配置文件,提高开发的速度,使程序看上去更简洁。对于注解的解释,Spring对于注解有专门的解释器,对定义的注解进行解析
实现对应bean对象的注入。通过反射技术实现。

6.2.1准备环境
  • 修改配置文件

    新增以下:

xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context/spring-context.xsd">
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmins:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
                           https://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
						   http://www.springframework.org/schema/context/spring-context.xsd">
  • 开启自动化注入
<context:annotation-config/>

<bean id="userDao" class="com.xp.dao.UserDao"></bean> 
<bean id="userService" class="com.xp.service.UserService"></bean>
  • 给注入的bean对象添加注解
6.2.2 @Resource 基于基于属性名称自动注入(反射) 不是spring的注解 是J2EE的一个注解

**@Resource(name = “userDao”):**name属性根据id为userDao的bean

  • 默认根据属性字段名称查找对应的 bean对象(属性字段的名称与bean标签的id属性值相等)。如果属性字段名称未找到,则会通过类型(Class类型)查找
  • 属性可以提供set方法,也可以不提供set方法
  • 注解可以声明在属性级别或set方法级别
  • 可以设置name属性,name属性值必须与bean标签的id属性值一致;如果设置了name属性值,就只会按照name属性值查找bean对象
  • 当注入接口时,如果接口只有一个实现类则正常实例化;如果接口存在多个实现类,则需要使用name属性指定需要被实例化的bean对象
6.2.3 @Autowired基于类型自动注入

@Autowired

@Qualifier(value = “orderService”)

  • 默认通过类型(Class类型)查找bean对象与属性字段的名称无关
  • 属性可以提供set方法,也可以不提供set方法
  • 注解可以声明在属性级别或set方法级别
  • 可以添加@Qualifier结合使用,通过value属性值查找bean对象((value属性值必须要设置,且值要与bean标签的id属性值对应)

七、Spring IOC扫描器

​ 作用:bean对象统一处理,简化开发配置,提高开发效率

1.设置自动化扫描的范围

​ 如果bean对象未在指定的范围,即使声明了注解,也无法实例化。

2.在需要被实例化的Bean对象上添加指定的注解( 声明在类级别,bean对象的id属性名默认是类的首字母小写)

Dao数据访问层:@Repository

Service业务逻辑层:@Service

Controller控制层:@Controller

任意类: @Component

spring.xml

   <!--开启自动扫描,设置扫描包的范围 只扫描该包下加spring注解的bean-->
    <context:component-scan base-package="com.xp.service,com.xp.factory"></context:component-scan>

八、Bean的作用域和生命周期

8.1 Bean的作用域

​ 默认情况下,我们从Spring容器中拿到的对象均是单例的,对于bean的作用域类型如下

8.1.1 singleton

在这里插入图片描述

​ 注意:

  • lazy-init 懒加载,“lazy-init=true”时代表Spring容器启动的时候不会去实例化这个bean,而是等程序调用它的时候采取实例化,默认lazy-init=fasle即Spring容器启动时就初始化。
  • 默认情况下,被管理的bean只会在IOC容器中存在一个实例,对于所有获取该bean的操作,spring容器将只会返回同一个bean。
  • 容器在启动的情况下就实例化所有singleton的bean对象,并缓存在容器中。
  • init-mtethod属性,当bean对象被实例化时调用的方法。
<bean id="userDao" class="com.xp.dao.UserDao" scope="singleton" lazy-init="true" init-method="test"></bean>
8.1.1.2 什么对象适合作为单例对象?

​ 一般来说对于无状态或状态不可改变的对象适合使用单例模式。(不存在会改变对象状态的成员变量)比如: controller层、service层、 dao层

8.1.1.3 什么是无状态或状态不可改变的对象?

​ 实际上对象状态的变化往往均是由于属性值的变化而引起的,比如user类姓名属性会有变化,属性姓名的变化一般会引起user对象状态的变化。对于我们的程序来说,无状态对象没有实例变量的存在,保证了线程的安全性,service层业务对象即是无状态对象。线程安全的。

8.1.2 prototype 原型多例

在这里插入图片描述

通过scope="prototype"设置bean的类型,每次向Spring容器请求获取Bean都返回一个全新的Bean,相对于"singleton"来说就是不缓存Bean,每次都是一个根据Bean定义创建的全新Bean。

 <bean id="userDao" class="com.xp.dao.UserDao" scope="prototype"></bean>

8.2 Bean的生命周期

在spring容器中,Bean的生命周期分为四个阶段:Bean的定义、Bean的初始化、Bean的使用、Bean的销毁

8.2.1 Bean的定义

在Spring中,通常是通过配置文件的方式来定义Bean的。

在一个配置文档中,可以定义多个bean

8.2.2 Bean的初始化

​ 默认Spring IOC容器加载时,实例化对象。

​ Spring bean初始化有两种方式:

方式一:在配置文件中通过指定init-method属性来完成。

方式二:实现org.springframework.beans.factory.InitializingBean的接口。

public class RoleService implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("init-method");
    }
}

​ 当bean被实例化时,就会调用afterPropertiesSet()方法。

​ Bean对象实例化过程是在Spring容器初始化时被实例化的,但也不是不可改变的,可以通过lazy -init="true"属性延迟bean对象的初始化操作,此时再调用getBean方法时才会进行bean的初始化操作

8.2.3 Bean的使用

方式一:使用BeanFactory

 //2.得到spring的上下文环境
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring03.xml");

方式二:使用ApplicationContext

// 1.得到spring的上下文环境
 // org.springframework.context.ApplicationContext接口代表 Spring IoC 容器,负责实例化、配置和组装 bean。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring01.xml");
8.2.4 Bean的销毁

实现销毁方式(Spring容器会维护bean对象的管理,可以指定bean对象的销毁所要执行的方法)。
步骤一︰实现销毁方式(Spring容器会维护bean对象的管理,可以指定bean对象的销毁所要执行的方法)

   <bean id="userService" class="com.xp.service.UserService" destroy-method="test"></bean>

步骤二:通过AbstractApplicationContext对象,调用其close方法实现bean的销毁过程

 //销毁bean对象
AbstractApplicationContext ac = new ClassPathXmlApplicationContext();
 ac.close();

九、Spring Task定时任务

9.1 主要内容

在这里插入图片描述

9.2 定时任务概述

​ 在项目中开发定时任务应该一种比较常见的需求,在Java中开发定时任务主要有三种解决方案:

1.是使用JDK自带的Timer

2.是使用第三方组件Quartz

3.是使用Spring Task

Timer是JDK自带的定时任务工具,其简单易用,但是对于复杂的定时规则无法满足,在实际项目开发中也很少使用到。Quartz功能强大,但是使用起来相对笨重。而Spring Task则具备前两者的优点(功能强大且简单易用),使用起来很简单,除Spring相关的包外不需要额外的包,而且支持注解和配置文件两种形式。

9.3 使用Spring Task实现定时任务

Spring Task开发定时任务有两种任务配置方式

方式一. XML配置

方拾二. 注解配置

9.3.1 使用XML配置实现定时任务

添加依赖

		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.10</version>
        </dependency>

新建配置文件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
                           https://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
						   http://www.springframework.org/schema/context/spring-context.xsd">
   <!--开启自动扫描,设置扫描包的范围 只扫描该包下加spring注解的bean-->
    <context:component-scan base-package="com.xp.xx"></context:component-scan>
</beans>

新建定时任务方法

package com.xp.job;

import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class TaskJob {
    public void job1(){
        System.out.println("任务1:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }

    public void job2(){
        System.out.println("任务2:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
}

在配置文件中添加定时任务空间

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

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

定时任务配置

   <!--定义定时任务的规则-->
    <task:scheduled-tasks>
        <task:scheduled ref="taskJob" method="job1" cron="0/2 *  * * * ?"/>
        <task:scheduled ref="taskJob" method="job2" cron="0/5 *  * * * ?"/>
    </task:scheduled-tasks>
9.3.2 使用注解方式实现定时任务

@Scheduled注解

package com.xp.job;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class TaskJob02 {
    @Scheduled(cron = "0/2 * * * * ?")
    public void job1(){
        System.out.println("任务一:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
    @Scheduled(cron = "0/5 * * * * ?")
    public void job2(){
        System.out.println("任务二:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    }
}

在xml开启配置驱动

 <!--配置定时任务驱动 开启配置,spring才能识别@Scheduled注解-->
    <task:annotation-driven/>

9.4 Cron表达式简介

关于cronExpression表达式有至少6个(也可能是7个)由空格分隔的时间元素。从左至右,这些元素的定义如下:
1.秒(0-59)

2.分钟(0-59)

3.小时(0-23)

4.月份中的日期(1-31)

5.月份(1-12或JAN-DEC)

6.星期中的日期(1-7或SUN-SAT)

7.年份(1970-2099)

表达式组成:**“秒 分 时 日 月 星期几 [年]”,**其中"年"是可选的,一般不指定。
如:"10 201835?“代表"5月3日18点20分10秒,星期几不确定”

位置时间域允许值特殊值
10-59, - * /
2分钟0-59, - * /
3小时0-23, - * /
4日期1-31, - * ? / L W
5月份1-12, - * /
6星期1-7(周日-周六), - * ? / L #
7年份(可选), - * /
9.4.1表达式符号意义
星号(*)可用在所有字段中,表示对应时间域的每一个时刻,例如,在分钟字段时,表示“每分钟”
问号(?)该字符只在日期和星期字段中使用,它通常指定为“不确定值”
减号(-)表达一个范围,如在小时字段中使用“10-12”,则表示从10到12点,即10,11,12
逗号(,)表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五
斜杠(/)x/y表达一个等步长序列,x为起始值,y为增量步长值。如在分钟字段中使用0/15,则表示为0,15,30和45秒,而5/15在分钟字段中表示5,20,35,50
井号(#)该字符只用在星期字段中,"4#2"代表第二个星期3,“5#4”代表第4个星期四
L该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。
如果L用在星期字段里,则表示星期六,等同于7
L出现在星期字段里,而且在前面有一个数值x,则表示“这个月的最后一个周x”,例如,6L表示该月的最后星期五
L在日期字段中,表示这个月份的最后一天,如一月的31号,非闰年二月的28号
W该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日
例如15W表示离该月15号最近的工作日,如果该月15号是星期六,则匹配14号星期五;如果15日是星期日,则匹配16号星期一;如果15号是星期二,那结果就是15号星期二;但必须注意关联的匹配日期不能够跨月
L W组合在日期字段可以组合使用LW,它的意思是当月的最后一个工作日
9.4.2 演示实例
表达式说明
0 0 12 * * ?每天12点运行
0 15 10 * * ?每天10:15运行
0 15 10 * * ? 2021在2021年的每天10:15运行
0 * 14 * * ?每天14点到15点之间每分钟运行一次,开始与14:00,结束语14:59
0 0/5 14 * * ?每天14点到15点之间每5分钟运行一次,开始与14:00,结束语14:59
0 0-5 15 * * ?每天15点到15:05,每分钟运行一次
0 0-5/2 15 * * ?每天15点到15:05,每2分钟运行一次
0 10,15 19 ? 4 34月每周二的19:10分和19:15分,各运行一次
0 15 10 ? * 2-6每周一、二、三、四、五的10:15分运行一次
0 15 10 L * ?每月的最后一天10:15分运行
0 15 10 ? * 6L每月的最后一个星期五10:15分运行【此时日期必须是 ?】
0 15 10 ? * 6L 2021-2023在2021,2022,2023年每月的最后一个星期五10:15分运行【此时日期必须是 ?】

十、Spring AOP

1.代理模式

​ 代理模式在Java开发中是一种比较常见的设计模式。设计目的旨在为服务类与客户类之间插入其他功能,插入的功能对于调用者是透明的,起到伪装控制的作用。如租房的例子:房客、中介、房东。对应于代理模式中即:客户类、代理类、委托类(被代理类)。

​ 为某一个对象(委托类)提供一个代理(代理类),用来控制对这个对象的访问。委托类和代理类有一个共同的父类或父接口。代理类会对请求做预处理、过滤,将请求分配给指定对象。

生活中常见的代理情况:
租房中介、婚庆公司等
代理模式的两个设计原则:

1.代理类与委托类具有相似的行为(共同)

2.代理类增强委托类的行为

常见的代理模式:

  • 静态代理
  • 动态代理

2.静态代理

​ 某个对象提供一个代理,代理角色固定,以控制对这个对象的访问。代理类和委托类有共同的父类或父接口,这样在任何使用委托类对象的地方都可以用代理对象替代。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。

2.1代理的三要素

a、有共同的行为(结婚)–接口

b、目标角色(新人)-实现行为

c、代理角色(婚庆公司)-实现行为增强目标对象行为

2.2静态代理的特点

a、有共同的行为(结婚)–接口

b、目标角色(新人)-实现行为

c、代理角色(婚庆公司)-实现行为增强目标对象行为

2.3静态代理的实现
2.3.1定义行为(共同)接口
/**
 * @author xiaopeng
 * 静态代理
 *      父类或父接口
 *      共同的行为
 */
public interface RentHouse {

    /**
     * 租房
     */
    public void toRentHouse();
}

public interface Marry {
    /**
     * 结婚
     */
    public void toMarry();
}

2.3.2 目标对象(实现行为)
/**
 * @author xiaopeng
 * 静态代理
 * 目标对象实现行为
 */
public class You implements RentHouse, Marry {
    @Override
    public void toRentHouse() {
        System.out.println("我要租一室一厅,独立厨卫 预算5k");
    }

    @Override
    public void toMarry() {
        System.out.println("想要二十辆大奔,预算50w");
    }
}

2.3.3 代理对象(实现行为、增强目标对象的行为)
package com.xp.statics;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author xiaopeng
 * 静态代理
 * 代理对象
 * 1.实现行为
 * 2.增强目标对象的行为
 */
public class AgencyProxy implements RentHouse {

    /**
     * 目标对象
     */
    private RentHouse target;
    
    /**
     * 通过构造器的形参传递目标对象
     */
    public AgencyProxy(RentHouse target) {
        this.target = target;
    }

    
    @Override
    public void toRentHouse() {
        //增强行为
        System.out.println("发布租房信息。。。" + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS'Z'").format(new Date()));

        target.toRentHouse();

        System.out.println("准备合同" + new SimpleDateFormat("dd/MM/yyyy").format(new Date()));
    }

}
package com.xp.statics;

/**
 * @author xiaopeng
 */
public class MarryCompanyProxy implements Marry{

    /**
     * 目标对象
     */
    private Marry marry;

    public MarryCompanyProxy(Marry marry) {
        this.marry = marry;
    }

    @Override
    public void toMarry() {
        System.out.println("发布寻车信息");

        marry.toMarry();

        System.out.println("准备结婚");

    }
}
2.3.4 测试
import com.xp.statics.*;

/**
 * @author xiaopeng
 */
public class Starter {
    public static void main(String[] args) {
        //目标对象
        RentHouse target = new You();

        Marry target2 = new You();

        //代理对象(传递对象)
        AgencyProxy agencyProxy = new AgencyProxy(target);

        MarryCompanyProxy companyProxy = new MarryCompanyProxy(target2);

        //通过调用代理对象中的方法,执行目标对象的行为方法
        agencyProxy.toRentHouse();
        System.out.println("==============================");
        companyProxy.toMarry();
    }
}

3.动态代理

​ 相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由Java反射机制动态产生。它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理类。代理的行为可以代理多个方法,即满足生产需要的同时又达到代码通用的目的。

动态代理的两种实现方式:

1.JDK动态代理

2.CGLIB动态代理

3.1 动态代理的特点

1.目标对象不固定

2.在应用程序执行时动态创建目标对象

3.代理对象会增强目标对象的行为

3.2 JDK动态代理

注:目标对象必须有接口实现

3.2.1 newProxyInstance

Proxy类:

​ Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下操作方法:

/*
	返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。(返回代理对象)
		loader:一个classLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
		interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果
					我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
		h:一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用
处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke方法(传入
InvocationHandler接口的子类)
*/
 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
     ...
 }



        Marry target2 = new You();

        //代理对象(传递对象)
        AgencyProxy agencyProxy = new AgencyProxy(target);

        MarryCompanyProxy companyProxy = new MarryCompanyProxy(target2);

        //通过调用代理对象中的方法,执行目标对象的行为方法
        agencyProxy.toRentHouse();
        System.out.println("==============================");
        companyProxy.toMarry();
    }
}


3.动态代理

​ 相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由Java反射机制动态产生。它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理类。代理的行为可以代理多个方法,即满足生产需要的同时又达到代码通用的目的。

动态代理的两种实现方式:

1.JDK动态代理

2.CGLIB动态代理

3.1 动态代理的特点

1.目标对象不固定

2.在应用程序执行时动态创建目标对象

3.代理对象会增强目标对象的行为

3.2 JDK动态代理

注:目标对象必须有接口实现

3.2.1 newProxyInstance

Proxy类:

​ Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下操作方法:

/*
	返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。(返回代理对象)
		loader:一个classLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
		interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果
					我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
		h:一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用
处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke方法(传入
InvocationHandler接口的子类)
*/
 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
     ...
 }
3.2.2获取代理对象
package com.xp.dynamic;

import com.xp.statics.Marry;

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

/**
 * @author xiaopeng
 */
public class JdkProxy {

    //目标对象
    private Object target;
    //通过带参构造传递目标对象
    public JdkProxy(Object target) {
        this.target = target;
    }

    /**
     * 得到代理对象
     */
    public Object getProxy() {
        /**
         * 通过调用Proxy代理类中的静态方法newProxyInstance() 得到代理对象
         *
         *  返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。(返回代理对象)
         * 		loader:一个classLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
         * 		interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果
         * 					我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
         * 		h:一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用
         * 处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke方法(传入
         * InvocationHandler接口的子类)
         *
         */

        //定义了由哪个ClassLoader对象对生成的代理对象进行加载
        ClassLoader classLoader = this.getClass().getClassLoader();
        //需要代理的对象提供一组什么接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        //一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口
        InvocationHandler invocationHandler = new InvocationHandler() {
            //当代理对象调用方法时,invoke方法就会被执行一次
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("方法执行前.......");
                //调用目标对象中的方法  通过反射中提供的invoke方法 调用指定对象的指定方法
                /**
                 *  反射中 invoke、
                 *      方法名.invoke(对象,方法所需参数)
                 */
                Object invoke = method.invoke(target, args);
                System.out.println("方法执行后.......");
                return invoke;
            }
        };
        //调用方法 得到代理对象
        Object object = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return object;
    }
}

3.2.3 通过代理对象实现目标对象的功能
package com.xp.dynamic;

import com.xp.dynamic.JdkProxy;
import com.xp.statics.Marry;
import com.xp.statics.RentHouse;
import com.xp.statics.You;

/**
 * @ClassName com.xp.dynamic.Starter02
 * @Description TODO
 * @Author XP
 * @Date 2021/10/22 14:16
 **/
public class Starter02 {
    public static void main(String[] args) {
        System.out.println("===============结婚===================");

        //目标对象
        Marry target = new You();
        //得到代理类
        JdkProxy jdkProxy = new JdkProxy(target);
        //得到代理对象
        Marry proxy = (Marry) jdkProxy.getProxy();
        //通过代理对象调用方法
//        proxy.toMarry();
        proxy.toMarry2("李思思");

        System.out.println("===============租房===================");

        //目标对象
        RentHouse target2 = new You();
        //得到代理类
        JdkProxy jdkProxy2 = new JdkProxy(target2);
        //得到代理对象
        RentHouse proxy2 = (RentHouse) jdkProxy2.getProxy();
        //通过代理对象调用方法
        proxy2.toRentHouse();
    }
}
3.3 CGLIB动态代理

​ JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能使用JDK的动态代理,cglib是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。

3.3.1 添加依赖

在pom.xml文件中引入cglib相关依赖

		<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>
3.3.2 生成代理类
package com.xp.dynamic;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @ClassName Cglib
 * @Description Cglib 动态代理
 * 继承思想
 * 创建一个代理类,代理类是目标类的子类,代理类对目标类中的方法进行重写。
 * @Author ZJ
 * @Date 2021/10/25 11:18
 **/
public class CglibProxy {
    /**
     * 目标对象
     */
    private Object target;

    /**
     * 通过有参构造传递目标对象
     */
    public CglibProxy(Object target) {
        this.target = target;
    }

    /**
     * 得到代理对象
     *
     * @return
     */
    public Object getProxy() {
        //通过Enhancer对象的create()方法可以生成一个类,用于生成代理对象
        Enhancer enhancer = new Enhancer();
        //设置代理类的父类
        enhancer.setSuperclass(target.getClass());
        //
        MethodInterceptor interceptor = new MethodInterceptor() {
            /**
             * 代理过程
             * 当代理对象调用方法时,intercept方法就会被执行
             * @param o 由cglib动态代理生成的代理类实例
             * @param method 目标方法
             * @param objects 方法所需的参数
             * @param methodProxy 代理对象对方法的引用
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

                System.out.println("方法执行前===================");
                //调用目标对象的方法
                /**
                 *  反射中 invoke、
                 *      方法名.invoke(对象,方法所需参数)
                 */
                Object result = method.invoke(target, objects);

                System.out.println("方法执行后===================");

                return null;
            }
        };
        //设置代理过程(调用目标对象的方法、增强用户行为)
        enhancer.setCallback(interceptor);

        //生成一个代理类
        return enhancer.create();
    }
}
3.3.3 测试
import com.xp.dynamic.CglibProxy;
import com.xp.model.Marry;
import com.xp.model.User;
import com.xp.model.You;

/**
 * @ClassName CglibProxyTest
 * @Description TODO
 * @Author xiaopeng
 * @Date 2021/10/25 11:41
 **/
public class CglibProxyTest {

    public static void main(String[] args) {
        //目标对象
        Marry target = new You();
        //得到代理类
        CglibProxy cglibProxy = new CglibProxy(target);
        //得到代理对象
        Marry proxy = (Marry) cglibProxy.getProxy();
        //调用方法
        proxy.toMarry();
        proxy.toMarry2("xp");


        /**
         * 没有接口实现的目标类
         */
        User user = new User();
        CglibProxy cglibProxy1 = new CglibProxy(user);
        User proxy1 =(User) cglibProxy1.getProxy();
        proxy1.goHome();

    }
}

目标类有接口可以用JDK动态代理和CGLIB动态代理,目标类没有接口只能用CGLIB动态代理。

4.Spring AOP

需要对以下功能进行日志记录,事务处理
在这里插入图片描述
假如没有AOP,在做日志和事务处理的时候,我们会在每个方法中添加相关处理
在这里插入图片描述
但大多数的代码是相同的,为了实现代码复用,我们可能把日志和事务处理抽离成一个新的方法。但是这样我们仍然必须手动插入这些方法。
在这里插入图片描述
但这样两个方法就是强耦合的,假如此时我们不需要这个功能了,或者想换成其他功能,那么就必须一个个修改。
通过代理模式,可以在指定位置执行对应流程。这样就可以将一些横向的功能抽离出来形成一个独立的模块,然后在指定位置插入这些功能。这样的思想,被称为面向切面编程,即AOP。
在这里插入图片描述

4.1 日志处理带来的问题

​ 按照之前方法每一模块都加日志

​ 缺点:代码重复太多,添加的日志代码耦合度太高(如果需要更改日志记录代码功能需求,类中方法需要全部改动,工程量浩大)

4.1.1使用装饰器模式/代理模式改进解决方案

装饰器模式:动态地给一个对象添加一些额外的职责。

​ 代理模式:以上刚讲过。

​ …

​ 仔细考虑过后发现虽然对原有内部代码没有进行改动,对于每个类做日志处理,并引用目标类,但是如果待添加日志的业务类的数量很多,此时手动为每个业务类实现一个装饰器或创建对应的代理类,同时代码的耦合度也加大,需求一旦改变,改动的工程量也是可想而知的。

​ 有没有更好的解决方案,只要写一次代码,对想要添加日志记录的地方能够实现代码的复用,达到松耦合的同时,又能够完美完成功能?

4.2 什么是AOP?

​ Aspect Oriented Programing 面向切面编,相比较oop面向对象编程来说,Aop关注的不再是程序代码中某个类,某些方法,而aop考虑的更多的是一种面到面的切入,即层与层之间的一种切入,所以称之为切面。联想大家吃的汉堡(中间夹肉)。那么aop是怎么做到拦截整个面的功能呢?考虑前面学到的servlet filter /* 的配置,实际上也是aop的实现思想。

4.3 AOP能做什么?

AOP主要应用于日志记录,性能统计,安全控制,事务处理等方面,实现公共功能性的重复使用。

4.4 AOP的特点

1.降低模块与模块之间的耦合度,提高业务代码的聚合度。(高内聚低耦合)

⒉提高了代码的复用性。

3.提高系统的扩展性。(高版本兼容低版本)

4.可以在不影响原有的功能基础上添加新的功能

4.4.1 AOP底层实现

​ 动态代理(JDK+CGLIB)

4.5 AOP基本概念
  • JoinPoint(连接点)

被拦截到的每个点,spring中指被拦截到的每一个方法,spring aop一个连接点即代表一个方法的执行。

  • PointCut(切入点)

对连接点进行拦截的定义(匹配规则定义规定拦截哪些方法,对哪些方法进行处理),spring有专门的表达式语言定义。

  • Advice(通知)

拦截到每一个连接点(方法)需要做的操作

	1.前置通知(前置增强)        -before()       执行方法前
	2.返回通知(返回增强)        -afterReturn()  方法正常结束返回后的通知
	3.异常抛出通知(异常抛出增强) -afterThrow()
	4.最终通知  -after 无论方法是否出现异常,均会执行通知
	5.环绕通知 -aroud 包围一个连接点的通知,如方法调用。这是最强大的一种通知类型,环绕通知可以在方法调用前后完成自定义的行为,也可以选择是否继续执行连接点或返回其返回值或者抛出异常结束执行
  • Aspect(切面=切入点+通知)

    切入点与通知的结合,决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面则是横切关注点抽象。

  • Target(目标对象)

    被代理的目标对象

  • Weave(织入)

    将切面应用到目标对象并生成代理对象的过程即为织入

  • Introduction(引入)

    在不修改程序原有代码的情况下,在程序运行期间为类动态的添加方法或者字段的过程即为引入

5.AOP实现

5.1 环境搭建
5.1.1 依赖引入
  <!-- spring框架核心依赖 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.10</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.1</version>
        </dependency>
5.1.2 添加命名空间
       xmlns:aop="http://www.springframework.org/schema/aop"
       
          http://www.springframework.org/schema/aop
          http://www.springframework.org/schema/aop/spring-aop.xsd
5.2 注解实现
5.2.1定义切面类
package com.xp.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @ClassName LogCut
 * @Description 定义切面
 * 切面是切入点和通知的结合
 * 切入点:定义拦截规则,即要拦截哪些类的哪些方法
 * 通知:拦截方法后要做什么
 * @Author xiaopeng
 * @Date 2021/10/26 9:52
 **/
@Component //将类交给IOC容器实例化
@Aspect //声明当前类是一个切面类
public class LogCut {

    /**
     * 定义切入点
     * 通过
     * @Pointcut(定义规则):
     *
     * @Pointcut("execution(* com.xp.service.*.*(..))")   修饰符 包名.类名.方法名(..)
     * 1.执行任意公共方法:
     * execution( public * ( .. ) )
     * 2.执行任意的set方法
     * execution( * set* ( .. ) )
     * 3.执行com . xxxx.service包下任意类的任意方法
     * execution( * com. xxxx.service.* .*( ..) )
     * 4.执行com . xx×x.service包以及子包下任意类的任意方法
     * execution( * com. xx×x.service . .* .*( ..) )
     * <p>
     * 注:表达式中的第一个* 代表的是方法的修饰范围
     * 可选值: private、protected、 public (*表示所有范围)
     * 包名.类名.方法名(..) 任意参数
     */
    @Pointcut("execution(* com.xp.service.*.*(..))")
    public void cut() {
    }

/**
     * 前置通知 应用在指定切入点上
     * 在目标方法前执行
     */

    @Before(value = "cut()")
    public void before() {
        System.out.println("========前置通知=======");
    }


/**
     * 返回通知
     * 在目标方法 无异常返回 后执行
     */

    @AfterReturning(value = "cut()")
    public void afterReturn() {
        System.out.println("=========无异常返回通知=======");
    }


/**
     * 最终通知
     *      目标方法不管有没有异常都会执行
     */

    @After(value = "cut()")
    public void after() {
        System.out.println("======最终通知:不管有没有异常都会执行========");
    }


/**
     * 异常通知
     */

    @AfterThrowing(value = "cut()",throwing = "e")
    public void afterThrow(Exception e) {
        System.out.println("=======异常通知======异常原因:"+e.getMessage()+"========");
    }


    /**
     * 环绕通知
     * 目标方法执行前后 通过环绕通知定义相应处理
     * 需要显示的调用对应的方法,否则无法访问指定方法  pjp.proceed()
     */
    @Around(value = "cut()")
    public Object around(ProceedingJoinPoint pjp) {
        System.out.println("环绕通知===前置通知");
        Object result = null;
        try {
            //需要显示的调用对应的方法,否则无法访问指定方法
            result = pjp.proceed();
            //打印目标对象和目标方法
            System.out.println(pjp.getTarget() + "===" + pjp.getSignature());
            System.out.println("环绕通知===返回通知");
        } catch (Exception e){
            e.printStackTrace();
            System.out.println("环绕通知===异常通知");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        } finally {
            System.out.println("环绕通知===最终通知");
        }
        return result;
    }

}

5.2.2 配置文件-springxml
  <!--开启自动扫描,设置扫描包的范围 只扫描该包下加spring注解的bean-->
    <context:component-scan base-package="com.xp"></context:component-scan>
    <!--aop注解实现:配置AOP自动代理-->
    <aop:aspectj-autoproxy/>
5.3 XML实现
5.3.1 定义切面
package com.xp.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @ClassName LogCut
 * @Description 定义切面
 * 切面是切入点和通知的结合
 * 切入点:定义拦截规则,即要拦截哪些类的哪些方法
 * 通知:拦截方法后要做什么
 * @Author xiaopeng
 * @Date 2021/10/26 14:15
 **/
@Component //将类交给IOC容器实例化
public class LogCut2 {

    /**
     * 定义切入点
     * 通过
     * @Pointcut(定义规则):
     *
     * @Pointcut("execution(* com.xp.service.*.*(..))")   修饰符 包名.类名.方法名(..)
     * 1.执行任意公共方法:
     * execution( public * ( .. ) )
     * 2.执行任意的set方法
     * execution( * set* ( .. ) )
     * 3.执行com . xxxx.service包下任意类的任意方法
     * execution( * com. xxxx.service.* .*( ..) )
     * 4.执行com . xx×x.service包以及子包下任意类的任意方法
     * execution( * com. xx×x.service . .* .*( ..) )
     * <p>
     * 注:表达式中的第一个* 代表的是方法的修饰范围
     * 可选值: private、protected、 public (*表示所有范围)
     * 包名.类名.方法名(..) 任意参数
     */
    public void cut() {
    }

/**
     * 前置通知 应用在指定切入点上
     * 在目标方法前执行
     */
    public void before() {
//        System.out.println("===2=====前置通知====2===");
    }


/**
     * 返回通知
     * 在目标方法 无异常返回 后执行
     */
    public void afterReturn() {
//        System.out.println("====2=====无异常返回通知===2====");
    }


/**
     * 最终通知
     *      目标方法不管有没有异常都会执行
     */
    public void after() {
//        System.out.println("====2==最终通知:不管有没有异常都会执行===2=====");
    }
/**
     * 异常通知
     */
    public void afterThrow(Exception e) {
//        System.out.println("==2=====异常通知====2==异常原因:"+e.getMessage()+"========");
    }
    /**
     * 环绕通知
     * 目标方法执行前后 通过环绕通知定义相应处理
     * 需要显示的调用对应的方法,否则无法访问指定方法  pjp.proceed()
     */
    public Object around(ProceedingJoinPoint pjp) {
        System.out.println("环绕通知=2==前置通知");
        Object result = null;
        try {
            //需要显示的调用对应的方法,否则无法访问指定方法
            result = pjp.proceed();
            //打印目标对象和目标方法
            System.out.println(pjp.getTarget() + "===" + pjp.getSignature());
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            System.out.println("环绕通知=2==异常通知");
        }
        System.out.println("环绕通知==2=最终通知");
        return result;
    }

}

5.3.2 配置文件-spring.xml
<!--aop相关配置-->
    <aop:config>
        <!--aop切面-->
        <aop:aspect ref="logCut2">
            <!--定义aop切入点-->
            <aop:pointcut id="cut" expression="execution(* com.xp.service.*.*(..))"/>
            <!--配置前置通知 指定前置通知方法 并引入切入点定义-->
            <aop:before method="before" pointcut-ref="cut"/>
            <!--配置返回通知 指定返回通知方法 并引入切入点定义-->
            <aop:after-returning method="afterReturn" pointcut-ref="cut"/>
            <!--配置异常通知 指定异常通知方法 并引入切入点定义-->
            <aop:after-throwing method="afterThrow" throwing="e" pointcut-ref="cut"/>
            <!--配置最终通知 指定前置通知方法 并引入切入点定义-->
            <aop:after method="after" pointcut-ref="cut"/>
            <!--配置环绕通知 指定前置通知方法 并引入切入点定义-->
            <aop:around method="around" pointcut-ref="cut"/>
        </aop:aspect>
    </aop:config>

6.AOP总结

6.1代理模式实现三要素

.接口定义

.目标对象与代理对象必须实现统一接口

.代理对象持有目标对象的引用增强目标对象行为

6.2 代理模式实现分类以及对应区别

.静态代理:手动为目标对象制作代理对象,即在程序编译阶段完成代理对象的创建.动态代理:在程序运行期动态创建目标对象对应代理对象。

. jdk动态代理:被代理目标对象必须实现某一或某一组接口实现方式通过回调创建代理对象。

. cglib动态代理:被代理目标对象可以不必实现接口,继承的方式实现。

动态代理相比较静态代理,提高开发效率,可以批量化创建代理,提高代码复用率。

6.3 AOP理解
  • 面向切面,相比oop关注的是代码中的层或面
  • 解耦,提高系统扩展性
  • 提高代码复用
6.4 AOP关键字
  • 连接点:每一个方法
  • 切入点:匹配的方法集合
  • 切面:连接点与切入点的集合决定了切面,横切关注点的抽象
  • 通知:几种通知
  • 目标对象:被代理对象
  • 织入:程序运行期将切面应用到目标对象并生成代理对象的过程
  • 引入:在不修改原始代码情况下,在程序运行期为程序动态引入方法或字段的过程

十一、Spring事务

10.1 事务传播行为

@Transactional(propagation=Propagation.REQUIRED)
如果有事务,那么加入事务,没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation. MANDATORY)
必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation. NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS)
如果其他 bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务。
@Transactional(propagation=Propagation. NESTED)
支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。

当涉及到事务嵌套(Service调用Service)时,可以设置:
.SUPPORTS=不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
·REQUIRED =不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。(默认值)(适合增删改)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值