Spring
概述
1、Spring 全家桶:Spring、SpringMVC、Spring Boot、Spring Cloud。
2、Spring:出现是在2002左右,解决企业开发的难度。减轻对项目模块之间的管理,类和类之间的管理, 帮助开发人员创建对象,管理对象之间的关系。
3、Spring 核心技术:IoC、AOP。能实现模块之间,类之间的解耦合。
(什么是依赖 class a 中使用 class b 的属性或者方法,叫做 class a 依赖 class b。)
框架怎么学习
框架是一个软件,其它人写好的软件。
1、知道框架能做什么,MyBatis 访问数据库,对表中的数据执行增删改查。
2、框架的语法,框架要完成一个功能,需要一定的步骤支持。
3、框架的内部实现,框架内部怎么做。原理是什么。
4、通过学习,可以实现一个框架。
Spring 中的 IoC
概述
1、IoC (Inversion of Control) : 控制反转,是一个理论,概念,思想。
2、把对象的创建,赋值,管理工作都交给代码之外的容器实现,也就是对象的创建是有其它外部资源完成。
3、控制: 创建对象,对象的属性赋值,对象之间的关系管理。
4、反转: 把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。由容器代替开发人员管理对象、创建对象、给属性赋值。(容器是一个服务器软件,一个框架---Spring)
5、解耦合
ioc能够实现业务对象之间的解耦合,例如 service 和 dao 对象之间的解耦合。
正转是什么
由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。
public static void main(String args[]){
Student student = new Student(); // 在代码中,创建对象。---正转
}
为什么要使用 IoC
目的就是减少对代码的改动, 也能实现不同的功能。 实现解耦合。
java中创建对象有哪些方式:
1、构造方法,new Student()
2、反射
3、序列化
4、克隆
5、IoC:容器创建对象
6、动态代理
IoC 的体现
Servlet:
1、创建类继承 HttpServlet。
2、在 web.xml 注册Servlet。
3、没有创建 Servlet 对象, 没有 MyServlet myservlet = new MyServlet()。
4、Servlet 是 Tomcat 服务器创建的。Tomcat也称为容器。
Tomcat 作为容器:里面存放的有 Servlet 对象,Listener 对象,Filter 对象。
IoC 的技术实现
1、DI 是 IoC 的技术实现,DI(Dependency Injection):依赖注入。
2、只需要在程序中提供要使用的对象名称就可以,至于对象如何在容器中创建,赋值,查找都由容器内部实现。
3、Spring是使用的 DI 实现了 IoC 的功能,Spring底层创建对象,使用的是反射机制。
4、Spring 是一个容器,管理对象,给属性赋值,底层是反射创建对象。
5、Spring默认创建对象的时间:在创建Spring的容器时,会创建配置文件中的所有的对象。
6、Spring创建对象:默认调用的是无参数构造方法(注解的是有参)。
实现步骤:
1、创建 Maven 项目;
2、加入 Spring 的依赖、Junit 依赖;
3、创建类(接口和实现类);
4、创建 Spring 需要使用的配置文件声明类的信息,这些类由 Spring 创建和管理;
5、测试 Spring 创建的类。
代码示例
spring-conetxt 和 spring-webmvc 是 Spring 中的两个模块。
1、spring-context:是 IoC 功能的,创建对象的。
2、spring-webmvc:做 web 开发使用的,是 Servlet 的升级。其中也会用到 spring-context 中创建对象的功能。
package com.yu.service;
public interface OneService {
void dosome();
}
package com.yu.service.Impl;
import com.yu.service.OneService;
public class OneServiceImpl implements OneService {
@Override
public void dosome() {
System.out.println("dosome!!!");
}
}
<?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对象
声明bean:就是告诉Spring要创建某个类的对象
id:对象的自定义名称,唯一值。Spring通过这个名称找到对象
class:类的全限定名称,不能是接口(因为Spring是反射机制创建对象,必须使用类)
-->
<bean id="oneServiceImpl" class="com.yu.service.Impl.OneServiceImpl"/>
<!--
Spring就完成 OneService oneServiceImpl = new OneServiceImpl();
Spring是把创建好的对象放入到map中,Spring框架有一个map存放对象的。
springMap.put(id的值, 对象);
例如springMap.put("oneServiceImpl", new OneServiceImpl());
一个bean标签声明一个对象。
-->
</beans>
<!--
Spring的配置文件
1、beans:是根标签,Spring 把 java 对象称为:bean
2、Spring-beans.xsd 是约束文件,和MyBatis 指定 dtd 是一样的。
-->
package com.yu;
import com.yu.service.OneService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestSpring {
@Test
public void oneService(){
//使用spring容器创建的对象
//1、指定spring配置文件的名称(在类路径下)
String configPath = "beans.xml";
//2、创建spring容器对象
//ApplicationContext:表示spring容器,通过容器获取对象
//ClassPathXmlApplicationContext:表示从类路径中加载spring的配置文件
//此时容器中所有对象均已装配完毕
ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
//从容器中获取某个对象,使用getBean("配置文件中的bean的id值")
OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");
oneServiceImpl.dosome();
//获取容器中对象数量
int beansNumber = context.getBeanDefinitionCount();
System.out.println(beansNumber);
//获取容器中所有对象的名字
String[] beanNames = context.getBeanDefinitionNames();
for (String beanName:
beanNames) {
System.out.println(beanName);
}
}
}
ApplicationContext 容器中对象的装配时机
1、ApplicationContext 容器,会在容器对象初始化时,将其中的所有对象一次性全部装配好。
2、以后代码中若要使用到这些对象,只需从内存中直接获取即可。执行效率较高。但占用内存。
Junit
单元测试, 一个工具类库,做测试方法使用的。
单元:指定的是方法, 一个类中有很多方法,一个方法称为单元。
单元测试的使用
- 加入junit依赖。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
- 创建测试作用的类
叫做测试类,在 src/test/java 目录中创建类。 - 创建测试方法
1)public 方法;
2)没有返回值,使用 void ;
3)方法名称自定义,建议名称是test + 要测试方法名称;
4)方法没有参数;
5)方法的上面加入 @Test,这样的方法是可以单独执行的。不用使用main方法。
基于 XML 的 DI
1、bean 实例在调用无参构造器创建对象后,就要对 bean 对象的属性进行初始化。初始化是由容器自动完成的,称为注入。
2、根据注入方式的不同,常用的有两类:set 注入、构造注入。
set 注入
1、set 注入也叫设值注入,是指通过 setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在 Spring 的依赖注入中大量使用。
2、设值注入只是使用 set 方法。比如:
在没有属性 email 的情况下,可以对其进行设值注入,但是对象里没有 email
的内容。
- 简单类型
<bean id="xx" class="xx">
<property name="属性名" value="属性值"/>
一个 property 只能给一个属性赋值
</bean>
代码示例:
package com.yu.domain;
public class User {
private String name;
private int age;
//有参无参
//get、set
//toString
}
<bean id="user" class="com.yu.domain.User">
<property name="name" value="zhangsan"/>
<property name="age" value="20"/>
</bean>
<bean id="myDate" class="java.util.Date">
<!--1970年加1588800000000毫秒-->
<property name="time" value="1588800000000"/>
</bean>
@Test
public void user(){
String configPath = "beans.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
User user = (User) context.getBean("user");
Date myDate = (Date) context.getBean("myDate");
System.out.println(user);
System.out.println(myDate);
}
- 引用类型
当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的值必须为某 bean 的 id 值。
<bean id="xx" class="yy">
<property name="属性名" ref="bean的id(对象的名称)"/>
</bean>
代码示例
public class Student {
private Integer id;
private String sex;
//有参无参
//get、set
//toString
}
public class User {
private String name;
private int age;
private Student student;
//有参无参
//get、set
//toString
}
<bean id="user" class="com.yu.domain.User">
<property name="name" value="zhangsan"/>
<property name="age" value="20"/>
<!--使用 ref 指定它们间的引用关系-->
<property name="student" ref="student"/>
</bean>
<bean id="student" class="com.yu.domain.Student">
<property name="id" value="1"/>
<property name="sex" value="男"/>
</bean>
@Test
public void user(){
String configPath = "beans.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
User user = (User) context.getBean("user");
System.out.println(user);
}
构造注入
1、构造注入是指,在构造调用者实例的同时,完成被调用者的实例化(给属性赋值)。即使用构造器设置依赖关系。
2、<constructorarg />
标签中用于指定参数的属性有:
name :指定参数名称。
index :指明该参数对应着构造器的第几个参数,从 0 开始。不过,该属性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。
value:构造方法的形参类型是简单类型的,使用 value。
ref:构造方法的形参类型是引用类型的,使用 ref。
- 代码示例
public class Student {
private Integer id;
private String sex;
public Student(Integer id, String sex) {
this.id = id;
this.sex = sex;
System.out.println("Student 有参构造");
}
//无参
//get、set
//toString
<bean id="student" class="com.yu.domain.Student">
<!--<property name="id" value="1"/>
<property name="sex" value="男"/>-->
<!-- 或 -->
<constructor-arg name="id" value="111"/>
<constructor-arg name="sex" value="女"/>
<!-- 或 -->
<!--<constructor-arg index="0" value="111"/>
<constructor-arg index="1" value="女"/>-->
<!-- 或(参数顺序要一致) -->
<!--<constructor-arg value="111"/>
<constructor-arg value="女"/>-->
</bean>
@Test
public void user(){
String configPath = "beans.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
Student user = (Student) context.getBean("student");
System.out.println(user);
}
- 构造注入创建文件对象
<bean name="myFile" class="java.io.File">
<constructor-arg index="0" value="C:\Users\lenovo\Desktop\spring"/>
<constructor-arg index="1" value="spring课堂笔记"/>
</bean>
@Test
public void myFile(){
String configPath = "beans.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
//User user = (User) context.getBean("user");
File myFile = (File) context.getBean("myFile");
System.out.println(myFile.getName());
}
引用类型属性自动注入
1、对于引用类型属性的注入,也可不在配置文件中显示的注入。
2、可以通过为<bean/>
标签设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性 )。
3、根据自动注入判断标准的不同,可以分为两种:
byName:根据名称自动注入;
byType:根据类型自动注入。
- byName 方式自动注入
1、当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的属性名相同时,可使用 byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。
2、容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。
3、图示
这里的 student 属性名、类型和配置文件中的 Student bean 的 id 值对应的属性名、类型一致,使用autowire="byName"
可实现自动注入。
autowire="byName"
表示:
给 User 类中的所有引用类型按照 byName 规则让 Spring 完成赋值。
4、代码示例
public class Student {
private Integer id;
private String sex;
//有参无参
//get、set
//toString
}
public class User {
private String name;
private int age;
private Student student;
//有参无参
//get、set
//toString
}
<bean id="user" class="com.yu.domain.User" autowire="byName">
<property name="name" value="zhangsan"/>
<property name="age" value="20"/>
<!--<property name="student" ref="student"/>-->
</bean>
<bean id="student" class="com.yu.domain.Student">
<property name="id" value="1"/>
<property name="sex" value="男"/>
</bean>
@Test
public void user(){
String configPath = "beans.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
User user = (User) context.getBean("user");
System.out.println(user);
}
- byType 方式自动注入
1、使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。
2、即要么相同,要么有 is a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配哪一个了。
3、java 类中引用类型的数据类型和 Spring 容器中(配置文件)<bean/>
的 class 属性是同源关系的,这样的 bean 能够赋值给引用类型。
4、什么是同源?
就是一类的意思,包括以下:
① java 类中引用类型的数据类型和 bean 的 class 的值是一样的;
② 或父子类关系的;
③ 或接口和实现类关系的。
5、图例:
Student 类型对应 com.yu.domain.Student,是一样的。
这里避免 bean 的 id 与 属性名相同,将其 id 改为 myStudent。
6、代码示例
在上面的代码中将 xml 进行改动即可:
<bean id="user" class="com.yu.domain.User" autowire="byType">
<property name="name" value="zhangsan"/>
<property name="age" value="20"/>
<!--<property name="student" ref="student"/>-->
</bean>
<bean id="myStudent" class="com.yu.domain.Student">
<property name="id" value="1"/>
<property name="sex" value="男"/>
</bean>
这里只是写了一个(java 类中引用类型的数据类型和 bean 的 class 的值是一样的)例子,还有两个例子(父子类关系的;接口和实现类关系的)也是类似的。
为应用指定多个 Spring 配置文件
- 概述
1、在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
2、包含关系的配置文件:
多个配置文件中有一个总文件,总配置文件将各其它子文件通过<import/>
引入。在 java 代码中只需要使用总配置文件对容器进行初始化即可。 - 多个配置优势
1、每个文件的大小比一个文件要小很多,效率高。
2、避免多人竞争带来的冲突。
3、如果你的项目有多个模块(相关的功能在一起),一个模块一个配置文件。比如:学生考勤模块一个配置文件,给张三;学生成绩一个配置文件,给李四。 - 多文件的分配方式:
1、按功能模块,一个模块一个配置文件。
2、按类的功能,数据库相关的一个配置文件,做事务功能的一个配置文件,做 Service 功能的一个配置文件等。 - 例子
Spring 主配置文件(beans.xml):
<import resource="spring-student.xml"/>
<import resource="spring-user.xml"/>
<!--<import resource="spring-*.xml"/>-->
也可使用通配符*
。但此时要求父配置文件名不能满足*
所能匹配的格式,否则将出现循环递归包含。就本例而言,父配置文件不能匹配 spring-*.xml
的格式,即不能起名为 spring-total.xml
。
(其中<import resource="classpath:com/yu/.../spring-student.xml"/>
这种方式表示类路径,即 class 文件所在的目录)
测试代码:
@Test
public void user(){
String configPath = "beans.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
User user = (User) context.getBean("user");
System.out.println(user);
}
基于注解的 DI
基于注解的 di:通过注解完成 java 对象创建,属性赋值。
使用注解的步骤:
1、加入 maven 的依赖 spring-context,在你加入 spring-context 的同时,间接加入 spring-aop 的依赖。使用注解必须使用 spring-aop 依赖。
2、在类中加入 spring 的注解(多个不同功能的注解)。
3、在 spring 的配置文件中,加入一个组件扫描器的标签,说明注解在你的项目中的位置。
组件扫描器
需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
- 单个包
<?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
https://www.springframework.org/schema/context/spring-context.xsd">
<!--声明组件扫描器(component-scan):指定注解所在的包名-->
<context:component-scan base-package="com.yu.domain"/>
</beans>
- 指定多个包的三种方式
1、使用多个 context:component-scan 指定不同的包路径。
<!--声明组件扫描器(component-scan):指定注解所在的包名-->
<context:component-scan base-package="com.yu.domain"/>
<context:component-scan base-package="com.yu.utils"/>
2、指定 base-package 的值使用分隔符。分隔符可以使用逗号(,
)、分号(;
)、还可以使用空格,但不建议使用空格。
逗号分隔:
<context:component-scan base-package="com.yu.domain, com.yu.utils"/>
分号分隔:
<context:component-scan base-package="com.yu.domain; com.yu.utils"/>
3、base-package 是指定到父包名。base-package 的值表示基本包,容器启动会扫描包及其子包中的注解,当然也会扫描到子包下级的子包。所以 base-package 可以指定一个父包就可以。
<context:component-scan base-package="com.yu"/>
或者最顶级的父包
<context:component-scan base-package="com"/>
但不建议使用顶级的父包,扫描的路径比较多,导致容器启动时间变慢。
指定到目标包和合适的。也就是注解所在包全路径。例如注解的类在 com.yu.domain 包中。
<context:component-scan base-package="com.yu.domain"/>
7个注解
1、@Component
2、@Respository
3、@Service
4、@Controller
5、@Value
6、@Autowired
7、@Resource
@Component
用来定义 Bean 的注解,需要在类上使用注解@Component。该注解的 value 属性用于指定该 bean 的 id 值。
- 第一种方式
@Component(value = "myStudent")
/**
* @Component:创建对象的,等同于 <bean> 的功能。
* 属性:value 就是对象的名称,也就是 bean 的 id 值,
* value 的值是唯一的,创建的对象在整个 spring 容器中就一个。
* 位置:在类的上面。
* @Component(value = "myStudent")
* 等同于
* <bean id="myStudent" class="com.yu.domain.Student" />
*/
@Component(value = "myStudent")
public class Student {
private Integer id;
private String sex;
//有参无参
//get、set
//toString
}
- 第二种方式
//使用省略 value 的写法
@Component("myStudent")
- 第三种方式
@Component 不指定 value 属性,bean 的 id 是类名的首字母小写。
@Component 注解的细化
1、另外,Spring 还提供了3个创建对象的注解(使用语法一样,但是功能不一):
@Repository 用于对 DAO 实现类进行注解。
@Service 用于对 Service 实现类进行注解。
@Controller 用于对 Controller 实现类进行注解。
2、这三个注解与 @Component 都可以创建对象,但这三个注解还有其他的 含义 。@Service 创建业务层对象,业务层对象可以加入事务功能。@Controller 注解创建的对象可以作为处理器接收用户的请求。
3、@Repository、@Service、@Controller 是对 @Component 注解的细化,标注不同层的对象。 即持久层对象,业务层对象,控制层对象。
@Value
1、用于简单类型属性注入,需要在属性上使用注解 @Value,该注解的 value 属性用于指定要注入的值(value 是 String 类型的)。
2、使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
3、代码示例
package com.yu.domain;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Student {
@Value(value = "123")
//@Value("123")
private Integer id;
//@Value(value = "男")
@Value("男")
private String sex;
@Override
public String toString() {
return "Student{" +
"id=" + id +
", sex='" + sex + '\'' +
'}';
}
}
<context:component-scan base-package="com.yu.domain"/>
@Test
public void user(){
String configPath = "applicationContext.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(configPath);
Student student = (Student) context.getBean("student");
System.out.println(student);
}
4、还可以加在 set 方法上:
@Value("男")
private String sex;
@Value("123")
public void setId(Integer id) {
this.id = id;
}
@Value("男")
public void setSex(String sex) {
this.sex = sex;
}
5、如果在属性和 set 方法上同时赋值 value,最终显示的只是 set 赋的值。
@Value("123")
private Integer id;
@Value("男")
private String sex;
@Value("456")
public void setId(Integer id) {
this.id = id;
}
@Value("女")
public void setSex(String sex) {
this.sex = sex;
}
@Autowired
1、在引用属性上使用注解 @Autowired。
2、Autowired:spring 框架提供的注解,实现引用类型的赋值。spring 中通过注解给引用类型赋值,使用的是自动注入原理,支持 byName、byType。
3、@Autowired 默认使用的是 byType 自动注入。
4、使用位置:
1)在属性定义的上面,无需 set 方法,推荐使用。
2)在 set 方法的上面。
- byType 方式
- byName 方式
1、需要在引用属性上联合使用注解 @Autowired 与 @Qualifier。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。
2、在属性上面加入 @Qualifier(value="bean的id"):表示使用指定名称的 bean 完成赋值。
3、示例:
- required 属性
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
1、是一个 boolean 类型,默认为 true。
2、required = true:表示引用类型赋值失败,程序报错,并终止执行。
3、required = false:引用类型如果赋值失败,程序正常执行,引用类型是 null。
使用 true:
报错:
使用 false:
不报错,student 为 null:
注意:@Autowired 的 required 属性推荐使用 true。这样更好调试,并且避免了空指针异常
@Resource
1、Spring 提供了对 jdk 中 @Resource 注解的支持。
2、@Resource 注解既可以按名称匹配 Bean 也可以按类型匹配 Bean。 默认是按名称注入使用该注解。
3、要求 JDK 必须是 6 及以上版本。@Resource 可在属性上,也可在 set 方法上。
- byType 注入引用类型属性
@Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean 则会按照类型进行 Bean 的匹配注入。
- byName 注入引用类型属性(默认的)
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。
注解与 XML 的对比
注解优缺点
- 优点
1、方便。
2、直观。
3、高效(代码少,没有配置文件的书写那么复杂)。 - 缺点
其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。
XML 方式优缺点
- 优点
1、配置和代码是分离的(不够直观)。
2、在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。 - 缺点
xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。
总结
1、经常改的用 XML,不经常改的用注解。
2、注解为主,XML 为辅。
使用配置文件的方式赋值来解耦合
配置 xml:
<context:property-placeholder location="test.properties"/>
使用@Value("${ }")
的方式进行赋值:
package com.yu.domain;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("oneStudent")
public class Student {
//@Value("123")
@Value("${myId}")
private Integer id;
//@Value("男")
@Value("${mySex}")
private String sex;
//有参无参
//get、set
//toString
}
动态代理
- 实现方式
1、jdk 动态代理,使用 jdk 中的 Proxy,Method,InvocaitonHanderl 创建代理对象。jdk 动态代理要求目标类必须实现接口。
2、cglib 动态代理:第三方的工具库,创建代理对象,原理是继承。通过继承目标类,创建子类。子类就是代理对象。 要求目标类不能是 final 的,方法也不能是 final 的。 - 动态代理的作用:
1、在目标类源代码不改变的情况下,增加功能;
2、减少代码的重复;
3、专注业务逻辑代码;
4、解耦合,让业务功能和日志,事务非业务功能分离。 - 代码示例
接口:
package com.yu.service;
public interface OneService {
public void doSome();
public void doOther();
}
实现类:
package com.yu.service.impl;
import com.yu.service.OneService;
public class OneServiceImpl implements OneService {
@Override
public void doSome() {
System.out.println("这是doSome方法");
}
@Override
public void doOther() {
System.out.println("这是doOther方法");
}
}
非业务方法工具类:
package com.yu.utils;
public class ServiceUtils {
public static void dobefore(){
System.out.println("111111执行目标方法前执行的!");
}
public static void doafter(){
System.out.println("222222执行目标方法后执行的!");
}
}
MyInvocationHandler:
package com.yu.handler;
import com.yu.utils.ServiceUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
//目标对象
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//目标方法执行结果
Object result = null;
//doSome才能执行非业务方法
if ("doSome".equals(method.getName())){
//非业务方法
ServiceUtils.dobefore();
//执行目标方法返回结果
result = method.invoke(target, args);
//非业务方法
ServiceUtils.doafter();
}else {
//其它的方法没有非业务方法
result = method.invoke(target, args);
}
return result;
}
}
测试:
package com.yu;
import com.yu.handler.MyInvocationHandler;
import com.yu.service.OneService;
import com.yu.service.impl.OneServiceImpl;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class testProxy {
@Test
public void test(){
//创建目标对象
OneService target = new OneServiceImpl();
//将目标对象交给InvocationHandler
InvocationHandler myHandler = new MyInvocationHandler(target);
//创建代理对象
OneService oneService = (OneService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
myHandler);
//代理对象执行方法
oneService.doSome();
System.out.println("============================");
oneService.doOther();
}
}
AOP
概述
1、AOP(Aspect Orient Programming)
面向切面编程。面向切面编程是从动态角度考虑程序运行过程,可通过运行期动态代理实现程序功能的统一维护的一种技术。
Aspect:切面,给目标类增加的功能,就是切面。 像上面用的 ServiceUtils 工具类中的 dobefore()、doafter() 方法都是切面。切面的特点:一般都是非业务方法,独立使用的。
Orient:面向。
Programming:编程。
2、AOP 底层是采用动态代理模式实现的
采用了两种代理:JDK 的动态代理与 CGLIB 的动态代理 。AOP 就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。
3、降低耦合度
利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
4、将交叉业务逻辑封装成切面
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志 、缓存等。若不使用 AOP ,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样会使主业务逻辑变的混杂不清。
5、例如:转账
在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑转账。
面向切面编程的好处
1、减少重复。
2、专注业务。
注意:面向切面编程只是面向对象编程的一种补充。
使用 AOP 减少重复代码,专注业务实现。
面向切面编程的使用
1、需要在分析项目功能时,找出切面。
2、合理的安排切面的执行时间(在目标方法前,还是目标方法后)。
3、合理的安全切面执行的位置,在哪个类,哪个方法增加增强功能。
相关术语
1、切面(Aspect)
切面泛指交叉业务逻辑。上例中的 ServiceUtils 工具类中的 dobefore()、doafter() 方法就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。用来完成一些非业务功能,常见的切面功能有日志、事务、统计信息、参数检查、权限验证等。
2、连接点(JoinPoint)
连接点指可以被切面织入的具体方法,通常业务接口中的方法均为连接点。
就是连接业务方法和切面的位置,例如某类中的业务方法。
3、切入点(Pointcut)
切入点指声明的一个或多个连接点的集合(多个方法)。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
4、目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。给哪个类的方法增加功能,这个类就是目标对象。
5、通知(Advice)
通知表示切面功能执行的时间,Advice 也叫增强。
上例中的 MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。
通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
切面的三个关键要素
1、切面的功能代码,表明切面干什么。
2、切面的执行位置,使用 Pointcut 表示切面执行的位置。
3、切面的执行时间,使用 Advice 表示时间,在目标方法之前,还是目标方法之后。
AOP 的实现
- AOP 是一个规范,是动态的一个规范化,一个标准。
- AOP 的技术实现框架
1、Spring:Spring 在内部实现了 AOP 规范,能做 AOP 的工作。Spring 主要在事务处理时使用 AOP。项目开发中很少使用 Spring 的 AOP 实现。因为 Spring 的 AOP 比较笨重。
2、AspectJ:一个开源的专门做 AOP 的框架。Spring 框架中集成了 AspectJ 框架,通过 Spring 就能使用 AspectJ 的功能。
AspectJ
概述
AspectJ 框架实现 AOP 有两种方式:
1、使用 xml 的配置文件:配置全局事务。
2、使用注解,我们在项目中要做 AOP 功能,一般都使用注解,AspectJ 有5个注解。
通知类型
切面的执行时间。这个执行时间在规范中叫做 Advice(通知,增强),在 AspectJ 框架中使用注解表示的。也可以使用 xml 配置文件中的标签。
1、@Before(前置通知)
2、@AfterReturning(后置通知)
3、@Around(环绕通知)
4、@AfterThrowing(异常通知)
5、@After(最终通知)
切入点表达式
表示切面执行的位置,使用的是切入点表达式。
AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
- 解释
modifiers-pattern:访问权限类型;
ret-type-pattern:返回值类型;
declaring-type-pattern:包名 类名;
name-pattern(param-pattern):方法名 参数 类型和参数个数;
throws-pattern:抛出异常类型;
?:表示可选的部分。
以上表达式共4 个部分。
execution( 访问权限 方法返回值 方法声明(参数) 异常类型) - 表达式中可使用的符号
1、切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就是方法的签名。
2、注意,表达式中(访问权限、异常类型)可省略,各部分间用空格分开。在其中可以使用以下符号:
示例
1、execution(public * *(..))
指定切入点为:任意公共方法。
2、execution(* set*(..))
指定切入点为:任何一个以 “set” 开始的方法。
3、execution(* com.xyz.service.*.*(..))
指定切入点为:定义在 service 包里的任意类的任意方法。
4、execution(* com.xyz.service..*.*(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..” 出现在类名中时,后面必须跟 “*”,表示包、子包下的所有类。
5、execution(* *..service.*.*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点。
6、execution(* *.service.*.*(..))
指定只有一级包下的 serivce 子包下所有类 (接口)中所有方法为切入点。
7、execution(* *.ISomeService.*(..))
指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点。
8、execution(* *..ISomeService.*(..))
指定所有包下的 ISomeSerivce 接口中所有方法为切入点
9、execution(* com.xyz.service.IAccountService.*(..))
指定切入点为:IAccountService 接口中的任意方法。
10、execution(* com.xyz.service.IAccountService+.*(..))
指定切入点为:IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。
11、execution(* joke(String, int))
指定切入点为:所有的 joke(String, int) 方法,且 joke() 方法的第一个参数是 String,第二个参数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int) 。
12、execution(* joke(String, *))
指定切入点为:所有的 joke() 方法,该方法第一个参数为 String ,第二个参数可以是任意类型,如 joke(String s1,String s2) 和 joke(String s1,double d2) 都是,但 joke(String s1,double d2,Strings3) 不是。
13、execution(* joke(String, ..))
指定切入点为:所有的 joke() 方法,该方法第一个参数为 String ,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1, Strings2) 和 joke(String s1, double d2, String s3) 都是。
14、execution(* joke(Object))
指定切入点为:所有的 joke() 方法,方法拥有一个参数,且参数是 Object 类型。 joke( Object ob)是,但, joke(String s) 与 joke(User u) 均不是。
15、execution(* joke(Object+))
指定切入点为:所有的 joke() 方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob) 是,joke(String s) 和 joke(User u) 也是。
使用 AspectJ 的基本步骤
- 新建 Maven 项目
- 加入依赖
spring 依赖、aspectj 依赖、junit 单元测试依赖 - 创建目标类
接口和它的实现类。因为要做的是给类中的方法增加功能。 - 创建切面类
这是一个普通类,需要在类的上面加入 @Aspect;
在类中定义方法,方法就是切面要执行的功能代码;
在方法的上面加入 AspectJ 中的通知注解,例如 @Before;
有需要的话指定切入点表达式 execution()。 - 创建 Spring 的配置文件
声明对象,把对象交给容器统一管理;
声明对象可以使用注解或者 xml 配置文件的<bean>
。
1、声明目标对象;
2、声明切面类对象;
3、声明 AspectJ 框架中的自动代理生成器标签。自动代理生成器:用来完成代理对象的自动创建功能的。 - 创建测试类
从 Spring 容器中获取目标对象(实际就是代理对象)。通过代理执行方法,实现 AOP 的功能增强。 <aop:aspectj-autoproxy/>
1、底层是由 AnnotationAwareAspectJAutoProxyCreator 实现的。从其类名就可看出,是基于 AspectJ 的注解适配自动代理生成器。
2、其工作原理是,<aop:aspectj-autoproxy/>
通过扫描找到 @Aspect 定义的切面类(一次性全部生成代理对象),再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
代码示例(前置通知-注解版)
接口:
package com.yu.service;
public interface OneService {
void doSome(String name, int age);
void doOther();
}
实现类:
package com.yu.service.impl;
import com.yu.service.OneService;
import org.springframework.stereotype.Component;
@Component
public class OneServiceImpl implements OneService {
@Override
public void doSome(String name, int age) {
System.out.println("这是doSome方法");
}
@Override
public void doOther() {
System.out.println("这是doOther方法");
}
}
普通类(切面):
package com.yu.aspects;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
/**
* @Aspect:是AspectJ框架中的注解。
* 作用:表示当前类是切面类。
* 切面类:是用来给业务方法增加功能的类,
* 在这个类中有切面的功能代码。
* 位置:在类定义的上面。
*/
@Aspect
@Component
public class OneAspect {
/* 定义的方法是实现切面功能的。
* 方法的定义要求:
* 1、公共方法(public)
* 2、方法没有返回值
* 3、方法名称自定义
* 4、方法可以有参数,也可以没有参数。
* 如果有参数,参数不是自定义的,有几个参数类型可以使用。
* */
/*
* @Before:前置通知注解
* 属性:value,是切入点表达式,表示切面的功能执行的位置。
* 位置:在方法的上面
* 特点:在目标方法之前先执行,不会改变、影响目标方法的执行。
* */
@Before(value = "execution(* com.yu.service.impl.OneServiceImpl.doSome())")
public void before(JoinPoint joinPoint){
System.out.println("这是前置通知,在目标方法前执行,例如输出日志");
}
}
xml 配置:
<!--声明组件扫描器(component-scan):指定注解所在的包名-->
<context:component-scan base-package="com.yu.domain, com.yu.service, com.yu.aspects"/>
<!--声明自动代理生成器,创建代理-->
<aop:aspectj-autoproxy/>
测试类:
package com.yu;
import com.yu.service.OneService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class testProxy {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");
//JDK动态代理
System.out.println(oneServiceImpl.getClass().getName());
oneServiceImpl.doSome("zahngsan", 20);
System.out.println("==============");
//doOther方法没有设置切入点
oneServiceImpl.doOther();
}
}
基于 XML 配置的 AOP
待完善。。。
JoinPoint
1、JoinPoint 对象封装了 Spring Aop 中切面方法的信息,在切面方法中添加 JoinPoint 参数,就可以获取到封装了该方法信息的 JoinPoint 对象。
2、在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个
JoinPoint 类型参数。这个 JoinPoint 参数的值是由框架赋予,必须是第一个位置的参数。
3、该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象、方法名称、方法的实参等。
4、不光前置通知的方法可以包含一个 JoinPoint 类型参数 ,所有的通知方法均可包含该参数,如果切面功能中需要用到方法的信息,就加入 JoinPoint。
方法名 | 功能 |
---|---|
Signature getSignature(); | 获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息 |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理的对象 |
Object getThis(); | 获取代理对象 |
- 代码示例
package com.yu.aspects;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.lang.reflect.Modifier;
@Aspect
@Component
public class OneAspect {
@Before(value = "execution(* com.yu.service.impl.OneServiceImpl.doSome(..))")
public void before(JoinPoint joinPoint){
System.out.println("目标方法签名为:" + joinPoint.getSignature());
System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
System.out.println("目标方法声明类型:" + Modifier.toString(joinPoint.getSignature().getModifiers()));
//获取传入目标方法的参数
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
System.out.println("第" + (i+1) + "个参数为:" + args[i]);
}
System.out.println("被代理的对象:" + joinPoint.getTarget());
System.out.println("代理对象自己:" + joinPoint.getThis());
System.out.println("这是前置通知,在目标方法前执行,例如输出日志");
}
}
后置通知
1、在目标方法执行之后执行。
2、由于是目标方法之后执行,所以可以获取到目标方法的返回值。
3、该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。
4、该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
- 代码示例
接口:
package com.yu.service;
import com.yu.domain.Student;
public interface OneService {
void doSome(String name, int age);
String doOther();
Student doStudent(int id, String sex);
}
实现类:
package com.yu.service.impl;
import com.yu.domain.Student;
import com.yu.service.OneService;
import org.springframework.stereotype.Component;
@Component
public class OneServiceImpl implements OneService {
@Override
public void doSome(String name, int age) {
System.out.println("这是doSome方法");
}
@Override
public String doOther() {
System.out.println("这是doOther方法");
return "qweasd";
}
@Override
public Student doStudent(int id, String sex) {
System.out.println("这是doStudent方法");
Student student = new Student();
student.setId(id);
student.setSex(sex);
return student;
}
}
普通类(切面):
package com.yu.aspects;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class OneAspect {
/* 方法的定义要求:
* 1、公共方法(public)
* 2、方法没有返回值
* 3、方法名称自定义
* 4、方法有参数,推荐Object,参数名自定义
* */
/*
* @AfterReturning:前置通知注解
* 属性:value---是切入点表达式,表示切面的功能执行的位置;
* returning---自定义的变量,表示目标方法的返回值;
* 自定义变量名必须和通知方法的形参名一样。
* 位置:在方法的上面
* 特点:在目标方法之后执行;
* 能够获取到目标方法的返回值,可以根据返回值做不同的处理功能;
* 可以修改这个返回值。
* */
@AfterReturning(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))",
returning = "result")
public void before(Object result){
/* Object result
* 是目标方法执行后的返回值,根据返回值做切面的功能处理
* */
/* 修改目标方法的返回值
*
* 相当于先执行Object result = doOther()
* 再执行before(Object result)
* */
if( result != null){
String st = (String)result;
result = st.toUpperCase();
}
System.out.println("这是后置通知,在目标方法后执行,获取的result是:" + result);
if("qweasd".equals(result)) {
System.out.println("做一些操作");
}else {
System.out.println("做另一些操作");
}
}
}
xml 配置:
<!--声明组件扫描器(component-scan):指定注解所在的包名-->
<context:component-scan base-package="com.yu.domain, com.yu.service, com.yu.aspects"/>
<!--声明自动代理生成器,创建代理-->
<aop:aspectj-autoproxy/>
测试类:
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");
oneServiceImpl.doSome("zahngsan", 20);
System.out.println("==============");
//doOther方法没有设置切入点
String result = oneServiceImpl.doOther();
System.out.println("result======" + result);
}
- 修改 result 的内容,属性值等
@AfterReturning(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))",
returning = "result")
public void before(Object result){
Student student = (Student) result;
student.setId(333);
student.setSex("men");
System.out.println("这是后置通知,在目标方法后执行,获取的result是:" + result);
}
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");
Student student = oneServiceImpl.doStudent(22,"women");
System.out.println(student);
}
调用 doOther 方法,修改返回的 String 类型的值:
@AfterReturning(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))",
returning = "result")
public void before(Object result){
String res = result.toString()+"aaaaaaa";
System.out.println("这是后置通知,在目标方法后执行,获取的result是:" + res);
}
String res = oneServiceImpl.doOther();
System.out.println(res);
不会改变。
环绕通知
1、在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。
2、接口 ProceedingJoinPoint 有一个 proceed() 方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。
3、最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
- 代码示例
接口:
package com.yu.service;
public interface OneService {
String doOne(String school, String address);
}
实现类:
package com.yu.service.impl;
import com.yu.service.OneService;
import org.springframework.stereotype.Component;
@Component
public class OneServiceImpl implements OneService {
@Override
public String doOne(String school, String address) {
System.out.println("这是doOne方法");
return "beida";
}
}
切面类:
package com.yu.aspects;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class OneAspect {
/**
* 环绕通知方法定义格式
* 1、public。
* 2、必须有一个返回值,推荐使用Object。
* 3、方法名称自定义。
* 4、方法有参数,固定参数ProceedingJoinPoint,
* 等同于动态代理中的Method,用于执行目标方法;
* 其返回值就是目标方法的执行结果,可以被修改。
* 5、是功能最强的通知,在目标方法前后都能增强功能。
* 6、控制目标方法是否被调用执行。
* 7、可以改变原来目标方法的执行结果,影响最后的调用结果。
* 8、等同于JDK动态代理中的InvocationHandler接口。
* */
@Around(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//获取第一个参数值
String arg1 = null;
Object[] args = pjp.getArgs();
if (args != null && args.length > 0){
arg1 = args[0].toString();
}
Object obj = null;
//增强功能
System.out.println("环绕通知: 在目标方法前执行,例如输出日志");
//执行目标方法的调用(必须),等同于method.invoke(target, args)
if ("xx".equals(arg1)){
//如果符合条件才执行目标方法
obj = pjp.proceed();
}
//增强功能
System.out.println("环绕通知: 在目标方法后执行,例如处理事务");
//返回目标方法的执行结果
return obj;
}
}
测试:
public class testProxy {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");
String doOne = oneServiceImpl.doOne("xx","bj");
System.out.println(doOne);
}
}
- 修改目标方法的返回结果
- 用于事务
环绕通知经常用于事务,在目标方法执行之前开启事务,在目标方法执行之后提交事务。
异常通知
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable ,参数名称为 throwing 指定的名称,表示发生的异常对象。
- 代码示例
业务方法的实现:
@Override
public void doTwo() {
System.out.println("这是doTwo方法" + 1/0);
}
切面类:
package com.yu.aspects;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class OneAspect {
/** 异常通知方法定义格式
* 1、public。
* 2、没有返回值。
* 3、方法名称自定义。
* 4、方法有参数Throwable或者Exception,
* 如果还有就是JoinPoint。
* */
/** @AfterThrowing:异常通知
* 属性:
* 1、value:切入点表达式
* 2、throwing:自定义的变量,表示目标方法抛出的异常对象。
* 变量名必须和方法的参数名一样。
*
* 特点:
* 1、在目标方法抛出异常时执行。
* 2、可以做异常的监控程序,监控目标方法执行时是否有异常。
* 如果有异常,可以发送邮件、短信进行通知。
* */
@AfterThrowing(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))", throwing = "ex")
public void around(Throwable ex) {
//把异常发生的时间、位置、原因记录到数据库,日志文件等等。
//可以在异常发生时,把异常信息通过短信、邮件发送给开发人员。
System.out.println("异常通知:在目标方法抛出异常时执行的,异常原因:" + ex.getMessage());
}
}
测试:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
OneService oneServiceImpl = (OneService) context.getBean("oneServiceImpl");
oneServiceImpl.doTwo();
最终通知
无论目标方法是否抛出异常,该增强均会被执行。
- 代码示例
业务方法的实现:
@Override
public void doThird() {
System.out.println("这是doThird方法");
}
切面类:
package com.yu.aspects;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class OneAspect {
/** 最终通知方法定义格式
* 1、public。
* 2、没有返回值。
* 3、方法名称自定义。
* 4、方法没有参数。
* */
/** @AfterThrowing:异常通知
* 属性:
* 1、value:切入点表达式
*
* 特点:
* 1、在目标方法之后执行。
* 2、总是会执行。
* */
@After(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))")
public void around() {
//一般做资源清理工作
System.out.println("执行最终通知,总是会被执行的代码。");
}
}
测试:
oneServiceImpl.doThird();
- 加上异常:
还是会执行:
@Pointcut 定义切入点
1、当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。
2、AspectJ 提供了 @Pointcut 注解,用于定义 execution 切入点表达式。其用法是,将 @Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。
3、代表的就是 @Pointcut 定义的切入点。这个使用 @Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
- 代码示例
package com.yu.aspects;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class OneAspect {
/**
* 用来定义和管理切面点,简化切入点的定义。
* 便于复用。
* */
@Pointcut(value = "execution(* com.yu.service.impl.OneServiceImpl.*(..))")
private void myPc(){
//无需代码
}
@After(value = "myPc()")
public void after() {
System.out.println("执行最终通知,总是会被执行的代码。");
}
}
设置 AspectJ 实现 AOP的方式
在 Spring 配置文件中,通过<aop:aspectj-autoproxy/>
的 proxy-target-class 属性设置选择通过 JDK 动态代理还是 cglib 动态代理实现 AOP。
<!--声明自动代理生成器:使用aspectj把spring容器中目标类对象生成代理
proxy-target-class="true"表示使用cglib动态代理
1、目标类有接口,默认使用jdk动态代理。
2、目标类没有接口,默认时候cglib动态代理
3、目标类有接口,也可以使用cglib动态代理,需要设置proxy-target-class="true"
-->
<!-- <aop:aspectj-autoproxy proxy-target-class="true" />-->
<aop:aspectj-autoproxy/>
AspectJ 基于 XML 的 AOP 实现
待完善。。。
Spring 实现 AOP
待完善。。。
集成 MyBatis 和 Spring
用的技术是:ioc 。
为什么ioc:能把mybatis和spring集成在一起,像一个框架, 是因为ioc能创建对象。
可以把mybatis框架中的对象交给spring统一创建, 开发人员从spring中获取对象。
开发人员就不用同时面对两个或多个框架了, 就面对一个spring
mybatis使用步骤,对象
1.定义dao接口 ,StudentDao
2.定义mapper文件 StudentDao.xml
3.定义mybatis的主配置文件 mybatis.xml
4.创建dao的代理对象,
StudentDao dao = SqlSession.getMapper(StudentDao.class);
List<Student> students = dao.selectStudents();
要使用dao对象,需要使用getMapper()方法,
怎么能使用getMapper()方法,需要哪些条件
1.获取SqlSession对象, 需要使用SqlSessionFactory的openSession()方法。
2.创建SqlSessionFactory对象。 通过读取mybatis的主配置文件,能创建SqlSessionFactory对象
需要SqlSessionFactory对象, 使用Factory能获取SqlSession ,有了SqlSession就能有dao , 目的就是获取dao对象
Factory创建需要读取主配置文件
我们会使用独立的连接池类替换mybatis默认自己带的, 把连接池类也交给spring创建。
主配置文件:
1.数据库信息
<environment id="mydev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库的驱动类名-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!--连接数据库的url字符串-->
<property name="url" value="jdbc:mysql://localhost:3306/springdb"/>
<!--访问数据库的用户名-->
<property name="username" value="root"/>
<!--密码-->
<property name="password" value="123456"/>
</dataSource>
- mapper文件的位置
<mappers>
<mapper resource="com/bjpowernode/dao/StudentDao.xml"/>
<!--<mapper resource="com/bjpowernode/dao/SchoolDao.xml" />-->
</mappers>
代码示例
1、新建 t_student 表。
2、加入 pom 依赖。
Junit 依赖;
spring 依赖;
mybatis 的依赖;
mybatis-spring 依赖;
mysql 的驱动;
spring-jdbc 依赖;
druid,数据库连接池的依赖;
资源插件。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yu</groupId>
<artifactId>demo</artifactId>
<version>1.0</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 告诉 maven 在 jdk1.8 上编译 -->
<maven.compiler.source>1.8</maven.compiler.source>
<!-- 在 jdk1.8 上运行 -->
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.5</version>
</dependency>
<!--mybatis整合spring的依赖:创建mybatis对象-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
<!--事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.5</version>
</dependency>
<!--spring访问数据库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.5</version>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main</directory><!--所在的目录-->
<includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<!-- filtering 选项 false 不启用过滤器, *.property 已经起到过滤的作用了 -->
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3、实体类 Student。
package com.yu.domain;
import org.springframework.stereotype.Component;
@Component
public class Student {
private Integer id;
private Integer stuNo;
private String stuName;
private Integer classNo;
//有参无参
//set、get
//toString
}
4、新建 Dao 接口和 sql 映射文件。
package com.yu.dao;
import com.yu.domain.Student;
import java.util.List;
public interface StudentDao {
int insertStudent(Student stu);
List<Student> selectStudent();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yu.dao.StudentDao">
<select id="selectStudent" resultType="com.yu.domain.Student">
select id,stuno,stuname,classno from t_student
</select>
<insert id="insertStudent">
insert into t_student values(#{id},#{stuNo},#{stuName},#{classNo})
</insert>
</mapper>
5、mybatis主配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--settings:控制 Mybatis 全局行为-->
<settings>
<!--配置日志功能-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--设置别名-->
<typeAliases>
<!--实体类所在的包名-->
<package name="com.yu.domain"/>
</typeAliases>
<mappers>
<!--
一次加载包中的所有mapper.xml文件
条件:
1、sql 映射文件名和 Dao 接口名一致;
2、sql 映射文件和 Dao 接口在同一目录。
-->
<mapper resource="java/com/yu/dao/StudentDao.xml"/>
</mappers>
</configuration>
6、新建 Service 接口和实现类,在实现类中有 Dao 的属性。
package com.yu.service;
import com.yu.domain.Student;
import java.util.List;
public interface StudentService {
int addStudent(Student student);
List<Student> queryStudent();
}
package com.yu.service.impl;
import com.yu.dao.StudentDao;
import com.yu.domain.Student;
import com.yu.service.StudentService;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class StudentServiceImpl implements StudentService {
private StudentDao studentDao;
public StudentServiceImpl(StudentDao studentDao) {
}
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public int addStudent(Student student) {
int nums = studentDao.insertStudent(student);
return nums;
}
@Override
public List<Student> queryStudent() {
List<Student> students = studentDao.selectStudent();
return students;
}
}
7、spring 配置文件。
声明数据源 DataSource 对象;
声明 SqlSessionFactoryBean,创建 SqlSessionFactory 对象;
声明 MyBatis 的 MapperScannerConfigurer 扫描器,创建 Dao 接口的实现类对象;
声明自定义的 Service ,把 Dao 对象注入赋值给 Service 的属性。
<?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
https://www.springframework.org/schema/context/spring-context.xsd">
<!--声明组件扫描器(component-scan):指定注解所在的包名-->
<context:component-scan base-package="com.yu"/>
<!--读取配置文件
location:指定属性配置文件的路径
"classpath:":关键字表示类文件,也就是class文件所在的目录
-->
<context:property-placeholder location="classpath:jdbc.properties" />
<!--声明数据源DataSource-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<!--读取属性配置文件的key的值,使用 ${key}-->
<!--数据库的uri-->
<property name="url" value="${url}"/> <!--setUrl()-->
<!--数据库的用户名-->
<property name="username" value="${user}"/> <!--setUser()-->
<!--访问密码-->
<property name="password" value="${password}" /><!--setPassoword()-->
<!--最大连接数-->
<property name="maxActive" value="${max}"/>
</bean>
<!--
DruidDataSource myDataSource = new DruidDataSource();
myDataSource.setUrl();
myDataSource.setUsername();
myDataSource.setPassword();
myDataSource.init();
-->
<!--声明SqlSessionFactoryBean,创建SqlSessionFactory对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--数据源-->
<property name="dataSource" ref="myDataSource" />
<!--指定mybatis的主配置文件-->
<property name="configLocation" value="mybatis-config.xml" />
</bean>
<!--声明MyBatis的扫描器,创建Dao接口的实现类对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定SqlSessionFactory对象,能获取SqlSession-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<!--指定Dao接口的包名,框架会把这个包中的所有接口一次创建出Dao对象-->
<property name="basePackage" value="com.yu.dao" />
</bean>
<!--
从spring中获取SqlSessionFacotory,因为spring是一个容器(Map)
SqlSessionFactory factory = map.get("sqlSessionFactory");
SqlSession session = factory.openSession();
for(接口:com.bjpowernode.dao)
{
Dao对象 = session.getMapper(接口)
//把创建好的对象放入到spring容器中
spring的Map.put( 接口名的首字母小写, Dao对象 )
}
-->
<!--声明Service-->
<bean id="studentService" class="com.yu.service.impl.StudentServiceImpl">
<property name="studentDao" ref="studentDao" />
</bean>
</beans>
jdbc.properties:
url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
user=root
password=?
max=20
8、测试
public class TestSpring {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService studentService = (StudentService) context.getBean("studentService");
List<Student> studentList = studentService.queryStudent();
System.out.println(studentList);
}
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService studentService = (StudentService) context.getBean("studentService");
Student student = new Student(9, 20211, "罗十一", 202);
int result = studentService.addStudent(student);
/*spring整合mybatis默认提交事务*/
System.out.println(result);
}
}
test1:
test2:
spring 整合 mybatis 默认提交事务。
总结
通过以上的说明,我们需要让spring创建以下对象
1.独立的连接池类的对象, 使用阿里的druid连接池
2.SqlSessionFactory对象
3.创建出dao对象
需要学习就是上面三个对象的创建语法,使用xml的bean标签。
连接池
待完善。。。
多个连接 Connection 对象的集合,List<Connection> connlist
,connList 就是连接池。
1、通常使用Connection访问数据库
Connection conn =DriverManger.getConnection(url,username,password);
Statemenet stmt = conn.createStatement(sql);
stmt.executeQuery();
conn.close();
2、使用连接池
在程序启动的时候,先创建一些 Connection
Connection c1 = ...
Connection c2 = ...
Connection c3 = ...
List<Connection> connlist = new ArrayLits();
connList.add(c1);
connList.add(c2);
connList.add(c3);
Connection conn = connList.get(0);
Statemenet stmt = conn.createStatement(sql);
stmt.executeQuery();
把使用过的connection放回到连接池
connList.add(conn);
Connection conn1 = connList.get(1);
Statemenet stmt = conn1.createStatement(sql);
stmt.executeQuery();
把使用过的connection放回到连接池
connList.add(conn1);
Spring 的事务处理
1、什么是事务
讲mysql的时候,提出了事务。 事务是指一组sql语句的集合, 集合中有多条sql语句
可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,
或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。
2、在什么时候想到使用事务
当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。需要保证
这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。
在java代码中写程序,控制事务,此时事务应该放在那里呢?
service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句
3、通常使用JDBC访问数据库, 还是mybatis访问数据库怎么处理事务
jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback();
hibernate访问数据库,处理事务, Session.commit(); Session.rollback();
4、3 问题中事务的处理方式,有什么不足
1)不同的数据库访问技术,处理事务的对象,方法不同,
需要了解不同数据库访问技术使用事务的原理
2)掌握多种数据库中事务的处理逻辑。什么时候提交事务,什么时候回顾事务
3)处理事务的多种方法。
总结: 就是多种数据库的访问技术,有不同的事务处理的机制,对象,方法。
5、怎么解决不足
spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。
使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理
使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。
6、处理事务,需要怎么做,做什么
spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了
1)事务内部提交,回滚事务,使用的事务管理器对象,代替你完成commit,rollback
事务管理器是一个接口和他的众多实现类。
接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback
实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
mybatis访问数据库---spring创建好的是DataSourceTransactionManager
hibernate访问数据库----spring创建的是HibernateTransactionManager
怎么使用:你需要告诉spring 你用是那种数据库的访问技术,怎么告诉spring呢?
声明数据库访问技术对于的事务管理器实现类, 在spring的配置文件中使用
例如,你要使用mybatis访问数据库,你应该在xml配置文件中
<bean id=“xxx" class="...DataSourceTransactionManager">
7、你的业务方法需要什么样的事务,说明需要事务的类型。
说明方法需要的事务:
1)事务的隔离级别:有4个值。
DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
➢ SERIALIZABLE:串行化。不存在并发问题。
2)事务的超时时间: 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
单位是秒, 整数值, 默认是 -1.
3)事务的传播行为 : 控制业务方法是不是有事务的, 是什么样的事务的。
7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的。
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
以上三个需要掌握的
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
8、事务提交事务,回滚事务的时机
1)当你的业务方法,执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。事务管理器commit
2)当你的业务方法抛出运行时异常或ERROR, spring执行回滚,调用事务管理器的rollback
运行时异常的定义: RuntimeException 和他的子类都是运行时异常, 例如NullPointException , NumberFormatException
3) 当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务
受查异常:在你写代码中,必须处理的异常。例如IOException, SQLException
总结spring的事务
1.管理事务的是 事务管理和他的实现类
2.spring的事务是一个统一模型
1)指定要使用的事务管理器实现类,使用
2)指定哪些类,哪些方法需要加入事务的功能
3)指定方法需要的隔离级别,传播行为,超时
你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为。
spring框架中提供的事务处理方案
1.适合中小项目使用的, 注解方案。
spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务。
@Transactional注解是spring框架自己注解,放在public方法的上面,表示当前方法具有事务。
可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等
使用@Transactional的步骤:
1.需要声明事务管理器对象
2.开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务。
spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。
spring给业务方法加入事务:
在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知
@Around("你要增加的事务功能的业务方法名称")
Object myAround(){
开启事务,spring给你开启
try{
buy(1001,10);
spring的事务管理器.commit();
}catch(Exception e){
spring的事务管理器.rollback();
}
}
3.在你的方法的上面加入@Trancational
2.适合大型项目,有很多的类,方法,需要大量的配置事务,使用aspectj框架功能,在spring配置文件中
声明类,方法需要的事务。这种方式业务方法和事务配置完全分离。
实现步骤: 都是在xml配置文件中实现。
1)要使用的是aspectj框架,需要加入依赖
2)声明事务管理器对象
-
声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)
-
配置aop:指定哪些哪类要创建代理。
代码示例
配置 xml:
<!--使用spring的事务管理器-->
<!--1、声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="myDataSource"/>
</bean>
<!--2、开启事务注解驱动(使用注解管理事务,创建代理对象)
transaction-manager:事务管理器对象的id
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
在选择 annotation-driven 时选择下面这个:
在公共业务方法上面加上注解:
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,
/*rollbackFor 表示发生指定的异常一定回滚*/
/*
* 处理逻辑是:
* 1、spring 会首先检查方法抛出的异常是不是在 rollbackFor 的属性值中,
* 如果异常在 rollbackFor 列表中,不管是什么类型的异常,一定回滚。
* 2、如果抛出的异常不在 rollbackFor 列表中,
* spring 会判断异常是不是 RuntimeException,如果是一定回滚。
* */
rollbackFor = {
NullPointerException.class,
ArrayIndexOutOfBoundsException.class
/*...*/
}
)
可以直接使用 @Transactional,不用添加属性。使用的是事务控制的默认值。默认的传播行为是 REQUIRED;默认的隔离级别 DEFAULT;默认抛出运行时异常,回滚事务。
================================================================================
第六章: web项目中怎么使用容器对象。
1.做的是javase项目有main方法的,执行代码是执行main方法的,
在main里面创建的容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
2.web项目是在tomcat服务器上运行的。 tomcat一起动,项目一直运行的。
需求:
web项目中容器对象只需要创建一次, 把容器对象放入到全局作用域ServletContext中。
怎么实现:
使用监听器 当全局作用域对象被创建时 创建容器 存入ServletContext
监听器作用:
1)创建容器对象,执行 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
2)把容器对象放入到ServletContext, ServletContext.setAttribute(key,ctx)
监听器可以自己创建,也可以使用框架中提供好的ContextLoaderListener
private WebApplicationContext context;
public interface WebApplicationContext extends ApplicationContext
ApplicationContext:javase项目中使用的容器对象
WebApplicationContext:web项目中的使用的容器对象
把创建的容器对象,放入到全局作用域
key: WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
value:this.context
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);