栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring笔记

Spring

1、Spring 概述

1.1、什么是Spring1.2、Spring优点 2、 IOC 控制反转

2.1、概念

2.1.1、IOC 分为控制和反转2.1.2、IOC 的技术实现2.1.3、Spring 框架是使用 DI 实现 IOC 2.2、创建容器对象

2.2.1、pom.xml2.2.2、Spring 配置文件2.2.3、创建spring容器对象

获取容器中对象信息:spring创建非自定义类的对象 2.3、DI:给属性赋值

2.3.1、基于 xml 的DI

2.3.1.1、set 注入2.3.1.2、构造注入2.3.1.3、引用类型属性自动注入

byName(按名称注入)byType(按类型注入) 2.3.1.4、使用多个Spring配置文件 2.3.2、基于注解的DI

2.3.2.1、定义bean的 @Component2.3.2.2、简单类型注入的 @Value2.3.2.3、自动注入的 @Autowired

@Autowired 属性 required使用 byType 自动注入使用 byName 自动注入 @Qualifier 2.3.2.4、JDK注解 @Resource 自动注入2.3.2.5、IOC 总结 3、AOP 面向切面编程

3.1、AOP概念

3.1.1、什么是 AOP ?3.1.2、AOP 中的术语: 3.2、使用AspectJ框架实现 AOP

3.2.1、通知分类3.2.2、PointCut 位置(切入点表达式)3.2.3、前置通知@Before3.2.4、后置通知@AfterReturning3.2.5、环绕通知@Around3.2.6、异常通知@AfterThrowing3.2.7、最终通知@After3.2.8、@Pointcut 定义切入点 3.3、总结3.4、练习 4、Spring 集成 MyBatis

4.1、集成思路4.2、集成步骤

4.2.1、pom.xm文件4.2.2、Dao 接口和 mapper 文件4.2.3、MyBatis 主配置文件4.2.4、service实现类4.2.5、Spring 配置文件★★★★★★ 5、Spring事务

5.1、事务概念

Spring统一管理事务概念 5.2、事务管理器接口

事务管理器接口:PlatformTransactionManager事务管理器工作方式:事务提交和事务回滚的时机:事务底层实现: 5.3、事务定义接口

5.3.1、事务隔离级别5.3.2、超时时限5.3.3、传播行为 5.4、程序举例(环境搭建)

5.4.1、pom.xml5.4.2、实体类5.4.3、dao接口与mapper5.4.4、mybatis主配置文件5.4.5、service接口的实现类5.4.6、spring配置文件 5.5、注解方式@Transactional

修改 spring 配置文件:添加 @TRansactional 注解: 5.6、xml配置文件方式 6、Spring 与 Web

6.1、环境配置

pom.xml实体类 Studentdao接口和mapper文件mybatis 主配置文件service接口和实现类servlet 接收请求参数 6.2、现在容器对象的问题6.3、ContextLoaderListener 监听器6.4、监听器源代码6.5、使用ContextLoaderListener监听器

1、Spring 概述 1.1、什么是Spring

spring 就是一个java框架,使用java语言开发的、轻量级的、开源的框架,可以在 j2se、j2ee项目中都可以使用

Spring核心技术:IOC、AOP

Spring又叫做:容器。spring作为容器,装载的是java对象,可以让spring创建java对象,给属性赋值

Spring作用:实现解耦合,解决java对象之间的耦合,解决模块之间的耦合

Spring文档地址:https://spring.io

Tomcat 也是容器,管理的是 servlet、listener、filter 等对象(创建HelloServlet类,写web.xml)

Spring管理的是上面以外的对象,写配置文件

Spring 的工作方式:

1.2、Spring优点

Spring 是一个框架、半成品软件,是一个容器管理对象,容器装的是对象,Spring 是存储对象的容器

优点:

    轻量针对接口编程,解耦合AOP 编程的支持方便集成各种优秀框架
2、 IOC 控制反转 2.1、概念

IOC ,Inversion Of Control:控制反转,是一个理论,一个指导思想,指导开发人员如何使用对象、管理对象的,把对象的创建、属性赋值、对象的生命周期都交给代码之外的容器管理

2.1.1、IOC 分为控制和反转

IOC 分为控制和反转

​ 控制:对象创建、属性赋值、对象生命周期管理

​ 反转:把开发人员管理对象的权限转移给代码之外的容器实现,由容器完成对象的管理

​ 正转:开发人员在代码中,使用 new 构造方法创建对象。开发人员掌握对象的创建、属性赋值、对象从开始到销毁的全部过程,开发人员对对象全部控制

通过容器,可以使用容器中的对象(容器已经创建对象、对象属性已赋值、对象也组装好了)

Spring 就是一个日期,可以管理对象、创建对象、给属性赋值

2.1.2、IOC 的技术实现

IOC 的技术实现

DI (依赖注入):Dependency Injection,是 IOC 的一种技术实现。程序只需要提供要使用的对象的名称就可以了,对象如何创建、如何从容器中查找与获取 都由容器内部自己实现

依赖: 比如 Class A 使用了 Class B 的属性或方法,叫做 Class A 依赖 Class B

public class B{
	public void createOrder(){}
}
public class A{
    private ClassB b = new ClassB();
    public void buy(){
        b.createOrder();
    }
}
执行Class A的buy()
    ClassA a = new ClassA();
	a.buy();
2.1.3、Spring 框架是使用 DI 实现 IOC

通过 Spring 框架,只需要提供要使用的对象名称就可以了,从容器中获取名称对应的对象

Spring 底层使用的是反射机制,通过反射创建对象,并属性赋值

2.2、创建容器对象

