Spring
2022年1月14日19:34:19
1. 什么是Spring
Spring框架是 Java 平台的一个开源的全栈(full-stack)应用程序框架和控制反转容器实现,Spring框架以 Apache License 2.0 开源许可协议的形式发布,该框架最初由 Rod Johnson 以及 Juergen Hoeller 等人开发。
2. IOC 控制反转容器(依赖注入)
控制反转(IOC,Inverse of contril),即把创建对象的权利交给框架,也就是将对象的创建、对象的存储、对象的管理交给了Spring容器
3. 第一个Spring程序
- 新建Maven程序
-
在
pox.xml
文件中导入Spring依赖<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.19.RELEASE</version> </dependency>
-
和普通项目一样创建一个JavaBean结构(需要提供set方法,Spring通过Set注入)
public class User {
private String name;
private String sex;
private Integer age;
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
public void show() {
System.out.println(this.toString());
}
}
- 在resource文件夹中创建一个 .xml文件
- 编写测试类:
@Test
public void show() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
user.show();
}
- 运行即可
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。 |
WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 |
4.注入依赖(基于XMl)
1.构造器注入
<bean id="user1" class="com.august.pojo.User.User" >
<constructor-arg index="0" value="111" />
<constructor-arg name="sex" value="女" />
<constructor-arg value="1"/>
</bean>
2.Set注入
private String name;
private String email;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> cards;
private Set<String> games;
private Properties properties;
xml:
<bean id="address" class="com.august.pojo.User.Address">
<property name="address" value="北京"/>
<property name="roadNumber" value="4477777"/>
</bean>
<bean id="person" class="com.august.pojo.User.Person">
<!--普通字符串注入-->
<property name="name" value="小明"/>
<!-- null 注入-->
<property name="email">
<null/>
</property>
<!--bean注入-->
<property name="address" ref="address"/>
<!--数组注入-->
<property name="books">
<array>
<value><![CDATA[<<红楼梦>>]]></value>
<value><![CDATA[<<西游记>>]]></value>
<value><![CDATA[<<水浒传>>]]></value>
<value><![CDATA[<<三国演义>>]]></value>
</array>
</property>
<!-- list注入-->
<property name="hobbys">
<list>
<value>吃饭</value>
<value>睡觉</value>
<value>打豆豆</value>
</list>
</property>
<!--map注入-->
<property name="cards">
<map>
<entry key="身份证" value="45277555577777"/>
<entry key="银行卡" value="6888454544466"/>
<entry key="电话卡" value="13888888888"/>
</map>
</property>
<!--set注入-->
<property name="games">
<set>
<value>LOL</value>
<value>CF</value>
<value>TGA5</value>
</set>
</property>
<!--properties 注入-->
<property name="properties">
<props>
<prop key="driver">mysql</prop>
<prop key="username">123456</prop>
<prop key="url">localhost</prop>
</props>
</property>
</bean>
3.扩展注入(P命名空间C命名空间)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.august.pojo.User.User" p:age="18" p:name="小王" p:sex="男"/>
<bean id="userA" class="com.august.pojo.User.User" c:age="18" c:name="小明" c:sex="女"/>
</beans>
注意:在使用P命名空间C命名空间时候需要导入约束;
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
说明: p 指的是 property
提供set方法
c 指的是 constructor-arg
,所以在使用c命名空间时,需要提供有参构造器
5.注入依赖(基于注解)
①情况一:最基本的扫描方式[常用]
<!-- 需要引入context约束 -->
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.august"/>
@Repository
、@Service
和@Controller
是@Component
的特化,用于更具体的用例(分别在持久层,服务层和表示层中)。
注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。
②情况二:指定匹配模式
<!-- 情况二:在指定扫描包的基础上指定匹配模式 -->
<context:component-scan
base-package="com.august"
resource-pattern="Soldier*.class"/>
③情况三:指定要排除的组件
<!-- 情况三:指定不扫描的组件 -->
<context:component-scan base-package="com.august">
<!-- context:exclude-filter标签:指定排除规则 -->
<!-- type属性:指定根据什么来进行排除,annotation取值表示根据注解来排除 -->
<!-- expression属性:指定排除规则的表达式,对于注解来说指定全类名即可 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
④情况四:仅扫描指定组件
<!-- 情况四:仅扫描指定的组件 -->
<!-- 仅扫描 = 关闭默认规则 + 追加规则 -->
<!-- use-default-filters属性:取值false表示关闭默认扫描规则 -->
<context:component-scan base-package="com.august" use-default-filters="false">
<!-- context:include-filter标签:指定在原有扫描规则的基础上追加的规则 -->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
6.完全注解开发
1、使用配置类取代配置文件
①创建配置类
- 使用 @Configuration 注解将一个普通的类标记为 Spring 的配置类。
@Configuration
public class MyConfiguration {
}
②根据配置类创建IOC容器对象
@Test
public void Test() {
//AnnotationConfigApplicationContext 根据配置类创建 IOC 容器对象
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
}
2、在配置类中配置bean
方式一:
@Configuration
public class MyConfiguration {
// @Bean 注解相当于 XML 配置文件中的 bean 标签
// @Bean 注解标记的方法的返回值会被放入 IOC 容器
// 默认以方法名作为 bean 的 id
@Bean("user")
public User getUser(){
User user = new User();
user.setName("xiaoMing");
return user;
}
}
- @Bean可以为Bean同时定义多个别名,但是别名不能为空字符串。
方式二:
自己在交给Spring管理类上加注解@Component
@Component
public class User {
....
}
3、在配置类中配置自动扫描的包
@Configuration
@ComponentScan("com.august")
public class MyConfiguration {
……
}
7.自动装配注入(DI Dependency Injection)
条件: 参与自动装配的组件(需要装配别人、被别人装配)全部都必须在IOC容器中。
1.@Autowired
声明级别: 字段(推荐) (不需要提供setXxx()方法)、构造器 、set方法
注入(装配)规则:默认按照属性值Class类型实现装配,如果接口仅有一个实现注入时默认注入对应实现类如果接口存在多个实现此时需要应用程序声明注入的Bean (使用@Qualifier(value="实现类id")
)
2.@Qualifier
通常和@Autowired
使用
- 使用 @Qualifier 注解:根据 @Qualifier 注解中指定的名称作为 bean 的id进行匹配
- 能够找到:执行装配
- 找不到:装配失败
3.@Resource
@AutoWried按by type自动注入,@Resource默认按byName自动注入。
默认按 byName自动注入,如果找不到再按byType找bean,如果还是找不到则抛异常,无论按byName还是byType如果找到多个,则抛异常。
4.@Value
注入属性值
@Component
public class User {
@Value("张三")
private String name;
@Value("16")
private String sex;
@Value("18")
private Integer age;
}
@Scope(“singleton”) 、@Scope(“prototype”)
返回单例或者新例
8. AOP面向切片编程
面向切面编程(AOP,Aspect oriented Programming) 就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任分开封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
1.AOP基本术语
- 连接点(JoinPoint):
所谓连接点是指那些被拦截到的点(可以被拦截代理的方法)。在Spring中,这些点指的是方法,Spring只支持方法类型的连接点
- 切入点(PointCut):
定位连接点的方式。
相当于特定的连接点(JoinPoint),AOP通过“切入点"来定位特定的连接点。连接点可以类别为数据库中的记录,而切入点相当于查询条件(在Spring AOP中使用一套特定的切入点表达式来描述)。切入点和连接点不是一对一的关系,一个切入点可以匹配多个连接点。
- 通知(Advice):
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
- 前置通知(Before):在被代理的目标方法前执行
- 环绕通知(Around):使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
- 返回通知(After-Returning):在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知(After-Throwing):在被代理的目标方法异常结束后执行(死于非命)
- 后置通知(After):在被代理的目标方法最终结束后执行(盖棺定论)
- 切面(Aspect):
封装通知方法的类。(引入增强方法)
- 目标(Traget):
被代理的目标对象。
- 代理(Proxy):
向目标对象应用通知之后创建的代理对象。
2.基于注解的AOP
- 基于注解的AOP用到的技术
- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)。
- cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口。
- AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
AspectJ依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
- 导入如上依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
- 准备被代理的目标资源
提供接口
public interface Calculator {
int add(int i,int j);
int sub(int i,int j);
int mul(int i,int j);
int div(int i,int j);
}
接口实现类
在 Spring 下工作,所有的一切都必须放在 IOC 容器中。现在接口的实现类是 AOP 要代理的目标类,所以它也必须放入 IOC 容器。
public class calculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部add result = " + result);
return result ;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部sub result = " + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部mul result = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部div result = " + result);
return result;
}
}
- 创建切面类
package com.august.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* 创建切面类
*
* @author : Crazy_August
* @description :
* @Time: 2022-01-20 18:30
*/
@Aspect // @Aspect表示这个类是一个 切面类
@Component // @Component 注解保证这个切面类能够放入IOC容器
public class LogAspect {
//切入点
@Pointcut(value="execution(* com.august.aop.impl..*(..))")
public void cut(){}
@Before(value="cut()")
public void before(){
System.out.println("前置通知....");
}
@After(value="cut()")
public void after(){
System.out.println("后置通知....");
}
@AfterThrowing(value="cut()")
public void afterThrowing(){
System.out.println("异常通知....");
}
@Around(value="cut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前通知....");+
//执行目标通知
Object result = pjp.proceed();
System.out.println("环绕后通知....");
return result;
}
@AfterReturning(value="cut()")
public void afterReturning(){
System.out.println("返回通知....");
}
}
- 创建Spring的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation=" http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启基于注解的AOP功能 -->
<aop:aspectj-autoproxy />
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.august" />
</beans>
- 测试
public class CalculatorTest {
private ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
@Test
public void add() {
//注意:用接口接收,动态代理不明确是哪个类
Calculator calculator = (Calculator)context.getBean("calculatorImpl");
calculator.add(1, 5);
}
}
正常输出结果:
环绕前通知…
前置通知…
方法内部add result = 6
返回通知…
后置通知…
环绕后通知…
异常输出结果:
环绕前通知…
前置通知…
方法内部add result = 6
异常通知…
后置通知…
3.各个通知获取细节信息
1、JoinPoint接口
org.aspectj.lang.JoinPoint
- 要点1:JoinPoint 接口通过 getSignature() 方法获取目标方法的签名(方法声明时的完整信息)
- 要点2:通过目标方法签名对象获取方法名
- 要点3:通过 JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数组
// @Before注解标记前置通知方法
// value属性:切入点表达式,告诉Spring当前通知方法要套用到哪个目标方法上
// 在前置通知方法形参位置声明一个JoinPoint类型的参数,Spring就会将这个对象传入
// 根据JoinPoint对象就可以获取目标方法名称、实际参数列表
@Before(value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))")
public void printLogBeforeCore(JoinPoint joinPoint) {
// 1.通过JoinPoint对象获取目标方法签名对象
// 方法的签名:一个方法的全部声明信息
Signature signature = joinPoint.getSignature();
// 2.通过方法的签名对象获取目标方法的详细信息
String methodName = signature.getName();
System.out.println("methodName = " + methodName);
int modifiers = signature.getModifiers();
System.out.println("modifiers = " + modifiers);
String declaringTypeName = signature.getDeclaringTypeName();
System.out.println("declaringTypeName = " + declaringTypeName);
// 3.通过JoinPoint对象获取外界调用目标方法时传入的实参列表
Object[] args = joinPoint.getArgs();
// 4.由于数组直接打印看不到具体数据,所以转换为List集合
List<Object> argList = Arrays.asList(args);
System.out.println("[AOP前置通知] " + methodName + "方法开始了,参数列表:" + argList);
}
需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。
methodName = add
modifiers = 1025
declaringTypeName = com.august.aop.Calculator
[AOP前置通知] add方法开始了,参数列表:[1, 5]
2、方法返回值
// @AfterReturning注解标记返回通知方法
// 在返回通知中获取目标方法返回值分两步:
// 第一步:在@AfterReturning注解中通过returning属性设置一个名称
// 第二步:使用returning属性设置的名称在通知方法中声明一个对应的形参
@AfterReturning(
value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))",
returning = "targetMethodReturnValue"
)
public void printLogAfterCoreSuccess(JoinPoint joinPoint, Object targetMethodReturnValue) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[AOP返回通知] "+methodName+"方法成功结束了,返回值是:" + targetMethodReturnValue);
}
3、目标方法抛出的异常
// @AfterThrowing注解标记异常通知方法
// 在异常通知中获取目标方法抛出的异常分两步:
// 第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称
// 第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们
@AfterThrowing(
value = "execution(public int com.atguigu.aop.api.Calculator.add(int,int))",
throwing = "targetMethodException"
)
public void printLogAfterCoreException(JoinPoint joinPoint, Throwable targetMethodException) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[AOP异常通知] "+methodName+"方法抛异常了,异常类型是:" + targetMethodException.getClass().getName());
}
4.切入点表达式
1.声明:
在一处声明切入点表达式之后,其他有需要的地方引用这个切入点表达式。易于维护,一处修改,处处生效。声明方式如下:
// 切入点表达式重用
//切入点
@Pointcut(value="execution(* com.august.aop.impl.*.*(..))")
public void pointCut() {}
1.1同一个类内部引用
@Before(value="pointCut()")
public void before(){
System.out.println("前置通知....");
}
1.2在不同类中引用
@After(value="com.august.aspect.LogAspect.pointCut()")
public void after(){
System.out.println("后置通知....");
}
2.集中管理
创建存放切入点表达式的类,可以把整个项目中所有切入点表达式全部集中过来,便于统一管理:
@Component
public class MyPointCut {
@Pointcut(value = "execution(public int *..Calculator.sub(int,int))")
public void atguiguGlobalPointCut(){}
@Pointcut(value = "execution(public int *..Calculator.add(int,int))")
public void atguiguSecondPointCut(){}
@Pointcut(value = "execution(* *..*Service.*(..))")
public void transactionPointCut(){}
}
5.切面的优先级
概念: 相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
- 优先级高的切面:外面
- 优先级低的切面:里面
使用 @Order 注解可以控制切面的优先级:
- @Order(较小的数):优先级高
- @Order(较大的数):优先级低
6.AOP基于XML
1、2步与注解相同
3.配置Spring配置文件
<!-- 配置目标类的bean -->
<bean id="calculatorPure" class="com.atguigu.aop.imp.CalculatorPureImpl"/>
<!-- 配置切面类的bean -->
<bean id="logAspect" class="com.atguigu.aop.aspect.LogAspect"/>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切入点表达式 -->
<aop:pointcut id="logPointCut" expression="execution(* *..*.*(..))"/>
<!-- aop:aspect标签:配置切面 -->
<!-- ref属性:关联切面类的bean -->
<aop:aspect ref="logAspect">
<!-- aop:before标签:配置前置通知 -->
<!-- method属性:指定前置通知的方法名 -->
<!-- pointcut-ref属性:引用切入点表达式 -->
<aop:before method="printLogBeforeCore" pointcut-ref="logPointCut"/>
<!-- aop:after-returning标签:配置返回通知 -->
<!-- returning属性:指定通知方法中用来接收目标方法返回值的参数名 -->
<aop:after-returning
method="printLogAfterCoreSuccess"
pointcut-ref="logPointCut"
returning="targetMethodReturnValue"/>
<!-- aop:after-throwing标签:配置异常通知 -->
<!-- throwing属性:指定通知方法中用来接收目标方法抛出异常的异常对象的参数名 -->
<aop:after-throwing
method="printLogAfterCoreException"
pointcut-ref="logPointCut"
throwing="targetMethodException"/>
<!-- aop:after标签:配置后置通知 -->
<aop:after method="printLogCoreFinallyEnd" pointcut-ref="logPointCut"/>
<!-- aop:around标签:配置环绕通知 -->
<!--<aop:around method="……" pointcut-ref="logPointCut"/>-->
</aop:aspect>
</aop:config>
- 测试相同