文章目录
- 1.Spring简介
- 1.1.什么是Spring
- 1.2.Spring框架结构
- 2.Spring资源管理
- 2.1.Spring资源管理简介
- 2.2.Spring资源管理案例
- 3.Spring容器创建
- 3.1.IOC容器创建的三种方法
- 3.2.从IOC容器getBean的三种方法
- 3.3.Spring实例化bean的3种方法
- 4.SpringIOC依赖注入
- 4.1.SpringIOC依赖注入简介
- 4.2.SpringIOC构造器注入
- 4.3.SpringIOC属性set方法注入
- 4.4.SpringIOC集合属性的注入
- 4.5.SpringIOC空间命名
- 4.6.depends-on属性
- 4.7.延迟加载lazy-init
- 5.SpringIOC自动注入
- 5.1.Spring容器管理注解
- 5.2.AutoWired注入
- 5.3.接口多个实现类的注入方式
- 5.4.Spring的5种自动装配模式
- 5.5.注入方式总结
- 6.Spring Bean的管理
- 6.1Bean的scope属性
- 6.2.Spring bean生命周期回调
- 6.3.钩子关闭IOC容器
- 6.4.BeanPostProcessor接口
- 6.5.FactoryBean接口
- 6.6.JSR注解
- 7.Spring表达式语言(SpEL)
- 7.1.SpEL简介
- 7.2.SpEL基本语法
- 8.SpringAOP
- 8.1.SpringAOP简介
- 8.2.AOP编程XML方式声明
- 8.3.AOP编程注解方式声明
- 8.4.JDK动态代理
- 8.5.CGLIB动态代理
- 9.Spring事务管理
- 9.1.事务的分类
- 9.2.Spring声明性事务
- XML管理声明性事务
- 注解方式管理声明式事务
- 9.3.事务传播特性
- 9.4.事务的四个关键属性
- 9.5.事务隔离级别
1.Spring简介
1.1.什么是Spring
- Spring是一个轻量级的框架,能够简化企业级应用开发,减少代码量。
Spring的核心框架是AOP(面向切面)与IOC(控制反转),IOC说白了就是跟容器要对象,DI(依赖注入)就是给属性赋值,AOP就是根据动态代理分为切面,切入点和通知。Spring还提供对JDBC的轻量级封装,还提供声明事务。Spring还根据MVC设计模式开发出SpringMVC框架。
(1)Spring框架目标
- 使用声明式事务,向EJB挑战
- 框架整合,像胶水一样整合多个框架.
(2)什么是JavaEE
- JAVAEE:Java Enterprise Edition(java企业级版本)是Sun制定的一套java开发规范
- 是由一系列的JSR组成
(3)什么是JSR
- Java Specification Requests Java规范提案
(4)JAVAEE容器和组件
容器
Applet Container
Web Container
Application Client Container
EJB Container
组件
Applet
JSP Servlet
Java Bean
EJB JavaBean
(5)Spring特点
- Ioc:解耦使用IoC机制避免硬编码造成程序耦合
- AOP:Aspect Orentied Programing 面向切面的编程
- 声明式事务管理
- 对JDBC进行轻量级封装,更加灵活的操作数据库
- Spring提供MVC模式支持:SpringMVC
- 提供文件上传,定时器常用工具类
- 对其他优秀框架的支持(集成其他框架)
1.2.Spring框架结构
(1)Spring的核心功能
- IoC容器
- Bean生命周期管理
- SpEL
- AOP容器
- 注解体系
- 数据验证
(2)Spring的好处
- 方便解耦
- AOP编程支持
- 声明式事务的支持
- 方便程序的测试
- 方便集成各种优秀框架
- 降低JavaEE API的使用难度
2.Spring资源管理
2.1.Spring资源管理简介
(1)Spring资源管理的特点
- 隐藏底层的实现
- 新增资源存在判断、资源操作权限相关的功能
- 支持通配符获取资源
(2)Spring管理哪些资源
- UrlResource
- ClassPathResource
- FileSystemResource
- ServletContextResource
- InputStreamResource
- ByteArrayResource
(3)资源协议与路径
- classpath:从当前jvm的classpath根路径开始获取资源
- file:从操作系统的路径获取资源
- http(s):从互联网获取资源
- 无标记:根据应用上下文获取资源
(4)Spring使用什么访问底层资源
Spring使用Resource接口访问底层资源
(5)Spring使用什么接口加载资源
Spring使用ResourceLoader接口加载资源
IOC容器实现了ResourceLoader接口,可以随时使用它的**getResource(location)**加载资源
2.2.Spring资源管理案例
- 创建maven工程,写HelloService
public class HelloService {
public void sayHello(String name){
System.out.println(name+":你好!");
}
}
- 引入spring-framework依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.15.RELEASE</version>
</dependency>
</dependencies>
- 在main/resources下创建applicationContext.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="helloService" class="com.tjetc.service.HelloService">
</bean>
</beans>
- 编写测试类
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloService helloService = (HelloService) context.getBean("helloService");
helloService.sayHello("李祥");
}
}
运行结果
李祥:你好!
3.Spring容器创建
3.1.IOC容器创建的三种方法
- 使用ClassPathXmlApplicationContext创建IOC容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
- 使用FileSystemXmlApplicationContext创建容器
此时,applicationContext.xml 的路径要写磁盘的物理路径
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("D:\\ideaworkspaces\\0919-spring\\01-spring\\src\\main\\resources\\applicationContext.xml");
- 使用XmlWebApplicationContext创建容器
使用XmlWebApplicationContext时要导入Tomcat,和spring-webmvc依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
XmlWebApplicationContext context = new XmlWebApplicationContext();
//设置配置文件位置
context.setConfigLocation("classpath:applicationContext.xml");
//调用refresh()方法
context.refresh();
HelloService helloService = context.getBean("helloService",HelloService.class);
helloService.sayHello("李祥");
3.2.从IOC容器getBean的三种方法
- 容器对象.getBean(“bean的id”),精确定位,需要强制转换
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloService helloService = (HelloService) context.getBean("helloService");
helloService.sayHello("李祥");
- 容器对象.getBean(“bean的id”,bean.class),精确定位,不需要强制转换
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloService helloService = context.getBean("helloService",HelloService.class);
helloService.sayHello("李祥");
- 容器对象.getBean(bean.class),不需要强制转换
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloService helloService = context.getBean(HelloService.class);
helloService.sayHello("李祥");
3.3.Spring实例化bean的3种方法
- 使用默认的构造方法创建bean对象
容器实例化会调用bean的默认无参数的构造方法,代码请看入门案例
- 静态工厂方法创建bean对象
- 创建工厂类HelloServiceFactory
public class HelloServiceFactory {
public static HelloService helloServiceFactory(){ //注意是静态方法
System.out.println("helloServiceFactory");
return new HelloService();
}
}
- applicationContext.xml文件编写
<bean id="helloServiceFactory" class="com.tjetc.factory.HelloServiceFactory" factory-method="helloServiceFactory">
</bean>
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloService helloService = context.getBean(HelloService.class);
helloService.sayHello("李祥");
- 实例工程方法创建bean
- 创建工厂类HelloServiceFactory
public class HelloServiceFactory {
public HelloService helloServiceFactory(){ //注意是非静态方法
System.out.println("helloServiceFactory");
return new HelloService();
}
}
- applicationContext.xml文件
<bean id="helloServiceFactory" class="com.tjetc.factory.HelloServiceFactory">
</bean>
<bean id="helloService" factory-bean="helloServiceFactory" factory-method="helloServiceFactory">
</bean>
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloService helloService = context.getBean(HelloService.class);
helloService.sayHello("李祥");
4.SpringIOC依赖注入
4.1.SpringIOC依赖注入简介
(1)SpringIOC的特点
- Spring是轻量级容器+组件的管理模式
(2)什么是控制反转
- 创建对象的权利由应程序创建改为由容器创建,控制权的转移称为控制反转.
(3)什么是依赖注入
- 把容器创建好的依赖对象注入进来的过程称为依赖注入.
(4)依赖注入的目标
- 提升组件重用的概率
- 为系统搭建一个灵活、可扩展的平台
(5)Spring Framework的 IoC 容器操作基于两个包
- org.springframework.beans 和 org.springframework.context
一个业务系统,由很多bean对象组成,bean之间存在调用关系,那么这些bean对象是如何协同工作的呢? 对象之间的调用,存在依赖关系,依赖对象的注入,称为依赖注入.
4.2.SpringIOC构造器注入
构造器注入bean子节点constructor-arg节点.可以使用constructor-arg节点属性index,name,type
基本类型注入:使用value
引用类型注入:使用ref
- index构造方法参数的索引
准备一个实体类
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
applicationContext.xml中配置
<bean id="student" class = "com.tjetc.domain.Student">
<constructor-arg index="0" value="张三"></constructor-arg> <!--构造器注入用constructor-arg-->
<constructor-arg index="1" value="18"></constructor-arg>
</bean>
测试代码
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean(Student.class);
System.out.println(student);
}
- name构造方法的名称
实体类和测似代码不动,只要改变配置文件即可
<bean id="student" class = "com.tjetc.domain.Student">
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
</bean>
- type构造方法参数的类型
实体类和测似代码不动,只要改变配置文件即可
<bean id="student" class = "com.tjetc.domain.Student">
<constructor-arg type="java.lang.String" value="张三"></constructor-arg>
<constructor-arg type="int" value="16"></constructor-arg>
</bean>
注意如果是引用类型的bean用ref=“bean的id”
4.3.SpringIOC属性set方法注入
name是setXxx() 方法后单词的首字母变小写后的单词,不是属性名。
value代表的是值
ref代表的是引用类型的bean的id 的值
实体类中必须要有setter 、getter方法,以及无参构造
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
applicationContext.xml配置文件
<bean id="student" class = "com.tjetc.domain.Student">
<property name="name" value="张三"></property>
<property name="age" value="13"></property>
</bean>
测试代码
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean(Student.class);
System.out.println(student);
}
set注入与构造器注入的对比
Set注入模式代码更加简洁
构造器注入对依赖关系的表达更加清楚
Set注入可以避免循环依赖问题
4.4.SpringIOC集合属性的注入
- list、数组基本类型语法
<property name="hobbies">
<list>
<value>吃</value>
<value>喝</value>
<value>玩</value>
<value>乐</value>
</list>
</property>
- list、数组引用类型语法
<property name="books">
<list>
<ref bean="book1"/>
<ref bean="book2"/>
<ref bean="book3"/>
</list>
</property>
- set基本数据类型语法
<property name="hobbies">
<set>
<value>吃</value>
<value>喝</value>
<value>玩</value>
<value>乐</value>
<value>读书</value>
</set>
</property>
- set引用数据类型语法
<property name="books">
<set>
<ref bean="book1"/>
<ref bean="book2"/>
<ref bean="book3"/>
</set>
</property>
- map基本类型语法
<property name="hobbies">
<map>
<entry key="chi" value="吃"></entry>
<entry key="he" value="喝"></entry>
<entry key="wan" value="完"></entry>
</map>
</property>
- map引用类型语法
<property name="books">
<map>
<entry key="xi" value-ref="book1"></entry>
<entry key="dong" value-ref="book2"></entry>
<entry key="nan" value-ref="book3"></entry>
</map>
</property>
- properties语法
<property name="properties">
<props>
<prop key="pk1">pv1</prop>
<prop key="pk2">pv2</prop>
<prop key="pk3">pv3</prop>
<prop key="pk4">pv4</prop>
</props>
</property>
4.5.SpringIOC空间命名
(1)p空间命名
先要引入p空间命名
xmlns:p="http://www.springframework.org/schema/p"
基本数据类型
p:属性=值
<bean id="student" class = "com.tjetc.domain.Student" p:name="张三" p:age="12">
</bean>
引用数据类型
p:属性-ref=另一个bean的id的值
<bean id="book" class="com.tjetc.domain.Book" p:name="西游记" p:price="30">
</bean>
<bean id="student" class="com.tjetc.domain.Student" p:name="张三" p:book-ref="book">
</bean>
(2)c空间命名
先要引入c空间命名
xmlns:c="http://www.springframework.org/schema/c"
基本数据类型
c:构造方法参数名=值
<!--用属性名称注入-->
<bean id="student" class = "com.tjetc.domain.Student" c:name="张三" c:age="12">
</bean>
<!--用索引下标注入-->
<bean id="student" class = "com.tjetc.domain.Student" c:_0="张三" c:_1="12">
</bean>
引用数据类型
c:构造方法参数名-ref=另一个bean的id的值
<bean id="book" class="com.tjetc.domain.Book" p:name="西游记" p:price="30">
</bean>
<bean id="student" class="com.tjetc.domain.Student" c:name="李四" c:book-ref="book">
</bean>
4.6.depends-on属性
B中没有A,在applicationContext.xml中配置depends-on=“a”,则先实例化A,再实例化B,销毁是先销毁B,再销毁A
依赖对象先创建后销毁.
public class Student {
private String name;
public Student() {
System.out.println("Student()");
}
public void init(){
System.out.println("Student.init()");
}
public void destroy(){
System.out.println("Student.destroy()");
}
}
public class MyClass {
public String cname;
public MyClass() {
System.out.println("MyClass()");
}
public void init(){
System.out.println("MyClass.init()");
}
public void destroy(){
System.out.println("MyClass.destroy()");
}
}
applicationContext.xml文件
<bean id="student" class="com.tjetc.domain.Student" depends-on="myClass" init-method="init" destroy-method="destroy">
</bean>
<bean id="myClass" class="com.tjetc.domain.MyClass" init-method="init" destroy-method="destroy">
</bean>
测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
context.close();
运行结果
MyClass() MyClass.init() Student() Student.init()
Student.destroy() MyClass.destroy()
4.7.延迟加载lazy-init
ApplicationContext的默认行为就是在创建IOC容器时将所有singleton 的bean提前进行实例化。
系统默认配置是lazy-init=“false”
当配置lazy-init="true"后,当第一次调用bean对象时,才进行实例
- lazy-init="true"创建IOC容器时没有实例化bean,当第一次调用context.getBean(C.class)才进行实例化bean
<bean id="c" class="com.tjetc.domain.C" lazy-init="true"></bean>
- lazy-init="true"只对当前的bean有效,对其他的bean不起作用
<bean id="c" class="com.tjetc.domain.C" lazy-init="true"></bean>
<bean id="d" class="com.tjetc.domain.D"></bean>
- beans节点设置 default-lazy-init=“true” ,让所有的bean都设置成懒加载.
<beans default-lazy-init="true" 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
https://www.springframework.org/schema/context/spring-context.xsd
">
<bean id="c" class="com.tjetc.domain.C"></bean>
<bean id="d" class="com.tjetc.domain.D"></bean>
</beans>
- default-lazy-init=“true” 所有bean懒加载的前提下,设置某个bean不懒加载 bean设置lazy-init=“false”
<bean id="d" class="com.tjetc.domain.D" lazy-init="false"></bean>
5.SpringIOC自动注入
5.1.Spring容器管理注解
Spring自动扫描,会把以下注解的bean纳入Spring容器管理
@Component不好分层时用该注解
@Controller控制层使用该注解
@Service业务层使用该注解
@Repository dao层使用该注解
5.2.AutoWired注入
- 添加context命名空间,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"
//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
//https://www.springframework.org/schema/context/spring-context.xsd
">
</beans>
- applicationContext.xml文件配置扫描基本包
<context:component-scan base-package="com.tjetc"></context:component-scan>
- @AutoWired自动注入
@Repository
public class UserDao {
public void login(){
System.out.println("用户登录");
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void login(){
userDao.login();
}
}
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService bean = context.getBean(UserService.class);
bean.login();
运行结果
用户登录
5.3.接口多个实现类的注入方式
- 准备UserDao接口,和两个实现类
public interface UserDao {
void login();
}
@Repository
public class UserMySqlDaoImpl implements UserDao {
public void login() {
System.out.println("UserMySqlDaoImpl.login()");
}
}
@Repository
public class UserOracleDaoImpl implements UserDao {
public void login() {
System.out.println("UserOracleDaoImpl.login()");
}
}
- 注意在UserService注入的时候,要在@Autowired下写@Qualifer(“bean的名字”)注解
@Service
public class UserService {
@Autowired
@Qualifier("userMySqlDaoImpl") //指定是哪个实现类
private UserDao userDao;
public void login(){
userDao.login();
}
}
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService bean = context.getBean(UserService.class);
bean.login();
- 运行结果
UserMySqlDaoImpl.login()
5.4.Spring的5种自动装配模式
no:默认情况,不自动装配,手动设置bean
byName:根据bean 的名字自动装配
byType:根据bean的数据类型自动装配
constructor:根据构造函数的参数的数据类型,进行byType模式的自动装配
autodetect:如果发现默认的构造函数,用constructor模式,否则,用byType模式
- 准备实体类Person、Customer
public class Customer {
private Person person;
public Customer(Person person) {
System.out.println("调用构造注入");
this.person = person;
}
public Customer() {
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Override
public String toString() {
return "Customer{" +
"person=" + person +
'}';
}
}
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
- 配置applicationContext.xml
no方式
<bean id="person" class = "com.tjetc.domain.Person">
<property name="name" value="李祥"></property>
</bean>
<bean id="customer" class="com.tjetc.domain.Customer" autowire="no">
<property name="person" ref="person"></property>
</bean>
byName方式
<bean id="person" class = "com.tjetc.domain.Person">
<property name="name" value="李祥"></property>
</bean>
<bean id="customer" class="com.tjetc.domain.Customer" autowire="byType">
</bean>
byType方式
<bean id="person" class = "com.tjetc.domain.Person">
<property name="name" value="李祥"></property>
</bean>
<bean id="customer" class="com.tjetc.domain.Customer" autowire="byType">
</bean>
constructor方式
<bean id="person" class = "com.tjetc.domain.Person">
<property name="name" value="李祥"></property>
</bean>
<bean id="customer" class="com.tjetc.domain.Customer" autowire="constructor">
</bean>
5.5.注入方式总结
6.Spring Bean的管理
6.1Bean的scope属性
(1)singleton单例(默认)
当scope的取值为singleton时 Bean的实例化个数:1个 Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“applicationContext.xml”);
对象运行:只要容器在,对象一直活着
对象销毁:当应用卸载,销毁容器时,对象就被销毁
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="singleton">
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao1 = (UserDao) app.getBean("userDao");
UserDao userDao2 = (UserDao) app.getBean("userDao");
System.out.println(userDao1);
System.out.println(userDao2);
打印的userDao1、userDao2地址相同
(2)prototype多例
Bean的实例化个数:多个 Bean的实例化时机:当调用getBean()方法时实例化Bean
对象创建:当使用对象时,创建新的对象实例
UserDao userDao1 = (UserDao) app.getBean(“userDao”);
对象运行:只要对象在使用中,就一直活着
对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl" scope="prototype">
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao1 = (UserDao) app.getBean("userDao");
UserDao userDao2 = (UserDao) app.getBean("userDao");
System.out.println(userDao1);
System.out.println(userDao2);
打印的userDao1、userDao2地址不同
(3)request ,session和global session
这三个类型是spring2.0之后新增的,他们不像singleton和prototype那么通用,因为他们只适用于web程序,通常是和XmlWebApplicationContext共同使用
<bean id ="requestPrecessor" class="...RequestPrecessor" scope="request" />
Spring容器,即XmlWebApplicationContext 会为每个HTTP请求创建一个全新的RequestPrecessor对象,当请求结束后,,该对象的生命周期即告结束。当同时有10个HTTP请求进来 的时候,容器会分别针对这10个请求创建10个全新的RequestPrecessor实例,且他们相互之间互不干扰,从不是很严格的意义上 说,request可以看做prototype的一种特例,除了场景更加具体之外,语意上差不多。
<bean id ="userPreferences" class="...UserPreferences" scope="session" />
Spring容器会为每个独立的session创建属于自己的全新的UserPreferences实例,他比request scope的bean会存活更长的时间,其他的方面真是没什么区别。
<bean id ="userPreferences" class="...UserPreferences" scope="globalsession" />
global session只有应用在基于porlet的web应用程序中才有意义,他映射到porlet的global范围的session,如果普通的servlet的web 应用中使用了这个scope,容器会把它作为普通的session的scope对待。
6.2.Spring bean生命周期回调
(1)xml文件配置方式
- 准备一个被spring容器管理的类
public class A {
public A(){
System.out.println("A()...");
}
public void init(){
System.out.println("A.init()...");
}
public void destroy(){
System.out.println("A.destroy()...");
}
}
- 在配置文件里配置bean、初始化方法、销毁方法
<bean id="a" class="com.tjetc.domain.A" init-method="init" destroy-method="destroy"></bean>
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A bean = context.getBean(A.class);
System.out.println(bean);
context.close();
- 运行结果
A()... A.init()... com.tjetc.domain.A@66a3ffec A.destroy()...
(2)JSR250注解@PostConstruct@PreDestroy
- spring容器管理的类加上@Component,在初始化方法上加上@PostConstruct注解,在销毁方法上加上@PreDestroy注解
@Component
public class A {
public A(){
System.out.println("A()...");
}
@PostConstruct
public void init(){
System.out.println("A.init()...");
}
@PreDestroy
public void destroy(){
System.out.println("A.destroy()...");
}
}
- applicationContext.xml
<context:component-scan base-package="com.tjetc"></context:component-scan>
- 测试代码不变
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A bean = context.getBean(A.class);
System.out.println(bean);
context.close();
- 运行结果
A()... A.init()... com.tjetc.domain.A@44a3ec6b A.destroy()...
(3)接口InitializingBean和DisposableBean
- spring容器管理的类要实现InitializingBean和DisposableBean接口
@Component
public class A implements InitializingBean, DisposableBean {
public A(){
System.out.println("A()...");
}
public void afterPropertiesSet() throws Exception {
System.out.println("A.afterPropertiesSet()...");
}
public void destroy() throws Exception {
System.out.println("A.destroy()...");
}
}
- applicationContext.xml配置基本扫描包
<context:component-scan base-package="com.tjetc"></context:component-scan>
- 测试代码不变
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A bean = context.getBean(A.class);
System.out.println(bean);
context.close();
- 运行结果
A()... A.afterPropertiesSet()... com.tjetc.domain.A@4516af24 A.destroy()...
6.3.钩子关闭IOC容器
- 使用钩子,在非WEB环境下,可以优雅的关闭IOC容器。
- 如富客户端的桌面环境,可以向JVM注册一个钩子。即使程序非正常退出,钩子函数也会被执行,这样在钩子函数中做环境清理工作,如关闭非托管资源,就是非常有效的方法。
- 注册一个shutdown hook,需要调用ConfigurableApplicationContext接口中的registerShutdownHook()方法。
@Test
public void testHook() throws Exception {
Runtime.getRuntime().addShutdownHook(new Thread(){//2.
@Override
public void run() {
System.out.println("钩子函数清除垃圾");
}
});
ConfigurableApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
context.registerShutdownHook();//1.在容器关闭时使用上面的钩子函数释放资源
context.close();
System.out.println("我要正常关闭了");
}
6.4.BeanPostProcessor接口
bean 的后置处理接口
IOC容器生成bean对象后,在bean初始化前后,你可以通过BeanPostProcessor接口定制你的业务逻辑,如日志跟踪等。
配置BeanPostProcessor后Bean的使用过程如下:
- 写一个类实现InitializingBean, DisposableBean接口
@Component
public class A implements InitializingBean, DisposableBean {
public A(){
System.out.println("A()...");
}
public void afterPropertiesSet() throws Exception {
System.out.println("A.afterPropertiesSet()...");
}
public void destroy() throws Exception {
System.out.println("A.destroy()...");
}
}
- 写一个类LogBean实现BeanPostProcessor接口,重写2个方法
@Component
public class LogBean implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("LogBean.postProcessBeforeInitialization():"+bean+":"+beanName);
return null;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("LogBean.postProcessAfterInitialization():"+bean+":"+beanName);
return null;
}
}
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A bean = context.getBean(A.class);
System.out.println(bean);
context.close();
- 运行结果
A()... LogBean.postProcessBeforeInitialization():com.tjetc.domain.A@10dba097:a A.afterPropertiesSet()... LogBean.postProcessAfterInitialization():com.tjetc.domain.A@10dba097:a com.tjetc.domain.A@10dba097 A.destroy()...
6.5.FactoryBean接口
- FactoryBean就是对一个复杂Bean的包装,可以在FactoryBean中进行初始化,然后把初始化的值传给它包装的对象。
- FactoryBean接口在Spring framework框架自身,有大量的实现,如用于创建动态代理对象的ProxyFactoryBean。
- 实现FactoryBean中的getObject()方法,返回真正需要的对象。
- 首先准备一个类,用于存放数据库连接属性
public class JDBCTest {
private String url;
private String username;
private String password;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
- 加入maven依赖,mysql,spring-jdbc
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
配置数据源和基本扫描包在applicationContext.xml
<context:component-scan base-package="com.tjetc"></context:component-scan>
<bean id = "dataSource" class ="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///test"></property>
<property name="username" value="root"></property>
<property name = "password" value="123456"></property>
</bean>
- 写一个工厂类实现FactoryBean,InitializingBean接口,在初始化中完成属性的赋值
@Component
public class MyFactoryBean implements FactoryBean<JDBCTest>, InitializingBean {
@Autowired
private DriverManagerDataSource dataSource;
private JDBCTest jdbcTest;
public JDBCTest getObject() throws Exception {
return jdbcTest;
}
public Class<?> getObjectType() {
return JDBCTest.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() throws Exception {
Connection connection = dataSource.getConnection();
System.out.println("链接对象为:"+connection);
jdbcTest = new JDBCTest();
jdbcTest.setUrl(dataSource.getUrl());
jdbcTest.setUsername(dataSource.getUsername());
jdbcTest.setPassword(dataSource.getPassword());
}
}
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
JDBCTest jdbcTest = (JDBCTest) context.getBean("myFactoryBean");
System.out.println(jdbcTest.getUrl());
System.out.println(jdbcTest.getUsername());
System.out.println(jdbcTest.getPassword());
- 运行结果
链接对象为:com.mysql.jdbc.JDBC4Connection@4b168fa9 jdbc:mysql:///test root 123456
6.6.JSR注解
(1)Spring与JSR330对应注解
Spring | javax.inject.* |
@Autowrid | @Inject |
@Component | @Named/@ManagedBean |
@Scope(“singleton”) | @Singleton |
@Qualifier | @Qualifier/@Named |
@Value | — |
@Required | — |
@Lazy | — |
ObjectFactory | Provider |
(2)@Inject 是JSR 330的注解,在使用@Autowired地方,可以使用 @Inject代替
@Service
public class UserService {
@Inject
private UserDao userDao;
public void login(){
userDao.login();
}
}
(3)@Named或者@ManagedBean,代替Component
@Named //@ManagedBean(需要javaee7.0 maven依赖)
public class MyFactoryBean implements FactoryBean<JDBCTest>, InitializingBean {
@Autowired
private DriverManagerDataSource dataSource;
private JDBCTest jdbcTest;
public JDBCTest getObject() throws Exception {
return jdbcTest;
}
public Class<?> getObjectType() {
return JDBCTest.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() throws Exception {
Connection connection = dataSource.getConnection();
System.out.println("链接对象为:"+connection);
jdbcTest = new JDBCTest();
jdbcTest.setUrl(dataSource.getUrl());
jdbcTest.setUsername(dataSource.getUsername());
jdbcTest.setPassword(dataSource.getPassword());
}
}
(4)@Resource代替@Inject、@Autowired
@Resource可以应用在属性、Set方法上,注入数据。
使用@Resource代替@Inject、@Autowired
与@Autowired相反,@Resource默认的装配方式是byName
如果不写name属性,用@Resource按名称装配,如果找不到就回退到按类型装配
如果写name属性,用@Resource(name=“userDaoMysql”)按名称装配,如果找不到就不能回退到按类型装配了
@Service
public class UserService {
@Resource
private UserDao userDao;
public void login(){
userDao.login();
}
}
(5)@Required
必须输入,只能用在属性setter,配置文件配置,不推荐使用
(6)@Configuration @Bean
Configuration //配置类,相当于applicationContext.xml
@Bean //
@Configuration
public class MyConfig {
@Bean
public UserDao userDao(){
return new UserMySqlDaoImpl();
}
}
(7)@Primary
多个相同对象使用@Primary优先采用哪一个对象
@Repository
@Primary
public class UserOracleDao implements UserDao {
@Override
public void login() {
System.out.println("UserOracleDao.login()...");
}
}
7.Spring表达式语言(SpEL)
7.1.SpEL简介
(1)什么是表达式语言
SpEL:Spring Expression Language, Spring 表达式语言
(2)SpEL特点
SpEL是强大的表达式语言。
支持运行时查询、操纵一个对象图功能。
SpEL语言的语法类似于EL,提供了更多的功能。
SpEL是一个基于技术中立的API,允许需要时与其他表达式语言集成。
SpEL与Spring不是直接绑定关系,它可以独立存在,并应用到其它平台。
7.2.SpEL基本语法
XML配置文件中使用:#{表达式}
Bean注解中使用:@Value(“#{表达式}”)
引用其他对象属性:#{对象名.属性}
(1)算数运算符
- 算数运算符:+, -, *, /, %, ^
1.xml中使用:#{表达式}
- 写一个student实体类
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 然后在applicationContext.xml中配置属性
<bean id="student" class="com.tjetc.domain.Student" >
<property name="name" value="#{'张三'}"></property>
<property name="age" value="#{10*2}"></property>
</bean>
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean(Student.class);
System.out.println(student);
- 运行结果
Student{name='李四', age=18}
2.Bean注解中使用 :@Value(“#{表达式 }”)
- 在实体类中用@Value(“#{10+8}”)注解赋值,加上@Component纳入spring容器管理
@Component
public class Student {
@Value("李四")
private String name;
@Value("#{10+8}")
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- applicationContext.xml文件,配置扫描包
<context:component-scan base-package="com.tjetc"></context:component-scan>
- 其他的代码不变
3.以用其他对象的属性:#{对象名.属性}
<bean id="a" class="com.tjetc.domain.A">
<property name="s" value="#{b.firstName}"></property>
</bean>
<bean id="b" class="com.tjetc.domain.B">
<property name="firstName" value="张"></property>
<property name="lastName" value="无忌"></property>
</bean>
4.使用类的静态变量:#{T(类的全路径名).静态变量名}
<bean id="a" class="com.tjetc.domain.A">
<property name="s" value="#{b.firstName}"></property>
<property name="d" value="#{T(java.lang.Math).PI}"></property>
</bean>
5.使用类的静态方法:#{T(类的全路径名).方法名(参数)}
<bean id="a" class="com.tjetc.domain.A">
<property name="s" value="#{b.firstName+' '+b.lastName}"></property>
<property name="d" value="#{T(java.lang.Math).max(3.1,12.3)}"></property>
</bean>
6.加号还可以作为字符串拼接
<bean id="a" class="com.tjetc.domain.A">
<property name="s" value="#{b.firstName+' '+b.lastName}"></property>
</bean>
<bean id="b" class="com.tjetc.domain.B">
<property name="firstName" value="张"></property>
<property name="lastName" value="无忌"></property>
</bean>
7.使用类的非静态方法:#{bean的id.方法名(参数)}
public class B {
public int sum() {
return 10+20;
}
}
<bean id="a" class="com.tjetc.domain.A">
<property name="i" value="#{b.sum()}"></property>
</bean>
<bean id="b" class="com.tjetc.domain.B"></bean>
(2)比较运算符
<,>==,>=,<=,!=,lt(小于),gt(大于),eq(等于),le(小于等于),ge(大于等于)
<property name="b" value="#{1 lt 1}"></property>
A [i=0, d=0.0, s=null, b=false]
(3)逻辑运算符号
and, or, not(!)
<property name="b" value="#{not(1==1 or 2==3)}"></property>
A [i=0, d=0.0, s=null, b=false]
(4)三目运算符
#{条件表达式?’true’:’false’}
<property name="s" value="#{2>1?'大于':'不大于'}"></property>
A [i=0, d=0.0, s=大于, b=false]
(5)正则表达式
表达式 | 说明 |
. | 除了换行符之外的任意字符 |
* | 匹配前面的子表达式零次或多次 |
/…/ | 代表一个模块的开启和结束 |
+ | 匹配前面的子表达式一次或多次 |
? | 匹配前面的子表达式零次或一次 |
{m} | 正好出现m次 |
{m,} | 至少m次 |
{,n} | 至少n次 |
{m,n} | 至少m次,至多n次 |
\d | 数字 |
\w | 字母数字下划线,单词 |
^ | 匹配输入字符串的开始位置 |
$ | 匹配输入字符串的结束位置 |
\s | 任何空白字符 |
\S | 任何非空白子符 |
\d | 匹配一个数字字符。等价于[0-9] |
\D | 匹配一个非数字字符。等价于[ ^ 0-9] |
\W | 匹配任何非单词字符。 |
- 语法
- #{变量名或值 matches ‘正则表达式’}
- 例子
<bean id="a" class="com.tjetc.domain.A">
<property name="b" value="#{b.firstName matches '1[3578]\d{9}'}"></property>
</bean>
<bean id="b" class="com.tjetc.domain.B"></bean>
A [i=0, d=0.0, s=大于, b=true]
8.SpringAOP
8.1.SpringAOP简介
(1)什么是AOP
- AOP:Aspect Oriented Programming 面向切面的编程
(2)AOP与OOP
- AOP:Aspect Oriented Programming 面向切面的编程
- OOP:Object Oriented Programming 面向对象的编程
(3)AOP与OOP的区别
- 面向目标不同:简单来说OOP是面向名词领域,AOP面向动词领域。
- 思想结构不同:OOP是纵向结构,AOP是横向结构。
- 注重方面不同:OOP注重业务逻辑单元的划分,AOP偏重业务处理过程的某个步骤或阶段。
- OOP与AOP联系:
- 两者之间是一个相互补充和完善的关系。
- AOP的优点:
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- Spring IOC容器不依赖AOP,如果不需要可以不导入AOP相关包。
(4)Spring AOP提供了两种模式
- 基于XML的模式
- 基于@AspectJ注解模式
(5)基于Spring 的 AOP,重要应用有哪些
- 用AOP声明性事务代替EJB的企业服务
- 用AOP做日志处理
- 用AOP做权限控制,如Spring Security
(6)AOP中的专业术语
- 连接点Joinpoint
- 方法的位置
- 程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。 Spring仅支持方法的连接点。
- 切点Pointcut
- 定位到方法的条件
- 每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点。但在这为数从多的连接点中,如何定位到某个感兴趣的连接点上?AOP通过“切点”定位特定连接点。
- 增强Advice
- 增强是织入到目标类连接点上的一段程序代码。增强既包含了用于添加到目标连接点上的一段执行逻辑,又包含了用于定位连接点的方位信息,所以SPRING所提供的增强接口都是带方位名的:BeforeAdvice,AfterReturningAdivce,throwsAdvice等等。
- 目标对象Target
- 增强逻辑的织入的目标类(被代理的目标类)
- 引介Introduction
- 引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口。通过AOP的引介功能,我们可以动态地为该业务类添加接口的实例逻辑,让业务类成为这个接口的实现类。
- 织入Weaving
- 织入是将增强添加对目标类具体连接点上的过程。
- 根据不同的实现技术,AOP有三种织入方式:
- 编译期织入,这要求使用特殊的JAVA编译器
- 类装载期织入,这要求使用特殊的类加载器;
- 动态代理织入,在运行期为目标类添加增强生成的方式。
- 代理Proxy
- 一个类被AOP增强后,就产出了一个结果类,它是整合了原类和增强逻辑的代理类。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类。所以我们可以采用调用原类相同的方式调用代理类。
- 切面Aspect
- 切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SPRINGAOP就是负责实施切面的框架,它将切面所定义的横切逻辑到切面所指定的连接点中。
(6)通知的类型(5种)
- before:前置通知(应用:各种校验)
- 在方法执行前执行,如果通知抛出异常,阻止方法运行
- afterReturning:后置通知(应用:常规数据处理)
- 方法正常返回后执行,如果方法中抛出异常,通知无法执行
- 必须在方法执行后才执行,所以可以获得方法的返回值。
- around:环绕通知(应用:十分强大,可以做任何事情)
- 方法执行前后分别执行,可以阻止方法的执行
- 必须手动执行目标方法
- afterThrowing:抛出异常通知(应用:包装异常信息)
- 方法抛出异常后执行,如果方法没有抛出异常,无法执行
- after:最终通知(应用:清理现场)
- 方法执行完毕后执行,无论方法中是否出现异常
8.2.AOP编程XML方式声明
(1)XML配置文件的方式声明切面
- 添加maven依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.15.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
- 写业务类(不写注解)
public class UserService {
public String login(String name){
System.out.println(name+":用户登录--login()...");
return name;
}
}
- 写切面类
public class TransactionPoint {
//增强部分
public void before(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
System.out.println("参数:"+arg);
}
System.out.println("前置增强");
}
public void afterReturning(){
System.out.println("后置增强");
}
public void after(){
System.out.println("最终增强");
}
public void afterThrowimg(){
System.out.println("例外增强");
}
/*
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object proceed = null;
try{
System.out.println("环绕增强开始");
proceed = pjp.proceed();
System.out.println("环绕增强结束");
}catch (Exception e){
System.out.println("环绕例外增强");
e.printStackTrace();
}finally {
System.out.println("环绕最终增强");
}
return proceed;
}*/
}
- applicationContext.xml配置bean和切面
<bean id="userService" class = "com.tjetc.service.UserService"></bean>
<bean id="transactionPoint" class = "com.tjetc.aspect.TransactionPoint"></bean>
<!-- aop配置 -->
<aop:config>
<!--配置切面 -->
<aop:aspect id="myaspect" ref="transactionPoint">
<!-- 切点 -->
<aop:pointcut expression="execution(* com.tjetc.service..*.*(..))" id="mycut"/>
<!-- 增强 -->
<aop:before method="before" pointcut-ref="mycut"/>
<aop:after-returning method="afterReturning" pointcut-ref="mycut"/>
<aop:after method="after" pointcut-ref="mycut"/>
<aop:after-throwing method="afterThrowimg" pointcut-ref="mycut"/>
</aop:aspect>
</aop:config>
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean(UserService.class);
userService.login("LiXiang");
- 运行结果
参数:LiXiang 前置增强 LiXiang:用户登录--login()... 后置增强 最终增强
(2)配置切面
1.切面的普通类:<bean id = "transactionPoint" class = "com.tjetc.aspect.TransactionPoint"></bean>
2.<aop:config>子节点<aop:aspect id = "myaspect" ref = "personAspect">启动和增强</aop:aspect>
(3)配置切点
1.切点配置的位置
(1)aop:config下,aop:config下所有的切面都能使用该切面
(2)aop:aspect下,只能是该切面能使用该切点
2.<aop:pointcut expression=”execution(* com.tjetc.service..*.*(..))” id=”mycut”>
(4)配置增强
1.增强的配置位置
aop:aspect下
2.增强5个
(1)前置增强
<aop:before method=”before” pointcut-ref=”mycut”>
(2)后置增强
<aop:after-returning method=”afterReturning” pointcut-ref=”mycut”>
(3)例外增强
<aop:after-throwing method=”afterThrowing” pointcut-ref=”mycut”>
(4)最终增强
<aop:after method=”after” pointcut-ref=”mycut”>
(5)环绕增强
<aop:around method=”around” pointcut-ref=”mycut”>
(5)后置增强的返回值
- 第一步:在配置文件的aop:after-returning添加属性returning=”变量名”
- 第二步:在切面类的afterReturning(Object 变量名)方法添加参数Object 变量名
- 第三步:测试调用有返回值的方法
<aop:after-returning method="afterReturning" pointcut-ref="mycut" returning="res"/>
public void afterReturning(JoinPoint jp,Object res) {
System.out.println("后置通知:res="+res);
}
(6)异常增强得到异常对象
- 第一步:在applicationContext.xml配置aop:after-throwing的属性throwing=”ex”
- 第二步:在切面类的afterThrowing(Exception ex)
- 第三步:在业务类的login()方法抛出异常
- 第四步:测试调用login()方法
<aop:after-throwing method="afterThrowing" pointcut-ref="mycut" throwing="ex"/>
public void afterThrowing(Exception ex) {
System.out.println("例外通知:ex="+ex);
}
(7)在增强里接受参数
- 使用JoinPoint接口的getArgs()接受参数
- 第一步:直接在切面类的增强方法里写JoinPoint jp参数
- 第二步:在方法体写jp.getArgs();得到数组,遍历数组得到每一个参数的值输出.
public void before(JoinPoint jp) {
Object[] args = jp.getArgs();
for (Object object : args) {
System.out.println("接收参数:"+object);
}
System.out.println("前置通知");
}
8.3.AOP编程注解方式声明
(1)AOP注解方式编程
@AspectJ是一种风格样式,可以把普通的java类声明为一个切面
- applicationContext.xml中添加AOP命名空间(1+2)
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
- 启动@Aspect注解支持
<aop:aspectj-autoproxy/>
- 启动注解扫包描机制
<context:component-scan base-package="com.tjetc"/>
- 添加aspectjweaver的maven依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
- 写切面类
@Component
@Aspect
public class TransactionPoint {
//切点
@Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件
private void anyMethod(){} //方法签名返回void类型
//增强部分
@Before("anyMethod()")
public void before(){
System.out.println("前置增强");
}
@AfterReturning("anyMethod()")
public void afterReturning(){
System.out.println("后置增强");
}
@After("anyMethod()")
public void after(){
System.out.println("最终增强");
}
@AfterThrowing("anyMethod()")
public void afterThrowimg(){
System.out.println("例外增强");
}
@Around("anyMethod()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知开始");
Object proceed = pjp.proceed();
System.out.println("环绕通知结束");
return proceed;
}
}
- 写业务类
@Service
public class UserService {
public void login(){
System.out.println("用户登录--login()...");
}
}
- 测试代码
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean(UserService.class);
userService.login();
- 运行结果
环绕通知开始 前置增强 用户登录--login()... 后置增强 最终增强 环绕通知结束
(2)测试异常
- 将UserService中加入除0异常
@Service
public class UserService {
public void login(){
System.out.println("用户登录--login()...");
System.out.println(1/0);
}
}
- 测试运行
前置增强 用户登录--login()... 例外增强 最终增强 Exception in thread "main" java.lang.ArithmeticException: / by zero
没有走后置增强,直接走的例外增强。
(3)环绕增强单独使用
- 切点类中只写around增强
@Component
@Aspect
public class TransactionPoint {
//切点
@Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件
private void anyMethod(){} //方法签名返回void类型
@Around("anyMethod()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object proceed = null;
try{
System.out.println("环绕增强开始");
proceed = pjp.proceed();
System.out.println("环绕增强结束");
}catch (Exception e){
System.out.println("环绕例外增强");
e.printStackTrace();
}finally {
System.out.println("环绕最终增强");
}
return proceed;
}
}
- 测试运行
环绕增强开始 用户登录--login()... 环绕增强结束 环绕最终增强
(4)联合使用pointcut表达式
在一个切面类中可以声明多个切点表达式,把几个切点签名用&& || !连起来使用
@Component
@Aspect//切面类=切点+增强
public class TransactionPrint {
//切点
@Pointcut("execution(* com.tjetc.service..*.*(..))") //定位到连接点的条件
private void anyMethod1(){} //方法签名,返回值void
@Pointcut("execution(* com.tjetc.dao..*.*(..))") //定位到连接点的条件
private void anyMethod2(){} //方法签名,返回值void
@Pointcut("anyMethod1() || anyMethod2()") //定位到连接点的条件
private void anyMethod(){} //方法签名,返回值void
(5)声明Advice
本类的方法直接写方法名()
@Before("anyMethod()")
public void before() {
System.out.println("前置增强");
}
非本类的方法写类的全路径名.方法名() (确保方法签名是public能访问的;否则报错)
@Component
@Aspect
public class Transcation2 {
@Before("com.tjetc.aspect.TransactionPrint.anyMethod()")
public void before() {
System.out.println("前置增强2");
}
}
(6)@AfterReturning返回值
第一步:在@AfterReturning添加returning属性retruning=”方法参数的名称”
第二步:在方法里写一个参数,名称是returning属性的值,接收返回值
@AfterReturning(value = "anyMethod()",returning = "result")
public void afterReturning(Object result){
System.out.println("返回值:"+result);
System.out.println("后置增强");
}
(7)@AfterThrowing异常
当异常发生时异常通知如何得到异常信息?
实现步骤:
第一步:在@AfterThrowing添加属性throwing=”方法参数的名称”
第二步:在方法里写一个参数,名称是throwing属性的值,接收异常对象
@AfterThrowing(value="anyMethod()",throwing="ex")
public void afterThrowing(Exception ex) {
System.out.println("异常通知,ex="+ex);
}
(8)在增强里接收参数
使用JoinPoint接口的getArgs()接收参数
@Before("anyMethod()")
public void before(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
System.out.println("参数:"+arg);
}
System.out.println("前置增强");
}
8.4.JDK动态代理
*(1)JDK动态代理是java.lang.reflect.包提供的方式,它必须借助一个接口才能产生代理对象,所以要先定义接口,代码如下:
public interface HelloWord {
void sayHelloWord();
}
(2)然后提供HelloWord的实现类
public class HelloWordImpl implements HelloWord {
@Override
public void sayHelloWord() {
System.out.println("Hello Word");
}
}
(3)这时就可以进行JDK动态代理了,先建立起代理对象和真实服务对象的关系,然后实现代理逻辑,代理类要实现java.lang.reflect.InvocationHandler接口,重写invoke()方法
public class JDKProxyExample implements InvocationHandler {
//真实对象
private Object target = null;
/**
*建立代理对象和真实对象的代理关系,并返回代理对象
* @Param target 真实对象
* @return 代理对象
*/
public Object bind(Object target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
/**
* 代理方法逻辑
* @param proxy 代理对象
* @param method 代理方法对象
* @param args 当前方法参数
* @return 代理结果返回
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强");
//参数一:真实对象 参数二: 方法参数
Object invoke = method.invoke(target, args);
System.out.println("后置增强");
return invoke;
}
}
**第1步,建立代理对象和真实对象的关系.**这里是使用了bind方法创建代理对象,方法里面首先用类的属性target保存了真实对象,然后通过如下代码建立并生成了代理对象.
Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
其中newProxyInstance方法的三个参数:
- 第1个:类加载器,我们采用了target本身的类加载器.
- 第2个:把生成的动态代理对象下挂在哪些接口下,这个写法就是放在target实现接口下.
- 第3个:定义实现方法逻辑的代理类.
**第2步,实现逻辑方法.**invoke方法可以实现代理逻辑.
invoke(Object proxy, Method method, Object[] args)
invoke方法的三个参数含义如下:
- proxy,代理对象,就是bind方法生成的对象
- method:代理方法对象
- args:调度方法的参数
当我们使用了代理对象调度方法后,他就会进入到invoke方法里面
Object invoke = method.invoke(target, args);
这行代码相当于调用真实的对象方法,只是通过反射实现而已.
第3步,测试JDK动态代理
public static void main(String[] args) {
JDKProxyExample jdk = new JDKProxyExample();
//绑定关系,因为挂在接口上的实现类,所以声明代理对象HelloWord proxy
HelloWord bind = (HelloWord) jdk.bind(new HelloWordImpl());
//此时bind是一个代理对象,调用代理方法
bind.sayHelloWord();
}
运行结果:
前置增强 Hello Word 后置增强
此时,在调度打印Hello Word之前和之后都可以加入相关的逻辑,甚至可以步调度Hello Word的打印.
8.5.CGLIB动态代理
JDK动态代理必须提供接口才嫩不过使用,在一些不能提供接口的环境中,只能采用其他的第三方方技术,比如CGLIB动态代理。它的优势在于不需要提供接口,只需要一个非抽象类就可以实现动态代理。
(1)我们还是用HelloWordImpl这个实现类,这次不需要接口
public class HelloWordImpl {
public void sayHelloWord() {
System.out.println("Hello Word");
}
}
(2)采用CGLIB动态代理技术,实现MethodInterceptor接口
public class CGLIBProxyExample implements MethodInterceptor {
//真实类对象
private HelloWordImpl target = null;
public CGLIBProxyExample() {
super();
// TODO Auto-generated constructor stub
}
public CGLIBProxyExample(HelloWordImpl target) {
super();
this.target = target;
}
/**
* 生成CGLIB代理对象
*/
public HelloWordImpl bind(){
Enhancer enhancer = new Enhancer();
//指定父类,即目标类。 因为cglib原理 子类增强父类,参数为真实类的class对象
enhancer.setSuperclass(HelloWordImpl.class);
//设置回掉接口.参数为代理类对象
enhancer.setCallback(this);
//生成并返回代理对象
return (HelloWordImpl)enhancer.create();
}
/**
* @param proxy 代理对象
* @param method 方法
* @param args 方法参数
* @param methodProxy 方法代理
* return 代理逻辑返回
* @throws Throwable 异常
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("前置增强");
Object o=methodProxy.invokeSuper(proxy, args);
System.out.println("后置增强");
return o;
}
}
}
这里面用了CGLIB的加强者Enhancer,通过设置超类的方法(setSuperclass),然后通过setCallback方法设置那个类为它的代理类。最后调用create()方法。
(3)测试CGLIB动态代理
public static void main(String[] args) {
HelloWordImpl hello = new HelloWordImpl();
HelloWordImpl h = new CGLIBProxyExample(hello).bind();
System.out.println(h);
h.sayHelloWord();
}
运行结果:
前置增强 Hello Word 后置增强
掌握了JDK动态代理就很容易掌握CGLIB动态代理,因为二者是相似的。他们都是用getProxy方法生成代理对象的,制定代理的逻辑类。而二者的区别就在于一个要实现接口,一个不需要实现接口。
9.Spring事务管理
9.1.事务的分类
- 本地事务:local transaction 使用单一资源管理器,管理本地资源。
- 全局事务:global transaction 通过事务管理和多种资源管理器,管理多种不同的资源。
- 编程式事务:通过编码方式,开启事务、提交事务、回滚事务。
- 声明性事务:通过xml配置或注解,实现事务管理,Spring AOP 和 EJB都是声明性事务。
- JTA事务:Java Transaction API ,使用javax.transaction.UserTransaction接口,访问多种资源管理器。
- CMT事务:Container Management transaction ,通过容器自动控制事务的开启,提交和回滚。
事务策略接口:PlatformTransactionManager
事务状态接口:TransactionStatus
事务定义接口:TransactionDefinition
9.2.Spring声明性事务
XML管理声明性事务
(1)pom.xml中添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
(2)配置文件db.properties配置数据源
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc.mysql:///cc
jdbc.username = root
jdbc.password = 123456
(3)applicationContext.xml文件
<!--配置基本扫描包-->
<context:component-scan base-package = "com.tjetc"></context:component-scan>
<!--加载db.properties获取四大金刚的值-->
<context:properties-placeholder location="classpath:db.properties"/>
<!--配置数据源-->
<bean id = "dataSource" class = "org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name = "driverClassName" value = "${jdbc.driver}"></property>
<property name = "url" value = "${jdbc.url}"></property>
<property name = "username" value = "${jdbc.username}"></property>
<property name = "password" value = "${jdbc.password}"></property>
</bean>
<!--配置数据源事务管理器-->
<bean id = "txManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name ="dataSource" ref = "dataSource"></property>
</bean>
<!--配置事务增强:tx:advice对目标的哪些方法使用事务增强-->
<tx:advice>
<tx:attributes>
<tx:method name = "add*" propagation = "REQUIRED" rollback-for = "Throwable"/>
<tx:method name = "update*" propagation = "REQUIRED" rollback-for = "Throwable"/>
<tx:method name = "del*" propagation = "REQUIRED" rollback-for = "Throwable"/>
<!--*代表除了上面方法之外的其他方法-->
<tx:method name = "*" propagation = "REQUIRED" read-only = "true"/>
</tx:attributes>
</tx:advice>
tx:method属性
属性 | 是否需要? | 默认值 | 描述 |
name | 是 | 事务属性关联的方法名,通配符(*)可以用来指定一批关联到相同的事务属性的方法。 | |
propagation | 不 | REQUIRED | 事务传播行为 |
isolation | 不 | DEFAULT | 事务隔离级别 |
timeout | 不 | -1 | 事务超时的时间(以秒为单位) |
read-only | 不 | false | 事务是否只读? |
rollback-for | 不 | 将触发进行回滚的Exception(s) | |
no-rollback-for | 不 | 不被触发进行回滚的Exception(s) |
(4)Student实体类
public class Student {
private int id;
private String name;
private int age;
}
(5)dao层
@Repository
public class StudentDao extends JdbcDaoSupport {
@Resource
private JdbcTemplate jdbcTemplate;
public boolean add(Student student){
String sql = "insert into student(name,age) values(?,?)";
int i = jdbcTemplate.update(sql, student.getName(), student.getAge());
System.out.println("受影响的行数:"+i);
return i>0;
}
}
(6)service层
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
public void add(Student student){
studentDao.add(student);
System.out.println(1/0);
studentDao.add(student);
}
}
(7)测试代码
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
StudentService studentService = context.getBean(StudentService.class);
studentService.add(new Student("张三",18));
}
注解方式管理声明式事务
(1)pom.xml、db.properties配置文件同上
(2)applicationContext.xml文件配置tx:annotation-driven注解
<!-- 配置扫描包 -->
<context:component-scan base-package="com.tjetc"></context:component-scan>
<!-- 加载从db.properties取得4大金刚的值 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置数据源事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--tx:annotation-driven 代表可以采用@Transactional注解方式使用事务-->
<tx:annotation-driven transaction-manager = "txManager"/>
(3)业务层的类上或者方法上写@Transactional注解
- 写在类上代表类的所有方法都是用事务
- 写在方法上值堆该方法使用事务
@Service
// @Transactional(rollbackFor=Throwable.class)//写在类上代表类的所有方法都使用事务
public class StudentService {
@Autowired
private StudentDao studentDao;
@Transactional(rollbackFor=Throwable.class)//写在方法只对该方法使用事务
public void add(Student student) {
studentDao.add(student);
System.out.println(1/0);
studentDao.add(student);
throw new ArrayIndexOutOfBoundsException("异常回滚测试...");
}
}
(4)测试代码
public static void main(String[] args) {
//实例化容器对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//从容器得到bean
StudentService studentService = context.getBean(StudentService.class);
//调用方法
studentService.add(new Student("张三", 20));
}
9.3.事务传播特性
- PROPAGATION_REQUIRED , required , 必须 【默认值】
- 支持当前事务,A如果有事务,B将使用该事务。
- 如果A没有事务,B将创建一个新的事务。
- PROPAGATION_SUPPORTS ,supports ,支持
- 支持当前事务,A如果有事务,B将使用该事务。
- 如果A没有事务,B将以非事务执行。
- PROPAGATION_MANDATORY,mandatory ,强制
- 支持当前事务,A如果有事务,B将使用该事务。
- 如果A没有事务,B将抛异常。
- PROPAGATION_REQUIRES_NEW , requires_new ,必须新的
- 如果A有事务,将A的事务挂起,B创建一个新的事务
- 如果A没有事务,B创建一个新的事务
- PROPAGATION_NOT_SUPPORTED ,not_supported ,不支持
- 如果A有事务,将A的事务挂起,B将以非事务执行
- 如果A没有事务,B将以非事务执行
- PROPAGATION_NEVER ,never,从不
- 如果A有事务,B将抛异常
- 如果A没有事务,B将以非事务执行
- PROPAGATION_NESTED ,nested ,嵌套
- A和B底层采用保存点机制,形成嵌套事务。
- 如果当前存在事务,则在嵌套事务内执行。
- 如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Autowired
private StudentDao studentDao;
@Transactional(rollbackFor=Throwable.class)
public void methodA() {
System.out.println("methodA()...");
studentDao.add(new Student("李四", 21));
serviceB.methodB();
}
}
@Service
public class ServiceB {
@Autowired
private StudentDao studentDao;
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
public void methodB() {
System.out.println("methodB()...");
studentDao.add(new Student("赵六", 22));
}
}
事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性。
9.4.事务的四个关键属性
- 原子性:事务时一个原子操作,有一系列动作完成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性:一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务规则的一致性状态中。
- 隔离性:可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏。
- 持久性:一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响。通常情况下,事务的结果被写到持久化存储器中
9.5.事务隔离级别
- Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)。
- Read Commited:读已提交数据(会出现不可重复读和幻读)
- Repeatable Read:可重复读(会出现幻读)
- Serializable:串行化