设置字符集 UTF-8

使用 Spring:Spring 作为容器管理对象,开发人员从 Spring 中获取要使用的对象

实现步骤:

    新建 maven 项目

    加入依赖,修改 pom.xml

    spring-context :Spring依赖

    junit :单元测试

    定义类:接口、实现类

    类也可以没有接口

    接口、实现类:和没有Spring一样定义

    创建 Spring 的配置文件,用于声明对象

    把对象交给Spring创建和管理

    使用标签表示对象声明,一个 bean 表示一个 java 对象

    使用容器中的对象

    创建一个表示 Spring 容器的对象 ApplicationContext

    从容器中,根据名称获取对象,使用 getBean("对象名称")

2.2.1、pom.xml

    UTF-8
    1.8
    1.8



    
        org.springframework
        spring-context
        5.2.5.RELEASE
    
    
        junit
        junit
        4.11
    

2.2.2、Spring 配置文件



    
    

Spring标准的配置文件:

    根标签是 beansbeans 后面是约束文件说明beans 里面是 bean 声明什么是bean: bean就是java对象,Spring容器管理的java对象,叫做bean
2.2.3、创建spring容器对象

容器对象特点:

    容器对象 ApplicationContext :接口

    通过 ApplicationContext 对象,获取要使用的 java 对象,执行 getBean(“id”)

    Spring 默认是调用类的无参构造器创建对象

    Spring 读取配置文件,一次创建好所有的 java 对象,都放到 map 中

    Object object = this.factoryBeanObjectCache.get(beanName);

// 1、指定spring配置文件:从类路径(classpath)之下开始的路径
String config="beans.xml";
// 2、创建容器对象,ApplicationContext 表示spring容器对象,通过applicationContext获取某个java对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
// 3、从容器中获取指定名称的悐,使用 getBean("id")
SomeServiceImpl service = (SomeServiceImpl) applicationContext.getBean("someService");
// 4、调用实现类对象的方法,接口中的方法
service.doSome();

    spring创建对象,调用类的哪个方法?

    Spring默认使用无参构造方法创建对象

    spring在什么时候创建的对象?

    创建 Spring 容器对象的时候,会读取配置文件,创建文件中声明的java对象

    所以在new ClassPathXmlApplicationContext(config)的时候调用无参构造器

    速度快,但是耗费占用内存

    spring一次创建几个对象?

    全部创建。

    在创建容器对象(ApplicationContext)时,会把配置文件中所有对象都创建处理(Spring默认规则)

获取容器中对象信息:

获取容器中定义对象的数量: getBeanDefinitionCount()

获取容器中定义对象的名称(bean标签的id属性值):getBeanDefinitionNames()

配置文件:


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

    //获取容器中定义对象的数量
    int nums = appc.getBeanDefinitionCount();
    System.out.println("容器中定义对象的数量==" + nums);

    //获取容器中定义对象的名称(bean标签的id属性值)
    String[] names = appc.getBeanDefinitionNames();
    for (String name : names) {
        System.out.println("容器中定义对象的名称==" + name);
    }
}
spring创建非自定义类的对象

配置文件:


创建对象:

@Test
public void test2(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");
    // 让Spring创建非自定义类的对象,就是有Class就能让Spring创建对象
    Date date = (Date) appc.getBean("mydate");
    System.out.println(date);
}
2.3、DI:给属性赋值

Spring 调用类的无参构造器创建对象,对象创建后给属性赋值

属性的赋值有两个方式:1、xml 配置文件中的标签和属性;2、使用注解

DI 分类:1、set 注入,也叫做设值注入;2、构造注入

2.3.1、基于 xml 的DI

在 xml 配置文件中使用标签和属性,完成对象创建和属性赋值

2.3.1.1、set 注入

set 注入,也叫做设值注入

概念 :Spring调用类中的set方法,在set方法中可以完成属性赋值。(推荐使用)

使用 标签

    简单类型:java中的基本数据类型和String

    简单类型的设值注入,使用value
    
         
         
    
    

    引用类型:自定义类对象作为属性

    引用类型的set注入,使用ref
    
         
         
         
    
    
         
    
    
2.3.1.2、构造注入

概念:Spring调用类中的有参构造器,在创建对象的同时,给属性赋值

使用标签

    使用 name 属性

    
         
         
        
    
    

    使用 index 属性

    
        
        
        
    
    

    也可以省略 index

    
        
        
        
    
    
2.3.1.3、引用类型属性自动注入

概念:Spring可以根据某些规则给引用类型完成赋值,只对引用类型有效

byName(按名称注入)

byName(按名称注入):java类中引用类型属性名称和Spring容器中bean 的 id 属性值一样,且数据类型也是一样的,这样bean能够赋值给引用类型

示例:

java对象:

@Data
public class Student {
    private String name;
    private Integer age;
    private School myschool;
}

配置文件:


    
     




    

byType(按类型注入)

byType(按类型注入):java类中引用类型属性的数据类型和 Spring容器中bean 的 class 属性值是同源关系,这样bean能够赋值给引用类型

注意: 在配置文件中,符合条件的对象只能有一个,多余一个都会报错

同源关系:

    引用类型属性和bean的class值 数据类型是一样的引用类型属性和bean的class值 数据类型是父子类关系引用类型属性和bean的class值 数据类型是借口和实现类关系

示例:

java对象:

@Data
public class Student {
    private String name;
    private Integer age;
    private School myschool;
}

配置文件:


    
     




    

2.3.1.4、使用多个Spring配置文件

分多个配置文件方式:

    按功能模块分,一个模块一个配置文件按类的功能分,数据库操作相关的类在一个文件,service类在一个配置文件,redis、事务等一个配置文件

