动力节点—2020最新Spring教程笔记(上)
@
根据动力节点2020Spring笔记整理而成,视频地址https://www.bilibili.com/video/BV1nz4y1d7uy/。
1 Spring 概述
Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring容器统一管理,自动“注入”,注入即赋值。 而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。
Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP, Aspects)、提供JVM的代理(Instrumentation)、消息发送(Messaging)、核心容器(Core Container)和测试(Test)。
2 IOC 控制反转
控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。
依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA 对 classB 有依赖。
依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
依赖注入 DI 是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
Spring 框架使用依赖注入(DI)实现IoC。
Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。
具体分为两种,基于 XML 和基于注解的 DI。
2.1 基于XML的DI
2.1.1 注入分类
2.1.1.1 set注入
public class Student{
private String name;
private int age;
private School school;
public Student(String name, Integer age, School school) {
this.name = name;
this.age = age;
this.school = school;
}
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setSchool(School school) {
this.school = school;
}
}
class School{
private String name;
private String address;
public void setAddress(String address) {
this.address = address;
}
public void setName(String schoolName) {
this.schoolName = schoolName;
}
}
set 注入,相当于调用 set 的方法,使用 <property />
,分为两种,一种是简单类型,即基本类型和String,另一种是引用类型。基本类型和String使用value,引用类型是ref。
<bean id="student" class="com.bjpowernode.ba01.Student" autowire="byName">
<property name="name" value="我" />
<property name="age" value="20" />
<property name="school" ref="school"/>
</bean>
<bean id="school" class="com.bjpowernode.ba01.School">
<property name="name" value="北大" />
<property name="address" value="北京" />
</bean>
2.1.1.2 构造注入(了解)
构造注入相当于调用构造器,使用 <constructor-arg />
。
<bean id="student" class="com.bjpowernode.ba01.Student">
<constructor-arg name="name" value="我" />
<constructor-arg name="age" value="20" />
<constructor-arg name="school" ref="school"/>
</bean>
<bean id="school" class="com.bjpowernode.ba01.School">
<property name="name" value="北大" />
<property name="address" value="北京" />
</bean>
2.1.2 自动注入
对于某个property是ref类型的,可以自动注入,不用自己写,byName是按照名字注入。在这里指的是Student类的属性school和下面School类的id一样。
<bean id="student" class="com.bjpowernode.ba01.Student" autowire="byName">
<property name="name" value="我" />
<property name="age" value="20"></property>
<!--自动注入,省略 <property name="school" ref="school"/>-->
</bean>
<!--id和上面省略的属性名一致-->
<bean id="school" class="com.bjpowernode.ba01.School">
<property name="schoolName" value="北大"></property>
</bean>
byType按照类型注入,不要求注入的id和属性名一致。
<bean id="student" class="com.bjpowernode.ba01.Student" autowire="byType">
<property name="name" value="我" />
<property name="age" value="20"></property>
<!--自动注入,省略 <property name="school" ref="school"/>-->
</bean>
<!--id和上面的属性名不同-->
<bean id="school1234" class="com.bjpowernode.ba01.School">
<property name="schoolName" value="北大"></property>
</bean>
2.2 基于注解的DI
需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
掌握下面几个注解,@Component,@Repository,@Service,@Controller,@Value,@Autowired,@Qualifier,@Resource。
其中前四个都是定义Bean,@Repository是对DAO实现类使用,@Service对Service实现类使用,@Controller对Controller实现类使用。@Value用于基本类型和String。@Autowired用于自动注入,默认byType,结合@Qualifier实现byName。@Resource默认byName,找不到则byType。
具体如下,第一个例子是byName,School类的对象名称为school1,要求自动注入的名称也是school1.
@Component("school1")
public class School {
@Value("北大")
private String schoolName;
}
@Component("student")
public class Student {
@Value("王坤")
private String name;
@Value("20")
private Integer age;
@Resource(name="school1") //对应School的名字
//@Resource(..)可换成下面两行
//@Autowired
//@Qualifier("school1")
private School school;
}
第二个例子是byType,按照School类去查找,不关心名称。
@Component("school1")
public class School {
@Value("北大")
private String schoolName;
}
@Component("student")
public class Student {
@Value("王坤")
private String name;
@Value("20")
private Integer age;
@Autowired //或者@Resource
private School school;
}
3 AOP面向切面编程
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB的动态代理。
3.1 AOP术语
(1) 切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。
(2) 连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
(3) 切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
(4) 目标对象(Target)
目 标 对 象 指 将 要 被 增 强 的 对 象 。 即 包 含 主 业 务 逻 辑 的 类 的 对 象 。
(5) 通知(Advice)
通知表示切面的执行时间,Advice 也叫增强。通知定义了增强代码切入到目标代码的时间点,是目标方
法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
3.2 AspectJ
3.2.1 切入点表达式
下面为切入点表达式,用中文表述为
execution(访问权限 方法返回值 方法声明(参数) 异常类型)
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
举例
execution(public * *(..))
指定切入点为:任意公共方法。
execution(* set*(..))
指定切入点为:任何一个以“set”开始的方法。
execution(* com.xyz.service..(..))
指定切入点为:定义在 service 包里的任意类的任意方法。
execution(* com.xyz.service...(..))
指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后
面必须跟“*”,表示包、子包下的所有类。
execution(* ..service..*(..))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
3.2.2 5种通知类型
所有通知方法可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式、方法签名、目标对象等。
AspectJ 中常用的通知有五种类型:
(1)前置通知 @Before
(2)后置通知 @AfterReturing
该注解的 returning 属性就是用于指定接收方法返回值的变量名的,可作为通知的参数
(3)环绕通知 @Around
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
(4)异常通知 @AfterThrowing
该注解的 throwing 属性用于指定所发生的异常类对象,可作为通知的参数
(5)最终通知 @After
无论目标方法是否执行,该通知都会执行。
(6) 定义切入点 @PointCut
可以用一个方法名表示一个切入点表达式,其他通知使用的时候可以用对应的方法取代切入点表达式。
注:在5种通知都有的情况下,如果目标对象的方法本身有异常,执行顺序是Around->Before->方法->Around->After->AfterThrowing,如果没有异常,执行顺序是Around->Before->方法->Around->After->AfterReturing。如果其他的通知出现异常,AfterThrowing同样会执行。特别的,如果 AfterReturing 抛出异常,则5种通知都会执行。
示例如下:
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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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 http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- <context:component-scan base-package="com.bjpowernode.service" />-->
<bean id="someServiceImpl" class="com.bjpowernode.service.SomeServiceImpl"></bean>
<bean id="myAspect" class="com.bjpowernode.service.MyAspect"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
接口:
package com.bjpowernode.service;
public interface SomeService {
String doOther(int num);
}
实现类:
package com.bjpowernode.service;
public class SomeServiceImpl implements SomeService {
@Override
public String doOther(int num) {
System.out.println("业务方法"+1/num);
return String.valueOf(num);
}
}
切面类:
package com.bjpowernode.service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
@Aspect
public class MyAspect {
@Before("mypt()")
public void myBefore(JoinPoint jp){
Signature signature = jp.getSignature();
System.out.println("方法定义"+signature);
for(Object arg:jp.getArgs()){
System.out.println("参数"+arg+","+arg.getClass());
}
System.out.println("Before");
}
@AfterReturning(value = "mypt()",returning = "retu")
public void myAfterReturning(JoinPoint jp,Object retu){ ;
System.out.println("返回的是"+retu);
System.out.println("afterReturning");
System.out.println(1/0);
}
@Around(value = "mypt()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
Object obj = null;
System.out.println("Around");
System.out.println("环绕通知,通知之前");
obj = pjp.proceed();
System.out.println("环绕通知,通知之后");
return obj;
}
@AfterThrowing(value = "mypt()",throwing="ex")
public void myAfterThrowing(Throwable ex){
System.out.println("AfterThrowing:"+ex.getMessage());
}
@After("mypt()")
public void myAfter(){
System.out.println("After");
// System.out.println(1/0);
}
@Pointcut("execution(* com.bjpowernode.service.SomeServiceImpl.do*(..))")
private void mypt(){
}
}