目录
Spring简介
Spring是一个开原框架,是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。Spring的核心是控制反转(IoC)和面向切面(AOP)。Spring 是为了解决企业级应用开发的复杂性而创建的。在 Spring 之前,有一个重量级的工具叫做 EJB,使用 Spring 可以让 Java Bean 之间进行有效的解耦,而这个操作之前只有 EJB 才能完成,EJB 过于臃肿,使用很少。Spring 不仅仅局限于服务端的开发,在测试性和松耦合方面都有很好的表现。
一站式,是指Spring有JavaEE开发的每一层解决方案
- web层:Struts2,Spring-MVC
- service层:Spring
- dao层:Hibernate,MyBatis ,Spring-Data
Spring发展史
1997年IBM提出了EJB的思想(EJB是的Enterprise Java Beans技术的简称, 又被称为企业Java Beans)
1998年,SUN制定开发标准规范EJB1.0
1999年,EJB1.1发布
2001年,EJB2.0发布
2003年,EJB2.1发布
2006年,EJB3.0发布
2004年,Spring1.0发布
2006年,Spring2.0发布
2009年,Spring3.0发布
2013年,Spring4.0发布
2017年,Spring5.0发布
2022年,Spring6.0发布
Spring核心体系
Spring为企业级开发提供了丰富的功能,这些功能的底层都依赖于它的两个核心特性:
- 控制反转(Inversion of Control,IOC)
- 面向切面编程(aspect-oriented programming,AOP)
Spring依赖
<!-- spring核心jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
IOC
Ioc (Inversion of Control),中文叫做控制反转。这是一个概念,也是一种思想。控制反转,实际上就是指对一个对象的控制权的反转。
控制:控制对象的创建、初始化、销毁
反转:对象的控制权(对象的创建、初始化、销毁)从主动管理转交给Spring的容器管理
IOC思想:面向接口编程,反射+配置文件,工厂模式
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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1.创建spring控制的资源
1). id 是这个bean的标识, 可以自定义,但是最好见名知意
2). class 指定实现类的全限定名
-->
<bean id="bean标识" class="实现类的全限定名" name="别名"/>
</beans>
获取bean
@Test
public void test01(){
//加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("配置文件名.xml");
//获取资源: 通过配置文件中的id
Object obj = ctx.getBean("bean的id属性");
}
beans标签
- 根标签
- 子标签:bean
bean基本属性
- id
- bean的名称,通过id值获取bean
- class
- bean的类型 (全限定名,即包名+类名)
- name
- bean的名称,可以通过name值获取bean,用于多人配合时给bean起别名
bean标签scope属性
- 作用定义bean的作用范围
- 取值
- singleton
- 设定创建出的对象保存在spring容器中,是一个单例的对象
- 饿汉单例
- prototype
- 设定创建出的对象保存在spring容器中,是一个非单例的对象
- 懒汉多例
- singleton
bean生命周期属性
-
init-method,destroy-method
- 定义bean对象在初始化或销毁时完成的工作
-
格式
<bean init-method="init" destroy-method="destroy"></bean>
-
注意事项
-
当scope=“singleton”时,spring容器中有且仅有一个对象,init方法在创建容器时仅执行一次
-
当scope=“prototype”时,spring容器要创建同一类型的多个对象,init方法在每个对象创建时均执行一次
-
当scope=“singleton”时,关闭容器会导致bean实例的销毁,调用destroy方法一次
-
当scope=“prototype”时,对象的销毁由垃圾回收机制gc()控制,destroy方法将不会被执行
-
加载第三方资源
如果知道类的全限定名可以通过直接配置的方式,但是有些类是没有类名的。例如:匿名内部类和动态代理类。可以通过工厂方式进行IOC配置。
工厂方式
1). 静态工厂 : 创建对象的方法是静态
2). 实例工厂 : 创建对象的方法是非静态的 一般用来配置其他框架的bean
-
静态工厂
-
bean标签属性factory-bean
-
作用:定义bean对象创建方式,使用静态工厂的形式创建bean
-
格式
<bean id="bean标识" class="FactoryClassName" factory-method="factoryMethodName"></bean>
- class属性必须配置成静态工厂的类名
- method是工厂中用于获取对象的静态方法名
-
要求:方法必须是静态的
-
原理:反射获取静态方法,调用静态方法获取目标对象
-
-
实例工厂
-
bean标签属性factory-bean,factory-method
-
格式
<bean id="实例工厂标识" class="实例工厂全限定名"/> <bean id="bean标识" factory-bean="实例工厂标识" factory-method="factoryMethodName"></bean>
-
原理:获取目标对象的方法是非静态的,需要先通过反射获取实例工厂对象,然后通过反射获取非静态方法,继而调用非静态方法获取目标对象
-
EL表达式
el : expression language 表达式语言
- ${}
${} 用于加载外部文件指定的Key值 (在下一节课的properties文件中演示)- #{}
#{} 强调的是把内容赋值给属性
-
Spring提供了对EL表达式的支持,统一属性注入格
-
类型:属性值
-
归属:value属性值
-
作用:为bean注入属性值
-
格式:
<property value="EL"></bean>
-
注意:所有属性值不区分是否引用类型,统一使用value赋值
-
提供配置文件路径
xml配置方式: <context:property-placeholder location="classpath:data.properties"/> 注解配置方式: @PropertySource(value = "classpath:filename.properties")
-
常量 #{10} #{3.14} #{2e5} #{‘itcast’}
-
引用bean #{beanId}
-
引用bean属性 #{beanId.propertyName}
-
引用bean方法 beanId.methodName().method2()
-
引用静态方法 T(java.lang.Math).PI
-
运算符支持 #{3 lt 4 == 4 ge 3}
-
正则表达式支持 #{user.name matches‘[a-z]{6,}’}
-
集合支持 #{likes[3]}
-
示例
<context:property-placeholder location="classpath:data.properties"/> <bean id="myDate" class="java.util.Date"/> <bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"/> <bean id="userService" class="com.itheima.service.impl.UserServiceImpl2"> <!-- <property name="name" value="zs"/> <property name="age" value="18"/> <property name="date" ref="myDate"/> <property name="dao" ref="userDao"/>--> <property name="name" value="#{'zs'}"/> <property name="age" value="#{18}"/> <property name="date" value="#{myDate}"/> <property name="dao" value="#{userDao}"/> </bean>
-
-
DI依赖注入
set注入
-
bean子标签property
-
格式
<bean> <property name="propertyName" value="propertyValue" /> <property name="propertyName" ref="propertyValue" </bean>
-
name
- 对应bean中的属性名
- 要求该属性必须提供可访问的set方法
-
value
- 设定非引用类型(8大基本类型和String)
- 不能与ref同时使用
-
ref
- 设定引用类型
- 不能与value同时使用
-
注意:一个bean可以有多个property标签
-
原理:反射获取对象的setter方法,然后调用setter方法给属性赋值
构造器注入
-
bean子标签constructor-arg
-
作用:使用构造方法的形式为bean提供资源,兼容早期遗留系统的升级工作
-
name
- 对应bean中的构造方法所携带的参数名
-
value
- 设定非引用类型构造方法参数对应的值,不能与ref同时使用
-
ref
- 设定引用类型构造方法参数对应bean的id ,不能与value同时使用
-
type
- 设定构造方法参数的类型,用于按类型匹配参数或进行类型校验
-
index
- 设定构造方法参数的位置,用于按位置匹配参数,参数index值从0开始计数
-
注意:
- 需要有参构造
- 一个bean可以有多个constructor-arg标签
-
原理:反射获取类的构造方法,通过构造器创建对象,完成注入
集合类型注入
-
子标签名称:array,list,set,map,props
-
父标签为property标签 或 constructor-arg标签
-
作用:注入集合数据类型属性
-
array
-
格式
<property name="arr"> <array> <value>12345</value> <value>66666</value> </array> </property>
-
-
list
-
格式
<property name="al"> <list> <value>12345</value> <value>66666</value> </list> </property>
-
-
set
-
格式
<property name="hs"> <set> <value>12345</value> <value>66666</value> </set> </property>
-
-
map
-
格式
<property name="hm"> <map> <entry key="name" value="12345"/> <entry key="value" value="66666"/> </map> </property>
-
-
props
-
格式
<property name="properties"> <props> <prop key="username">itheima666</prop> <prop key="passwo">666666</prop> </props> </property>
-
注解配置
优点:书写简单
缺点:配置需要基于源代码,而第三方框架提供的代码都是字节码。为达成目的,可能会将xml方式中很简单的书写方式变得复杂
全注解方式获取bean
AnnotationConfigApplicationContext
-
加载纯注解格式上下文对象,需要使用AnnotationConfigApplicationContext
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
半注解方式启用注解
注解配置和xml配置可以混合使用
-
启动注解扫描,加载类中配置的注解项
<context:component-scan base-package="packageName"/>
-
说明:
-
在进行包扫描时,会对配置的包及其子包中所有文件进行扫描
-
扫描过程是以文件夹递归迭代的形式进行的
-
扫描过程仅读取合法的java文件
-
扫描时仅读取spring可识别的注解
-
扫描结束后会将可识别的有效注解转化为spring对应的资源加入IoC容器
-
配置类
-
代码
@Configuration @ComponentScan("scanPackageName") public class SpringConfig{ }
-
@Configuration、@ComponentScan
- 类注解
- @Configuration设置当前类为spring核心加载类、
- @ComponentScan定义扫描的路径从中找出标识了需要装配的类自动装配到spring的bean容器中
bean的定义
-
@Component @Controller @Service @Repository
- 类注解
- 作用:设置该类为spring管理的bean
- 示例:
@Component public class ClassName{}
bean的作用域
- @Scope
- 类注解
- value(默认):定义bean的作用域,默认为singleton
bean的生命周期
-
@PostConstruct、@PreDestroy
-
方法注解
-
作用:设置该方法作为bean对应的生命周期方法
-
示例
@PostConstructpublic void init(){ System.out.println("init..."); } @PreDestroy void destory(){ System.out.println("destory..."); }
-
加载properties文件
-
@PropertySource
-
类注解
-
作用:加载properties文件中的属性值
-
示例
@PropertySource(value = "classpath:filename.properties") public class ClassName { @Value("${propertiesAttributeName}") private String attributeName; }
-
非引用类型注入
-
@Value
-
属性注解、方法注解
-
作用:设置对应属性的值或对方法进行传参
-
示例
@Value("${jdbc.username}") private String username;
-
原理:反射获取注解的属性值,赋给当前的属性
-
说明
-
value值仅支持非引用类型数据,赋值时对方法的所有参数全部赋值
-
value值支持读取properties文件中的属性值,通过类属性将properties中数据传入类中
-
value值支持SpEL
-
@value注解如果添加在属性上方,可以省略set方法(属性注入原理不是反射获取setter方法)
-
-
引用类型注入
-
@Autowired、@Qualifier
-
属性注解、方法注解
-
作用:设置对应属性的对象或对方法进行引用类型传参
-
示例
@Autowired(required = true) @Qualifier("userDao") private UserDao userDao;
-
说明
- @Autowired
1.自动配置, 如果IOC容器中只有一个此类型的对象,会自动配置
2.如果IOC容器中有多个此类型的对象,会自动配置跟变量名一致的标识的bean
3.如果IOC容器中有多个此类型的对象,且跟变量都不一致,需要手动设置@Qualifier- 属性: 了解,一般不写
required:
true: 此对象必须注入成功,若不成功则报错. 默认值
false: 可以注入不成功,此对象为null
- 属性: 了解,一般不写
- @Autowired
-
加载第三方资源
工厂模式: 框架中的类(第三方资源)是字节码,不是源码,无法使用注解注入
所以用工厂模式进行配置这个bean
-
@Bean
-
方法注解
-
作用:设置该方法的返回值作为spring管理的bean
-
相关属性
- value(默认):定义bean的访问id
-
说明:
- 因为第三方bean无法在其源码上进行修改,使用@Bean解决第三方bean的引入问题
- 该注解用于替代XML配置中的静态工厂与实例工厂创建bean,不区分方法是否为静态或非静态
- @Bean所在的类必须被spring扫描加载,否则该注解无法生效
-
第三方bean配置与管理
-
@Import
-
类注解
-
作用:导入第三方bean作为spring控制的资源
-
示例
@Configuration @Import(OtherClassName.class) public class ClassName { }
-
说明:
-
@Import注解在同一个类上,仅允许添加一次,如果需要导入多个,使用数组的形式进行设定
-
在被导入的类中可以继续使用@Import导入其他资源
-
@Bean所在的类可以使用导入的形式进入spring容器,无需声明为bean
-
-
AOP
AOP(Aspect Oriented Programing)面向切面编程,一种编程范式,指导开发者如何组织程序结构。AOP 广泛应用于处理一些具有横切性质的系统级服务,AOP 的出现是对 OOP 的良好补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。
AOP要解决的问题是用一个“横切面”的方式,来统一处理很多对象都需要的,相同或相似的功能,减少程序里面的重复代码,让代码变得更干净,更专注于业务。
spring的AOP的实现方式
- 当Bean实现接口时,Spring就会用JDK的动态代理
- 当Bean没有实现接口时,Spring使用CGLib来实现
- JDK8之后, JDK动态代理效率高于CG lib
- 备注: 开发者可以在spring中强制使用CGLib
- 在Spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
动态代理
代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
Spring AOP动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。
- JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
- 如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类(通过修改字节码来实现代理)。 注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。 jdk和cglib动态代理来共同实现我们的aop面向切面的功能。
AOP相关概念
- Target(目标对象)
- 要被增强的对象(被代理类的对象)
- Proxy(代理对象)
- 对目标对象的增强对象 (生成的代理类对象)
- Joinpoint(连接点)
- 目标对象中的可被增强的所有方法(被代理类中的所有方法)
- JDKProxy中被代理类不可被增强方法 (父接口没有的方法)
- CGlib中被代理类不可被增强方法(比如用final修饰的方法)
- 目标对象中的可被增强的所有方法(被代理类中的所有方法)
- Pointcut(切入点)
- 要被增强的方法(被代理类中要增强的方法)
- 切入点一定是连接点
- 但连接点不一定是切入点
- 要被增强的方法(被代理类中要增强的方法)
- Advice(通知/增强)
- 通知是增强的那段代码形成的方法
- 前置通知 在方法之前进行增强
- 后置通知 在方法之后进行增强
- 异常通知 在方法异常进行增强
- 最终通知 最终执行的方法进行增强
- 环绕通知 单独使用(以上所有通知)
- 通知是增强的那段代码形成的方法
- Aspect(切面)
- 切面= 切入点+通知
- 目标方法和增强方法合到在一起 叫做切面
- Weaving(织入)
- 在运行过程中,spring底层将通知和切入点进行整合的过程,称为织入
AOP注解配置
依赖
<!-- spring核心jar包,已经依赖的AOP的jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- TODO: 切入点表达式 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
示例代码
原始设计(需要增强的类)
package com.itheima.service;
public interface AccountService {
//有返回值
String findAll();
//有参数
void insert(String str);
void update();
void delete();
}
package com.itheima.service.impl;
import com.itheima.service.AccountService;
@Service
public class AccountServiceImpl implements AccountService {
@Override
public String findAll() {
System.out.println("findAll.............");
return "result: 查询结果";
}
@Override
public void insert(String str) {
// int i = 1/0;
System.out.println("insert............." + str);
}
@Override
public void update() {
System.out.println("update.............");
}
@Override
public void delete() {
System.out.println("delete.............");
}
}
配置类
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
//配置类
@Configuration
//包扫描
@ComponentScan("com.itheima")
// 开启aop注解支持
@EnableAspectJAutoProxy
public class SpringConfig {
}
代理类书写方式一
package com.itheima.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//IOC配置: 当前bean需要加载到ioc容器中
@Component
//aop配置 : 声明当前类是切面类 (切面=切入点+通知)
@Aspect
public class MyAdvice {
/*
指定切入点 : 指定AccountServiceImpl类中所有方法为切入点 (切入点表达式)
1. @Pointcut配置切入点
2. 需要写在一个三无方法上 (无参无返回值空方法体)
*/
@Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.*(..))")
public void pt(){
}
@Before("pt()")
public void before(){
System.out.println("前置通知:11111111111111111");
}
@AfterReturning("pt()")
public void afterReturning(){
System.out.println("后置通知:2222222222222222");
}
@AfterThrowing("pt()")
public void afterThrowing(){
System.out.println("异常通知:3333333333333333333");
}
@After("pt()")
public void after(){
System.out.println("最终通知:44444444444444444444444");
}
}
代理类书写方式二
使用环绕通知@Around
环绕通知:在原始方法执行前后均有对应执行,还可以阻止原始方法的执行
package com.itheima.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/*
TODO: 环绕通知
*/
@Component
@Aspect
public class MyAdvice2 {
@Pointcut("execution(* com.itheima.service.impl.AccountServiceImpl.*(..))")
public void pt(){
}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp){
Object result = null;
try {
System.out.println("前置通知:11111111111111111");
result = pjp.proceed();
System.out.println("后置通知:2222222222222222");
} catch (Throwable throwable) {
// throwable.printStackTrace();
System.out.println("异常通知:3333333333333333333");
} finally {
System.out.println("最终通知:44444444444444444444444");
}
return result;
}
}
测试类
//spring整合junit
@RunWith(SpringJUnit4ClassRunner.class)
//加载注解配置类
@ContextConfiguration(classes = SpringConfig.class)
public class MyApp {
@Autowired
AccountService service;
@Test
public void test01(){
System.out.println(service);
System.out.println(service.getClass());
//配置对insert方法具有增强效果
service.insert("参数");
}
}
运行结果
before: 前置通知
insert.............参数
afterReturning:后置通知
after: 最终通知
相关注解
-
@Aspect配置当前类为切面类(切入点+通知)
-
@PonitCut:定义公共的切入点
-
切入点表达式
1. 完整写法:execution(方法的修饰符 方法的返回值 类的全限定名.方法名(参数)) 2. 支持通配符的写法: 1) * 表示任意字符串 2) .. 表示任意重复次数 3. 规则 1. 方法的修饰符可以省略: 2. 返回值可以使用*号代替:标识任意返回值类型 3. 包名可以使用*号代替,代表任意包(一层包使用一个*) 4. 使用..配置包名,标识此包以及此包下的所有子包 5. 类名可以使用*号代替,标识任意类 6. 方法名可以使用*号代替,表示任意方法 7. 可以使用..配置参数,任意参数
-
通知类型
@Before: 前置通知 @AfterReturning:后置通知 @AfterThrowing :异常通知 @After :最终通知 @Around:环绕通知 1. 前置通知 原始方法(切入点)执行前执行,如果通知中抛出异常,阻止原始方法运行 应用:数据校验 2. 后置通知:原始方法执行后执行,无论原始方法中是否出现异常,不再执行 应用:返回值相关数据处理 3. 抛出异常后通知:原始方法抛出异常后执行,如果原始方法没有抛出异常,无法执行 应用:对原始方法中出现的异常信息进行处理 4. 最终通知:无论如何最终都执行 应用:现场清理 5. 环绕通知:在原始方法执行前后均有对应执行,还可以阻止原始方法的执行 应用:十分强大,可以做包括四种类型的所有事情
1. 环绕通知是在原始方法的前后添加功能,在环绕通知中,存在对原始方法的显式调用 public Object around(ProceedingJoinPoint pjp) throws Throwable { Object ret = pjp.proceed(); return ret; } 2. 环绕通知方法相关说明: 1).方法须设定Object类型的返回值,否则会拦截原始方法的返回。如果原始方法返回值类型为void,通知方 也可以设定返回值类型为void,最终返回null 2). 方法需在第一个参数位置设定ProceedingJoinPoint对象(代表切入点),通过该对象调用proceed()方法,实现对原始方法的调用。如省略该参数,原始方法将无法执行 3). 使用proceed()方法调用原始方法时,因无法预知原始方法运行过程中是否会出现异常,强制抛出Throwable对象,封装原始方法中可能出现的异常信息