Spring管理多个配置文件:

​ 常用的是包含关系的配置文件。项目中有一个总的问文件,里面用 import 标签指向包含的其他多个配置文件

总的文件



关键字"claspath":
	表示类路径,也就是类文件(class文件)所在的目录。Spring到类路径中加载文件
什么时候使用classpath:
	在一个文件中要使用其他文件,需要使用classpath

示例:

总的文件beans.xml,包含其他多个配置文件,一般不声明bean



通配符

包含关系的配置文件,可使用通配符 * ,表示任意字符

注意:总的文件名称,不能包含在通配符范围内(beans.xml不能是spring-beans.xml)

示例: 总的文件beans.xml

可使用以下一行代码指代上面例子中的两行代码,使用通配符 * 替代了后面所有字符


2.3.2、基于注解的DI

基于注解的DI:使用 Spring 提供的注解,完成 java 对象创建、属性赋值

2.3.2.1、定义bean的 @Component

@Component:表示创建对象,对象放到容器中,作用是

使用步骤:

    在源代码加入注解 @Component

    import lombok.Data;
    import org.springframework.stereotype.Component;
    
    //使用value指定对象的名称
    //@Component(value = "mystudent")
    //可以省略value,默认就是首字母小写的类名
    @Component
    @Data
    public class Student {
        private String name;
        private Integer age;
    }
    

    在 Spring 的配置文件,加入注解扫描器标签

    
    

    context:exclude-filter 标签用于设置哪些内容不进行扫描

    
        
    
    

扫描多个包的三种方式:










2.3.2.2、简单类型注入的 @Value

简单类型属性赋值: @Value

示例:

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@Data
public class Student {
    
    @Value(value = "小草莓")
    private String name;
    private Integer age;
}

@Value 使用外部属性配置文件

    创建外部属性配置文件 resources/myconf.properties

    myname=小草莓
    myage=18
    

    在 Spring 的配置文件,加入外部属性配置文件指定标签

    
    

    在源代码加入注解

    @Component
    @Data
    public class Student {
    
        //使用外部属性文件中的数据,@Value("${key名}")
        @Value("${myname}")
        private String name;
        private Integer age;
    }
    
2.3.2.3、自动注入的 @Autowired @Autowired 属性 required

boolean类型的属性,默认为true

true 表示若引用类型属性赋值失败,终止程序执行,并报错

false 表示若引用类型属性赋值失败,程序正常执行,引用类型属性值为 null

@Component
@Data
public class Student {
    //使用外部属性文件中的数据,@Value("${key名}")
    @Value("${myname}")
    private String name;
    private Integer age;
    
    @Autowired(required = false)
    @Qualifier("aaaa")
    private School school;
}

测试:

@Test
public void test1(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");
    Student student = (Student) appc.getBean("student");
    System.out.println(student);
}

输出结果:

Student(name=小草莓, age=null, school=null)
使用 byType 自动注入

注意:

当一个接口有多个实现类的时候,使用 @Autowired 进行类型自动注入就会报错

自定义类:

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component(value = "myschool")
public class School {
    @Value(value = "家里蹲")
    private String schoolName;
}

使用自动注入,引用类型属性

import com.domain02.School;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@Data
public class Student {
    @Value("${myname}")
    private String name;
    private Integer age;
    
    //默认使用byType
    @Autowired
    private School school;
}

测试:

@Test
public void test1(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("beans.xml");
    Student student = (Student) appc.getBean("student");
    System.out.println(student);
}

结果:

Student(name=小草莓, age=null, school=School(schoolName=家里蹲))
使用 byName 自动注入 @Qualifier

自定义类:

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component(value = "myschool")
public class School {
    @Value(value = "家里蹲")
    private String schoolName;
}

使用自动注入,引用类型属性

import com.domain02.School;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@Data
public class Student {
    @Value("${myname}")
    private String name;
    private Integer age;
    
    //使用byName
    @Autowired
    @Qualifier("myschool")
    private School school;
}
2.3.2.4、JDK注解 @Resource 自动注入

注意: JDK高于1.8的就没有这个注解,需要加入依赖


    javax.annotation
    javax.annotation-api
    1.3.2

默认先使用 byName 赋值,如果赋值失败,再使用 byType

@Component
@Data
public class Student {
    @Value(value = "小草莓")
    private String name;
    private Integer age;
    
    //默认使用 byName 自动注入
    //先使用 byName 赋值,如果赋值失败,再使用 byType
    @Resource
    private School school;
}

如果只想使用 byName 赋值,使用注解属性 name="bean的id"

    @Resource(name="myschool")
    private School school;
2.3.2.5、IOC 总结

IOC :管理对象的,把对象放到容器中,创建、赋值、管理依赖关系

通过管理对象,实现解耦合,IOC 解决处理业务逻辑对象之间的耦合关系,也就是 servlet 与 dao 之间的解耦合

Spring 作为容器适合管理什么对象?

    service 对象、dao 对象工具类对象

不适合交给 Spring 的对象?

    实体类servlet、listener、filter 等web中的对象,这些是Tomcat创建和管理的
3、AOP 面向切面编程

在编写好程序之后,又有需求对编写好的代码上进行功能的增加

在源代码中、业务方法中增加功能,可能导致的问题:

    源代码可能改动的比较多重复代码多代码难于维护

现在想要实现在不更改原来编写的源代码的情况下,增加新的功能

AOP 能完成代理的功能,在保证核心的源代码不动的情况下,增加其他功能,并调用核心的源代码

public class ServiceProxy implements UserService {
    //真正的目标
    UserService userService = new UserServiceImpl();
    @Override
    public void addUser() {
        System.out.println("日志功能:记录方法执行时间" + new Date());
        userService.addUser();
        System.out.println("事务功能:业务方法之后,提交事务");
    }
}
3.1、AOP概念 3.1.1、什么是 AOP ?
    AOP(Aspect Orient Programming):面向切面编Aspect:表示切面,给业务方法增加的功能,叫做切面。切面一般都是非业务功能,而且切面功能一般都是可以复用的。例如,日志功能、事务功能、权限检查、参数检查、统计信息等Orient:面向Programming:编程

怎么理解面向切面编程?

    就是以切面为核心设计开发你的应用设计项目时,找出切面的功能安排切面的执行时间,执行的位置

AOP 的作用:

    让切面功能复用让开发人员专注业务逻辑,提高开发效率实现业务功能和其他非业务功能解耦合给存在的业务方法增加功能,不用修改原来的代码
3.1.2、AOP 中的术语:
    Aspect:切面。给业务方法增加的功能JoinPoint:连接点。连接切面的业务方法,在这嘎业务方法执行时,会同时执行切面的功能PointCut:切入点。是一个或多个连接点集合,表示这些方法执行时,都能增加切面的功能,表示切面执行的位置target:目标对象。给哪个对象增加切面的功能,这个对象就是目标对象Advice:通知(增强)。表示切面的执行时间,在目标方法之前执行切面,还是目标方法之后执行切面

AOP 中重要三个要素:Aspect、PointCut、Advice。

理解:在 Advice 的时间,在 PointCut 的位置,执行 Aspect

AOP 是一个动态的思想,在程序运行期间,创建代理(ServiceProxy),使用代理执行法时,增加切面的功能,这个代理对象是存在内存中的

什么时候用 AOP ?

    当你要给某些非法增加相同的一些功能时,源代码不能改给业务方法增加非业务功能,也可用 AOP

AOP 技术思想的实现

使用框架实现 AOP,实现 AOP 的框架有很多,有名的是以下两个:

    Spring:spring框架实现 AOP 思想中的部分功能,比较繁琐Aspectj:独立的框架,专门作用于 AOP,属于 Eclipse
3.2、使用AspectJ框架实现 AOP

AspectJ 框架可以使用注解和xml配置文件两种方式实现AOP

3.2.1、通知分类

AspectJ 表示切面执行时间,用的通知(Advice),这个通知可以使用注解表示
下面是表示切面的 5 个执行时间,这些注解叫做通知注解

1. @Before :前置通知
2. @AfterReturning :后置通知
3. @Around : 环绕通知
4. @AfterThrowing :异常通知
5. @After :最终通知
3.2.2、PointCut 位置(切入点表达式)

PointCut 用来表示切面执行的位置,使用 AspectJ 中切入点表达式来表示
切入点表达式: execution(权限类型 返回值类型 包名类名方法名(参数类型和参数个数) 抛出异常类型)
举例:execution(public void com.proxy.MyProxy.doSome(String,Integer))

    权限类型、包名类名、抛出异常类型 :三个是可选部分,可有可无返回值类型、方法名(参数类型和参数个数) :必须存在

通配符:

    * : 0至多个任意字符.. : 用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径,此时后须跟*+ : 用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类

切入点表达式例子:

    execution(puiblic * *(..))
    指定切入点为:任意公共方法execution( * set*(..))
    指定切入点为:任意以set开头方法名的方法execution( * com.service.*.*(..))
    指定切入点为:在com.service包内的任意类的任意方法execution( * com.service..*.*(..))
    指定切入点为:在com.service包及其子包的任意类的任意方法execution( * *..service.*.*(..))
    指定所有包下的 service 子包的任意类的任意方法
3.2.3、前置通知@Before

实现步骤:

1. 新建maven项目,加入依赖
   spring-context、spring-aspects、junit
2. 创建业务接口和实现类
3. 创建一个普通类,作为切面类
   1. 在类的上面加入@Aspect
   2. 在类中定义方法,方法表示切面的功能
      在方法的上面加入 Aspect 框架中的通知注解,如@Before(value="切入点表达式")
4. 创建Spring配置文件
   1. 声明目标对象
    2. 声明切面类对象
    3. 声明自动代理生成器
5. 创建测试类,测试目标方法执行时,增加切面的功能

前置通知方法的定义:

    方法是 public方法是没有返回值的,void方法名称自定义方法可以有参数,如果有是JoinPoint;也可以没有参数

示例:

com.service.impl.SomeServiceImpl 实现类:

public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, Integer age) {
        System.out.println("业务方法doSome()");
    }
}

com.handle.MyAspect 切面类:

@Aspect
public class MyAspect {
    //定义方法
    

    
    @Before(value = "execution(public void com.service.impl.SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(){
        System.out.println("前置通知,切面的功能。在目标方法之前先执行");
    }
}

Spring配置文件:




    
    
    
    
    
    

测试:

@Test
public void test02(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
    //注意,对于使用AOP这里一定是接口的类型,而不是实现类的类型
    SomeService service = (SomeService) appc.getBean("someServiceImpl");
    System.out.println(service.getClass().getName()); //com.sun.proxy.$Proxy8
    service.doSome("",null); 
}

使用参数 JoinPoint :
当想要在通知方法里面使用到目标类的信息时,可以使用参数 JoinPoint

使用参数的前置通知方法:

@Aspect
public class MyAspect {
    //定义方法
    
    
    @Before(value = "execution(* *..SomeServiceImpl.do*(..))")
    public void myBefore(JoinPoint jp){
        //获取方法的定义
        System.out.println("前置通知==获取方法声明" + jp.getSignature());
        System.out.println("前置通知==获取方法名" + jp.getSignature().getName());
        //获取方法执行时参数
        Object[] args = jp.getArgs();  //数组中存放的是方法的所有参数
        for (Object o : args) {
            System.out.println("前置通知==获取方法的参数" + o);
        }

        
        String methodName = jp.getSignature().getName();
        if("doSome".equals(methodName)){
            //切面的代码
            System.out.println("前置通知==输出日志功能");
        }else if ("doOther".equals(methodName)){
            System.out.println("前置通知==记录时间功能");
        }
    }
}

测试结果:

3.2.4、后置通知@AfterReturning

后置通知方法的执行是在目标方法之后执行

语法:@AfterReturning(value=“切入点表达式” , returning=“通知方法的形参名”)

后置通知方法的定义:

    方法是 public方法是没有返回值的,void方法名称自定义方法有参数,推荐使用 Object

Spring配置文件未做改变

SomeServiceImpl 实现类:

public class SomeServiceImpl implements SomeService {
    @Override
    public String doSome(String name, Integer age) {
        System.out.println("业务方法doSome()");
        return name+age;
    }
}

MyAspect 切面类:

@Aspect
public class MyAspect {
    
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doSome(..))",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知==在目标方法之后执行,能拿到执行结果:" + obj);
    }
}

测试:

@Test
public void test03(){
    ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
    //注意,对于使用AOP这里一定是接口的类型,而不是实现类的类型
    SomeService service = (SomeService) appc.getBean("someServiceImpl");
    System.out.println(service.getClass().getName()); //com.sun.proxy.$Proxy8
    String result = service.doSome("xcm",18);
    System.out.println(result);    //xcm18
}

测试结果:

后置通知的方法参数可以拿到目标方法的返回值结果,那么拿到结果有什么用呢?

    @AfterReturning(value = "execution(* *..SomeServiceImpl.doSome(..))",returning = "obj")
    public void myAfterReturning(Object obj){
        System.out.println("后置通知==在目标方法之后执行,能拿到执行结果:" + obj);
        
        if ("xcm".equals(obj)){
            System.out.println("根据返回值的不同执行不同的增强功能");
        }
        if (obj != null){
            obj = "更改后的方法返回值结果";
        }
    }

思考:

    doSome 方法返回值是String、Integer、Long等基本类型时
    在后置通知中,修改返回值,是不会影响目标方法的最后调用结果的doSome 返回的结果是对象类型,例如 Student
    在后置通知中,修改这个Student对象的属性值,会不会影响最后调用结果?
3.2.5、环绕通知@Around

环绕通知方法的定义:

    方法是 public

    方法是必须是有返回值,推荐使用 Object 类型

    方法名称自定义

    方法必须有 ProceedingJoinPoint 参数

    不使用环绕通知的 ProceedingJoinPoint 参数时:使用环绕通知,执行目标方法的时候实际上调用的是切面类中的通知方法

    示例:

      切面类中的环绕通知:

      @Aspect
      public class MyAspect {
          
          @Around(value = "execution(* *..SomeServiceImpl.doSome(..))")
          public Object myAround(ProceedingJoinPoint pjp){
              System.out.println("环绕通知==执行环绕通知方法");
              return "HelloAround";
          }
      }
      

      测试:

      @Test
      public void test04(){
          ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
          //注意,对于使用AOP这里一定是接口的类型,而不是实现类的类型
          SomeService service = (SomeService) appc.getBean("someServiceImpl");
          System.out.println(service.getClass().getName());   //com.sun.proxy.$Proxy8
          String result = service.doSome("xcm",18);    //环绕通知==执行环绕通知方法
          System.out.println(result);    //HelloAround
      }
      

      测试结果:输出的返回值也是通知方法的返回值

    使用 ProceedingJoinPoint 参数

      在目标方法执行的前、后都能有增强功能;控制目标方法是否执行

      @Aspect
      public class MyAspect {
      	@Around(value = "execution(* *..SomeServiceImpl.doSome(..))")
          public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
              System.out.println("环绕通知==在目标方法执行之前");
              
              Object methodResult = null;
              //执行目标方法,相当于反射的method.invoke()
              methodResult = pjp.proceed();  //返回的结果methodResult就是目标方法的返回值
      
              System.out.println("环绕通知==在目标方法执行之后");
              return "HelloAround";
              //可以直接返回目标方法的返回结果,不修改
              //return methodResult;
          }
      }
      

      修改目标方法的执行结果

      @Aspect
      public class MyAspect {
          @Around(value = "execution(* *..SomeServiceImpl.doSome(..))")
          public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
              Object methodResult = null;
              methodResult = pjp.proceed();  //返回的结果就是目标方法的返回值
              //修改目标方法的返回值
              if (methodResult != null){
                  methodResult = "在环绕通知中更改方法返回值";
              }
              return methodResult;
          }
      }
      

      可以获取目标执行方法的参数、方法名

      @Aspect
      public class MyAspect {
      	@Around(value = "execution(* *..SomeServiceImpl.doSome(..))")
          public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
              Object methodResult = null;
      
              //ProceedingJoinPoint 类继承了 JoinPoint 类,所以也可以得到目标方法的参数、方法名
              String objName = "";
              Object[] args = pjp.getArgs();
              for (Object o : args) {
                  if (o != null){
                      //此时前提是doSome方法的参数仅有一个 String name
                      objName = (String) o;
                  }
              }
              //判断参数内容
              if ("xcm".equals(objName)){
                  //执行目标方法,相当于反射的method.invoke()
                  methodResult = pjp.proceed();  //返回的结果就是目标方法的返回值
              }
      
              //可以直接返回目标方法的返回结果
              return methodResult;
          }
      }
      
3.2.6、异常通知@AfterThrowing

语法:@AfterThrowing (value=“切入点表达式” , throwing=“通知方法的形参名”)

异常通知方法的定义:

    方法是 public方法是没有返回值的,void方法名称自定义方法有参数,参数是 Exception

Spring配置文件未做改变

@Aspect
public class MyAspect {
	
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSome(..))", throwing = "ex")
    public void myAfterThrowing(Exception ex){
        System.out.println("异常通知,在目标方法抛出异常的时候执行。异常原因:" + ex.getMessage());
        
    }
}
3.2.7、最终通知@After

最终通知方法的定义:

    方法是 public方法是没有返回值的,void方法名称自定义方法没有参数

Spring配置文件未做改变

@Aspect
public class MyAspect {
	
    @After(value = "execution(* *..SomeServiceImpl.doSome(..))")
    public void myAfter(){
        System.out.println("最终通知,总是会被执行的");
    }
}
3.2.8、@Pointcut 定义切入点

当使用较多相同的 execution 切入点表达式时,可使用 @Pointcut 注解,用于定义 execution 切入点表达式

注解 @Pointcut 放在一个方法上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点

@Aspect
public class MyAspect {
	
    @Pointcut(value = "execution(* *..SomeServiceImpl.doSome(..))")
    private void mpt(){
        //方法里面无需代码
    }


    //这里 mpt() 就相当于 execution(* *..SomeServiceImpl.doSome(..))
    @After(value = "mpt()")
    public void myAfter(){
        System.out.println("最终通知,总是会被执行的");
    }
}
3.3、总结

AOP 是一种动态的技术思想,目的是实现业务功能和非业务功能的耦合。业务功能是独立的模块,其他功能也是独立的模块

例如 事务功能、日志功能等,让这些事务、日志功能是可以被复用的

当目标方法需要一些功能时,可以在不修改、不能修改源代码的情况下,使用 AOP 技术在程序执行期间,生产代理对象,通过代理执行业务方法,同时增加功能

3.4、练习

使用 AOP 做方法的参数检查

要求:

    当 addNumber 方法的参数不为null、参数大于0 的时候,才可以执行 addNumber() 方法计算如果任意一个参数是 null 、或者小于 0,则调用 addNumber 方法返回结果是 -1

Spring 配置文件



    
    
    

切面类

package com.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class MyAspect {
    @Around(value = "execution(* *..NumberServiceImpl.addNumber(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知");
        Object methodResult = null;
        Object[] args = pjp.getArgs();
        for (Object arg : args) {
            if (arg == null || (Integer)arg <= 0){
                methodResult = -1;
                break;
            }else {
                methodResult = pjp.proceed();
            }
        }
        return methodResult;
    }
}

实现类

public class NumberServiceImpl implements NumberService {
    @Override
    public Integer addNumber(Integer n1, Integer n2, Integer n3) {
        return n1+n2+n3;
    }
}

测试类

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

public class MyTest {
    @Test
    public void test1(){
        ApplicationContext appc = new ClassPathXmlApplicationContext("applicationcontext.xml");
        NumberService numberService = (NumberService) appc.getBean("numberServiceImpl");
        Integer integer = numberService.addNumber(23, 23, 23);
        System.out.println(integer);
    }
}
4、Spring 集成 MyBatis 4.1、集成思路

Spring 能集成很多的框架,是Spring的一个有事功能,通过集成功能,让开发人员使用其他框架更方便,集成使用的是 Spring IOC 核心技术

使用 MyBatis,需要创建 MyBatis 框架中的某些对象,使用这些对象,就能使用 MyBatis 提供的功能了

分析:MyBatis 执行 SQL 语句,要使用哪些对象?

    需要有 dao 接口的代理对象,例如 StudentDao 接口,需要它的代理对象

    使用 SqlSession.getMapper(StudenDao.class),得到 dao 代理对象

    需要有 SqlSessionFactory ,创建 SqlSessionFactory 对象,才能使用 openSession() 得到 SqlSession 对象

    数据源 DataSource 对象,使用一个更强大、功能更多的连接池对象代替 MyBatis 自己的 PooledDataSource

4.2、集成步骤

实现步骤:

    使用 MySQL 库,使用学生表

    创建 maven 项目

    加入依赖

    spring依赖、mybatis依赖、mysql驱动、Junit依赖、mybatis-spring依赖(用来在spring中创建mybatis对象)、sprig有关事务的依赖

    创建实体类 Student

    创建 Dao 接口和 mapper 文件写 sql 语句

    MyBatis 主配置文件

    新建 service 接口和其实现类

    创建spring的配置文件

      声明数据源DataSource,使用功能阿里的Druid连接池声明 SQLSessionFactoryBean类,在这个类内部创建SQLSessionFactory对象声明MapperScannerConfiguration类,在内部创建dao代理对象,创建的对象都放在spring容器中声明service对象,将上面 3 中的 dao代理对象 赋值给service属性

    测试dao访问数据库

4.2.1、pom.xm文件

  UTF-8
  1.8
  1.8



  
    junit
    junit
    4.11
    test
  
  
  
    org.springframework
    spring-context
    5.2.5.RELEASE
  
  
  
    org.springframework
    spring-tx
    5.2.5.RELEASE
  
  
    org.springframework
    spring-jdbc
    5.2.5.RELEASE
  
  
  
    org.mybatis
    mybatis
    3.5.1
  
  
  
    org.mybatis
    mybatis-spring
    1.3.1
  
  
  
    mysql
    mysql-connector-java
    5.1.9
  
  
  
    com.alibaba
    druid
    1.1.12
  
  
  
    org.projectlombok
    lombok
    1.18.12
    provided
  



  
    
      src/main/java
      
        ***.xml
      
      false
    
  

4.2.2、Dao 接口和 mapper 文件

dao接口:com.afei.dao.StudentDao

public interface StudentDao {
    int insertStudent(Student student);
    List selectStudents();
}

Mapper文件:com.afei.dao.StudentDao.xml





    
        select * from goods where id = #{id}
    
    
        update goods set amount = amount-#{amount} where id = #{id}
    

SaleDao

public interface SaleDao {
    int insertSale(Sale sale);
}




    
        insert into sale(gid,num) values(#{gid},#{num})
    

5.4.4、mybatis主配置文件



    
    
        
    
    
    
        
    
    
    
        
        
        
        
    

5.4.5、service接口的实现类
@Setter
public class BuyGoodsServiceImpl implements BuyGoodsService {
    private GoodsDao goodsDao;
    private SaleDao saleDao;

    @Override
    public void buy(Integer goodsId, Integer num) {
        System.out.println("===buy方法的开始===");
        //生成销售记录
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNum(num);
        int rows = saleDao.insertSale(sale);
        //查询商品
        Goods goods = goodsDao.selectById(goodsId);
        if (goods == null){
            throw new NullPointerException(goodsId + "商品不存在");
        }else if (goods.getAmount() < num){
            throw new NotEnoughException(goodsId +"库存不足");
        }
        //更新库存
        Goods buyGoods = new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(num);
        goodsDao.updateGoods(buyGoods);
        System.out.println("===buy方法的完成===");
    }
}
5.4.6、spring配置文件



    
    
        
        
        
    
    
    
        
        
    
    
    
        
        
    
    
    
        
        
    

5.5、注解方式@Transactional

@Transactional 注解,使用注解的属性控制事务(隔离级别,传播行为,超时)

注解的属性:

    propagation:设置事务传播属性,使用 Propagation 类的枚举值,默认为 Propagation.REQUIREDisolation:设置事务隔离级别,使用 Isolation 类的枚举值,默认为 Isolation.DEFAULTreadOnly:是boolean类型,表示数据库操作是不是只读,默认是 falsetimeout:设置本操作与数据库连接的超时时间,单位为秒,int 类型,默认 -1rollbackFor:表示回滚的异常类列表,值是一个数组,每个值是异常类型的 classrollbackForClassName:表示回滚的异常类列表,值是异常类名称,是 String 类型的值noRollbackFor:表示不需要回滚的异常类列表,是 class 类型noRollbackForClassName:表示不需要回滚的异常类列表的名称,是 String 类型

注解的位置:

    在业务方法上面,public 类型在类的上面

注解的使用步骤:

    在 spring 的配置文件,声明事务的内容

    声明事务管理器,说明使用哪个事务管理器对象

    声明使用注解管理事务,开启注解驱动

    在类的源代码中,加入 @Transactional

修改 spring 配置文件:

注意事务驱动使用哪一个


    
    



添加 @TRansactional 注解:

注解使用的特点:

    一定在 public 方法上面适用于中小型项目,使用方便
package com.service.impl;

import com.dao.GoodsDao;
import com.dao.SaleDao;
import com.domain.Goods;
import com.domain.Sale;
import com.exception.NotEnoughException;
import com.service.BuyGoodsService;
import lombok.Setter;
import org.springframework.transaction.annotation.Transactional;

@Setter
public class BuyGoodsServiceImpl implements BuyGoodsService {
    private GoodsDao goodsDao;
    private SaleDao saleDao;

    
    @Transactional
    @Override
    public void buy(Integer goodsId, Integer num) {
        System.out.println("===buy方法的开始===");
        //生成销售记录
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNum(num);
        int rows = saleDao.insertSale(sale);
        //查询商品
        Goods goods = goodsDao.selectById(goodsId);
        if (goods == null){
            throw new NullPointerException(goodsId + "商品不存在");
        }else if (goods.getAmount() < num){
            throw new NotEnoughException(goodsId +"库存不足");
        }
        //更新库存
        Goods buyGoods = new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(num);
        goodsDao.updateGoods(buyGoods);
        System.out.println("===buy方法的完成===");
    }
}
5.6、xml配置文件方式

使用 AspectJ 的 AOP ,在spring配置文件中声明事务控制

使用步骤:

    pom.xml 文件中加入 spring-aspects 依赖

    
      org.springframework
      spring-aspects
      5.2.5.RELEASE
    
    

    在 spring 配置文件中声明事务内容

      声明事务管理器对象

      声明业务方法需要的事务属性

      声明切入点表达式



    
    
    
        
        
        
    
    
    
        
        
    
    
    
        
        
    
    
    
        
    

    
    
    
        
        
    
    
    
        
        
            
            
            
            
            
            
            
            
            
            
        
    
    
    
        
        
        
        
    

声明式事务优缺点:

    理解难、配置复杂代码和事务配置分开,控制事务源代码不用修改能快速了解项目全部事务,适合大型项目
6、Spring 与 Web 6.1、环境配置

完成学生注册功能,步骤:

    新建maven,修改 pom.xml创建实体类 Studentdao接口和mapper文件创建 mybatis 主配置文件service接口和实现类servlet,接收请求参数,调用service对象创建 jsp ,提交请求参数创建jsp,作为视图,显示请求的处理结果创建 spring 配置文件
pom.xml



  4.0.0

  com.afei
  demo02
  1.0-SNAPSHOT
  war

  
    UTF-8
    1.8
    1.8
  

  
    
      junit
      junit
      4.11
      test
    
    
    
      javax.servlet
      javax.servlet-api
      3.1.0
    
    
    
      javax.servlet.jsp
      jsp-api
      2.2.1-b03
    
    
    
      org.springframework
      spring-web
      5.2.5.RELEASE
    
    
    
      org.springframework
      spring-aspects
      5.2.5.RELEASE
    
    
    
      org.springframework
      spring-context
      5.3.1
      compile
    
    
    
      org.springframework
      spring-tx
      5.2.5.RELEASE
    
    
      org.springframework
      spring-jdbc
      5.2.5.RELEASE
    
    
    
      org.mybatis
      mybatis
      3.5.1
    
    
    
      org.mybatis
      mybatis-spring
      1.3.1
    
    
    
      mysql
      mysql-connector-java
      5.1.9
    
    
    
      com.alibaba
      druid
      1.1.12
    
    
    
      org.projectlombok
      lombok
      1.18.12
      provided
    
  

  
    
      
        src/main/java
        
          ***.xml
        
        false
      
    
    
      
      
        org.apache.tomcat.maven
        tomcat7-maven-plugin
        2.1
        
          8080 
          / 
          UTF-8 
        
      
    
  

实体类 Student
@Data
public class Student {
    private Integer stuId;
    private String stuName;
    private Integer stuAge;
}
dao接口和mapper文件
package com.dao;

import com.domain.Student;
import org.apache.ibatis.annotations.Param;

public interface StudentDao {
    int insertStudent(Student student);
    Student selectById(@Param("studentid")Integer id);
}



    
    
        insert into student(name,age) values(#{stuName},#{stuAge})
    

    
    
        
        
        
    
    
    

mybatis 主配置文件



    
    
        
    
    
    
        
        
    

    
    
        
        
    


service接口和实现类
package com.service.impl;

import com.dao.StudentDao;
import com.domain.Student;
import com.service.StudentService;
import lombok.Setter;

@Setter
public class StudentServiceImpl implements StudentService {
    private StudentDao dao;

    @Override
    public int addStudent(Student student) {
        return dao.insertStudent(student);
    }

    @Override
    public Student queryById(Integer id) {
        return dao.selectById(id);
    }
}
servlet 接收请求参数
package com.controller;

import com.domain.Student;
import com.service.StudentService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class AddStudentServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("name");
        String age = request.getParameter("age");

        //调用service
        ApplicationContext appc = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentService service = (StudentService) appc.getBean("studentServiceImpl");

        Student student = new Student();
        student.setStuName(name);
        student.setStuAge(Integer.valueOf(age));
        service.addStudent(student);
        //给用户显示处理结果
        request.getRequestDispatcher("/show.jsp").forward(request,response);
    }
}
6.2、现在容器对象的问题

问题:

    创建容器对象次数多,没调用一次 service ,就要创建一次容器对象在多个 servlet 中,分别创建容器对象

当创建容器对象的时候,spring 配置文件中的对象都会一同被创建

现在需要一个什么样的容器对象?

    容器对象只有一个,创建一次即可容器对象应该在整个项目中共有,多个 servlet 都能使用同一个容器对象

解决问题:

    使用监听器 ServletContextListener (内含两个方法:初始时执行的,销毁时执行的)在监听器中创建好的容器对象,应该放在 web 应用中的 ServletContext 作用域中
6.3、ContextLoaderListener 监听器

ContextLoaderListener 是一个监听器对象,是 ServletContextListener 的实现类,是spring框架提供的

使用这个监听器作用:

    创建容器对象,一次把容器对象放到 ServletContext 作用域

步骤:

    加入 spring-web 依赖

    
      org.springframework
      spring-web
      5.2.5.RELEASE
    
    

    web.xml 声明监听器

    
    
    
        
        
        
            
            contextConfigLocation
            
            classpath:applicationContext.xml ,classpath:myspring.xml
        
        
            org.springframework.web.context.ContextLoaderListener
        
    
        
            AddStudentServlet
            com.controller.AddStudentServlet
        
        
            AddStudentServlet
            /add
        
    
    
6.4、监听器源代码
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    //监听器的初始方法
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

}
private WebApplicationContext context;

public WebApplicationContext initWebApplicationContext(ServletContext servletContext){
	try {
        if (this.context == null) {
            //创建spring的容器对象
            this.context = this.createWebApplicationContext(servletContext);
        }
        //把容器对象放入 ServletContext 作用域
        
        servletContext.setAttribute(
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
            this.context
        );
    } catch (Error | RuntimeException var8) {}
}

//WebApplicationContext是web项目中使用的容器对象
public interface WebApplicationContext extends ApplicationContext {
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
}
6.5、使用ContextLoaderListener监听器

自己手动编写来获取spring容器对象

//使用监听器已经创建好了的容器对象,从 ServletContext 作用域获取容器

WebApplicationContext wappc = null;
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
//获取 ServletContext 对象
ServletContext sc = getServletContext();   // servlet中的方法获取 ServletContext 对象
//ServletContext sc = request.getServletContext();  请求对象来获取 ServletContext 对象
Object attribute = sc.getAttribute(key);
if(attribute != null){
    wappc = (WebApplicationContext) attribute;
}

StudentService service = (StudentService) wappc.getBean("studentServiceImpl");

使用 spring 提供的工具方法,获取容器对象

//使用 spring 提供的工具方法,获取容器对象
WebApplicationContext wappc = WebApplicationContextUtils
    						.getRequiredWebApplicationContext(getServletContext());
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/735168.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号