以下笔记来自于b站视频 https://www.bilibili.com/video/BV1Gt411N7HF
在这里感谢老师的无私贡献 让我学会很多东西
# spring
大纲 :
1. spring概念
2. IOC 容器
3. Aop
4. JdbcTemplate
5. 事务管理
6. spring5的新特性
## 两大核心机制
- IoC(控制反转) /DI (依赖注入)
- AOP(面向切面编程)
spring是一个企业级开发框架,是软件设计层面的框架,优势在于将应用程序进行分层,开发者可以自主选择组件
MVC : Struts2 、Spring MVC
ORMapping : Hibernate 、MyBatis 、Spring Data
## Ioc的使用
### IoC的使用
1. 创建一个maven工程
2. 添加依赖
```xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
</dependencies>
```
3. 创建实体类Student
```java
package tech.haonan;
import lombok.Data;
@Data
public class Student {
private long id;
private String name;
private int age;
}
```
4. 传统开发方式和IoC对比
传统开发的方式
```java
package tech.haonan.test;
import tech.haonan.entity.Student;
public class test {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
student.setAge(15);
student.setId(1);
System.out.println(student);
}
}
```
Ioc方式
resources下新建spring.xml(文件名可以任意取)
```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="student" class="tech.haonan.entity.Student">
<property name="id" value="1"></property>
<property name="age" value="22"></property>
<property name="name" value="张三"></property>
</bean>
</beans>
```
test类中代码
```java
package tech.haonan.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import tech.haonan.entity.Student;
public class test {
public static void main(String[] args) {
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
//获取id名为student的类 需要强制转换
Student student= (Student) applicationContext.getBean("student");
System.out.println(student); //输出Student(id=1, name=张三, age=22)
}
}
```
### 配置文件
- 通过配置`bean`标签来完成对象的管理
- `id` : 对象名
- `class` : 对象的模板类(交给IoC容器管理的类必须有无参构造器,因为Spring底层是用反射机制来创建对象,调用的是无参构造)
- 对象的属性成员变量通过property 标签完成
- `name` 是成员变量名
- `value`是成员变量的值(基本数据类型 ,String类型可以直接赋值 )
- `ref` :将IoC的另外一个bean赋给当前的成员变量(DI)
```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="student" class="tech.haonan.entity.Student">
<property name="id" value="1"></property>
<property name="age" value="22"></property>
<property name="name" value="张三"></property>
<property name="address" ref="address"></property>
</bean>
<bean id="address" class="tech.haonan.entity.Address">
<property name="id" value="1"></property>
<property name="name" value="山东省"></property>
</bean>
</beans>
```
### IoC 底层原理
- 读取配置文件,解析XML
- 通过反射机制来实例化配置文件中所配置的所有的bean
```shell
目录结构如下
aispringioc/
├── aispringioc.iml
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── tech
│ │ │ └── haonan
│ │ │ ├── entity
│ │ │ │ ├── Address.java
│ │ │ │ └── Student.java
│ │ │ ├── ioc
│ │ │ │ ├── ApplicationContext.java
│ │ │ │ ├── ClassPathXmlApplicationContext.java
│ │ │ │ └── Test.java
│ │ │ └── test
│ │ │ └── test.java
│ │ └── resources
│ │ └── spring.xml
│ └── test
│ └── java
```
```java
//src/main/java/tech/haonan/ioc/ApplicationContext.java
package tech.haonan.ioc;
public interface ApplicationContext {
public Object getBean(String id);
}
```
```java
//src/main/java/tech/haonan/ioc/ClassPathXmlApplicationContext.java文件
//是src/main/java/tech/haonan/ioc/ApplicationContext.java 接口的实现类
package tech.haonan.ioc;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
//这个只是实现了一部分功能 ref的引用没有实现 因为太麻烦了 需要考虑递归
public class ClassPathXmlApplicationContext implements ApplicationContext{
private Map<String ,Object> ioc = new HashMap<>();
public ClassPathXmlApplicationContext(String path){
try {
SAXReader reader = new SAXReader();
Document document = reader.read("./src/main/resources/"+path);
// System.out.println(document);
Element root = document.getRootElement();
// System.out.println(root);
Iterator<Element> iterator = root.elementIterator();
while(iterator.hasNext()){
Element element = iterator.next();
// System.out.println(element);
String id = element.attributeValue("id");
String className = element.attributeValue("class");
// System.out.println(id);
// System.out.println(className);
//通过反射机制创建对象
Class clazz = Class.forName(className);
//获取无参构造函数 创建目标对象
Constructor constructor= clazz.getConstructor();
// System.out.println(constructor);
Object object= constructor.newInstance();
//给目标对象赋值
Iterator<Element> beanIter = element.elementIterator();
while (beanIter.hasNext()){
Element property = beanIter.next();
String name = property.attributeValue("name");
String valueStr = property.attributeValue("value");
String ref = property.attributeValue("ref");
if(ref == null){
// System.out.println(ref);
//将第一个字母取出来 然后大写 拼接成setXxx
String methodName = "set"+name.substring(0,1).toUpperCase()+name.substring(1);
Field field = clazz.getDeclaredField(name);
// System.out.println(methodName);
Method method = clazz.getDeclaredMethod(methodName,field.getType());
// System.out.println(method);
// System.out.println(field.getType().getName());
//根据成员变量的数据类型将value进行转换 因为都是string类型
Object value = null;
if (field.getType().getName() == "long"){
value = Long.parseLong(valueStr);
}
if (field.getType().getName() == "java.lang.String"){
value = valueStr;
}
if(field.getType().getName() == "int"){
value = Integer.parseInt(valueStr);
}
method.invoke(object,value);
}else {
//把ref取出来 去找这么一个bean
//这里没实现
}
ioc.put(id ,object);
}
// System.out.println(object);
}
} catch (DocumentException e) {
e.printStackTrace();
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (NoSuchMethodException e){
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public Object getBean(String id) {
return ioc.get(id);
}
}
```
#### 两种方式来调用bean
##### 通过id调用
```java
//src/main/java/tech/haonan/ioc/Test.java 文件 使用的是自己的
package tech.haonan.ioc;
import tech.haonan.entity.Student;
//自己写的IoC控制翻转的调用
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
}
}
```
##### 运行时类的弊端
```java
//src/main/java/tech/haonan/test/test.java 文件
//这里调用的是Spring的Ioc方法
package tech.haonan.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import tech.haonan.entity.Student;
//spring 自带的IoC控制翻转 可以使用运行时类
public class test {
public static void main(String[] args) {
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
// Student student = (Student) applicationContext.getBean("student");
//这里可以使用运行时类 但是运行时类不能唯一确定对象
Student student = (Student) applicationContext.getBean(Student.class);
System.out.println(student);
}
}
```
### spring的bean的一些配置
#### 通过有参构造创建bean
- 在实体类中创建对应的有参构造函数
- 配置文件
##### 通过过name获取
```xml
<bean id="student1" class="tech.haonan.entity.Student">
<constructor-arg name="id" value="3"></constructor-arg>
<constructor-arg name="name" value="小明" ></constructor-arg>
<constructor-arg name="age" value="55"></constructor-arg>
<constructor-arg name="address" ref="address"></constructor-arg>
</bean>
```
##### 通过下标index 实现
```xml
<bean id="student1" class="tech.haonan.entity.Student">
<constructor-arg index="0" value="3"></constructor-arg>
<constructor-arg index="1" value="小明" ></constructor-arg>
<constructor-arg index="2" value="55"></constructor-arg>
<constructor-arg index="3" ref="address"></constructor-arg>
</bean>
```
#### ref有多个应该怎么映射 (给bean注入集合)
```java
//实体类中的定义
private List<Address> addresses;
```
```xml
//spring.xml 中的bean配置
<bean id="student" class="tech.haonan.entity.Student">
<property name="id" value="1"></property>
<property name="age" value="22"></property>
<property name="name" value="张三"></property>
<property name="addresses" >
<list><!--这里就注入了两个address 一个ref无法解决-->
<ref bean="address"></ref>
<ref bean="address1"></ref>
</list>
</property>
</bean>
<bean id="address" class="tech.haonan.entity.Address">
<property name= "id" value="1"></property>
<property name="name" value="山东省"></property>
</bean>
<bean id="address1" class="tech.haonan.entity.Address">
<property name= "id" value="2"></property>
<property name="name" value="山西省"></property>
</bean>
```
最后打印
```java
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
//打印Student(id=1, name=张三, age=22, addresses=[Address(id=1, name=山东省), Address(id=2, name=山西省)])
```
#### scope作用域
spring管理的bean是根据scope来生成的,表示bean的作用域,共四种
- singleton: 单例,表示通过Ioc容器获取的bean是唯一的
- prototype: 原型,表示通过Ioc容器获取的bean 不唯一
- request: 请求 ,表示在一次HTTP请求内有效
- session : 回话,表示在一个用户会话内有效
request和session只适用于web项目,大多数情况下单例和原型较多
1. 单例模式(默认模式)
```xml
<bean id="student" class="tech.haonan.entity.Student" scope="singleton"></bean>
```
```java
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
Student student1 = (Student) applicationContext.getBean("student");
System.out.println(student == student1); //输出为true
```
2. prototype模式
```xml
<bean id="student" class="tech.haonan.entity.Student" scope="prototype"></bean>
```
此时输出为false
prototype模式当业务代码获取Ioc容器的bean时,Spring才去调用无参构造创建对应 的bean
singleton 模式无论业务代码是否获取Ioc容器中的bean加载配置文件的时候就会创建
### spring的继承
与java的继承不同,java是类层面的继承,子类可以继承父类的内部结构信息. Spring是对象层面的继承,子对象可以继承父类对象的属性值
注意继承的时候是需要实体类的属性子类一定大于父类 否则会报错
比如 父类有 age id 和name 那么 子类 必须 有age id和name 否则就不能完成继承
```xml
<!--//src/main/resources/spring.xml-->
<bean id="student" class="tech.haonan.entity.Student" >
<property name="id" value="1"></property>
<property name="age" value="22"></property>
<property name="name" value="张三"></property>
<property name="addresses" >
<list>
<ref bean="address"></ref>
<ref bean="address1"></ref>
</list>
</property>
</bean>
<!--stu继承了student-->
<bean id="stu" class="tech.haonan.entity.Student" parent="student"></bean>
```
```java
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student stu = (Student) applicationContext.getBean("stu");
System.out.println(stu); //此时输出和student一样 因为stu继承了student
```
### spring的依赖
与继承类似,依赖也是描述bean和bean之间的一种关系,被依赖的bean一定是先创建,再创建依赖的bean,A依赖于B,B先创建,然后再创建A。
没有依赖的时候,spring加载bean是从上到下依次加载对象, 但是如果有了依赖,就可能不会按照顺序加载
```xml
<!--//src/main/resources/spring_dependency.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">
<!--这里就是先加载student 后加载user-->
<bean id="user" class="tech.haonan.entity.User" depends-on="student"></bean>
<bean id="student" class="tech.haonan.entity.Student" ></bean>
</beans>
```
### spring 的p命名空间
p命名空间是对IoC/DI的简化操作,使用p命名空间可以更加方便的完成bean的配置以及bean之间的依赖注入。
xml文件中需要插入这么一句话 否则会报错
> xmlns:p="http://www.springframework.org/schema/p"
```xml
<!--//src/main/resources/spring_p.xml-->
<bean id="student" class="tech.haonan.entity.Student" p:id="1" p:name="张三" p:age="18" p:addresses-ref="address1"></bean>
<!--上边这一行相当于下边六行-->
<!-- <bean id="student2" class="tech.haonan.entity.Student" >-->
<!-- <property name="id" value="1"></property>-->
<!-- <property name="name" value="张三"></property>-->
<!-- <property name="age" value="18"></property>-->
<!-- <property name="addresses" ref="address1"></property>-->
<!-- </bean>-->
<bean id="address1" class="tech.haonan.entity.Address" p:id="1" p:name="山东省"></bean>
```
### spring的工厂模式
IoC通过工厂模式创建bean的方式有两种
#### 静态工厂
##### 原始的工厂的创建与调用
实体类Car
```java
//src/main/java/tech/haonan/entity/Car.java 文件
package tech.haonan.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Car {
private long id;
private String name;
}
```
创建一个类叫做 StaticCarFactory 用来生产汽车
```java
//src/main/java/tech/haonan/entity/StaticCarFactory.java 文件
package tech.haonan.factory;
import tech.haonan.entity.Car;
import java.util.HashMap;
import java.util.Map;
public class StaticCarFactory {
private static Map<Long , Car> carMap;
static {
carMap =new HashMap<Long,Car>();
carMap.put(1L,new Car(1L,"宝马"));
carMap.put(2L,new Car(2L,"奔驰"));
}
public static Car getCar(long id){
return carMap.get(id);
}
}
```
test类中调用
```java
//src/main/java/tech/haonan/test/test.java 文件
package tech.haonan.test;
import tech.haonan.entity.Car;
import tech.haonan.factory.StaticCarFactory;
public class test {
public static void main(String[] args) {
Car car = StaticCarFactory.getCar(1L);
System.out.println(car);
}
}
```
##### 交给spring去管理
用到几个对象就配置一个bean就行
创建一个类叫做 StaticCarFactory 用来生产汽车和前面一致
使用factory-method 属性
```xml
<!--//src/main/resources/spring_factory.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="car" class="tech.haonan.factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="1"></constructor-arg>
</bean>
</beans>
```
test类中调用 生产汽车
```java
//src/main/java/tech/haonan/test/test.java 文件
package tech.haonan.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import tech.haonan.entity.Car;
public class test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_factory.xml");
Car car = (Car) applicationContext.getBean("car");
System.out.println(car);
}
}
```
#### 实例工厂
##### 原始的工厂的创建与调用
实现类都是一样的 上边有
创建一个InstanceCarFactory类
```java
//src/main/java/tech/haonan/factory/InstanceCarFactory.java 文件
package tech.haonan.factory;
import tech.haonan.entity.Car;
import java.util.HashMap;
import java.util.Map;
public class StaticCarFactory {
private static Map<Long , Car> carMap;
static {
carMap =new HashMap<Long,Car>();
carMap.put(1L,new Car(1L,"宝马"));
carMap.put(2L,new Car(2L,"奔驰"));
}
public static Car getCar(long id){
return carMap.get(id);
}
}
```
test类的调用
```java
//src/main/java/tech/haonan/test/test.java 文件
package tech.haonan.test;
import tech.haonan.entity.Car;
import tech.haonan.factory.InstanceCarFactory;
public class test {
public static void main(String[] args) {
InstanceCarFactory instanceCarFactory = new InstanceCarFactory();
Car car = instanceCarFactory.getCar(1L);
System.out.println(car);
}
}
```
##### 交给spring去管理
car的实体类和前面一样
创建一个InstanceCarFactory类 和上边一致
使用factory-bean和factory-method 属性
```xml
<!--//src/main/resources/spring_factory.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-->
<bean id="carFactory" class="tech.haonan.factory.InstanceCarFactory" ></bean>
<!--配置实例工厂创建Car-->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="1"></constructor-arg>
</bean>
</beans>
```
Test类中进行调用
```java
//src/main/java/tech/haonan/test/test.java 文件
package tech.haonan.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import tech.haonan.entity.Car;
public class test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_factory.xml");
Car car = (Car) applicationContext.getBean("car2");
System.out.println(car);
}
}
```
### Ioc自动装载(Autowire)
IoC负责创建对象,DI负责完成对象的依赖注入,通过配置property标签的ref属性来完成,同时spring提供了另外一种更加简便的方式,不需要手动配置property,IoC容器会自动选择bean完成注入。
自动装载两种方式
- byName : 通过属性名自动装载
- byType: 通过类型自动装载
#### byName
Person实体类的创建
```java
//src/main/java/tech/haonan/entity/Person.java 文件
package tech.haonan.entity;
import lombok.Data;
@Data
public class Person {
private long id;
private String name;
private Car car555;
}
```
Car实体类的创建
```java
//src/main/java/tech/haonan/entity/Car.java 文件
package tech.haonan.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car {
private long id;
private String name;
}
```
xml文件的配置
```xml
<!--//src/main/resources/spring_autowire.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="person" class="tech.haonan.entity.Person">-->
<!-- <property name="name" value="张三"></property>-->
<!-- <property name="id" value="1"></property>-->
<!-- <property name="car" ref="car"></property>-->
<!-- </bean>-->
<!-- <bean id="car" class="tech.haonan.entity.Car">-->
<!-- <property name="id" value="1"></property>-->
<!-- <property name="name" value="奔驰"></property>-->
<!-- </bean>-->
<!--上方的代码和下方代码是等价的 下方加上 autowire="byName" 后 id="person"的bean会自动读取
实体Person类的成员 private Car car555; 他会寻找叫做car555的bean并自动注入到person的bean中
-->
<bean id="person" class="tech.haonan.entity.Person" autowire="byName">
<property name="name" value="张三"></property>
<property name="id" value="1"></property>
</bean>
<bean id="car555" class="tech.haonan.entity.Car">
<property name="id" value="1"></property>
<property name="name" value="奔驰"></property>
</bean>
</beans>
```
#### byType
Car实体类中该名称 private Car car555; 为 private Car car;
此时的id名字为car555 实体类名称为car 但是不影响注入 他会自己寻找beans中符合类型的bean
但是如果有两个同样类型的bean 那么就会报错
```xml
<!--//src/main/resources/spring_autowire.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="person" class="tech.haonan.entity.Person">-->
<!-- <property name="name" value="张三"></property>-->
<!-- <property name="id" value="1"></property>-->
<!-- <property name="car" ref="car"></property>-->
<!-- </bean>-->
<!-- <bean id="car" class="tech.haonan.entity.Car">-->
<!-- <property name="id" value="1"></property>-->
<!-- <property name="name" value="奔驰"></property>-->
<!-- </bean>-->
<!--上方的代码和下方代码是等价的 下方加上 autowire="byName" 后 id="person"的bean会自动读取
实体Person类的成员 private Car car555; 他会寻找叫做car555的bean并自动注入到person的bean中
-->
<bean id="person" class="tech.haonan.entity.Person" autowire="byType">
<property name="name" value="张三"></property>
<property name="id" value="1"></property>
</bean>
<bean id="car555" class="tech.haonan.entity.Car">
<property name="id" value="1"></property>
<property name="name" value="奔驰"></property>
</bean>
</beans>
```
## AOP的使用
### Aop简介
Aop : aspect oriented programming 面向切面编程
将复杂的需求分解出不同方面,将散布在系统中的公共功能集中解决
Aop其实是面向对象的一个补充,在运行时,动态地把代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是Aop
面向切面编程,基于动态代理,可以使用jdk,cglib两种代理方式,aop就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式去使用动态代理.
增加的功能叫做切面 , 切面都是非业务方法,可以进行独立使用
#### 怎么理解面向切面编程
1. 需要在分析项目功能时,找出切面
2. 合理的安排切面的执行时间 (在目标方法前,还是目标方法后)
3. 合理的安排切面执行的位置,在那个方法去增加功能
#### 术语
1. 切面 : 常见的切面功能有日志,事务,统计信息,参数检查,权限验证
2. JoinPoint : 连接点,连接业务方法和切面的位置,就是某类中的业务方法
3. PointCut: 切入点,指多个连接点方法的集合.
4. 目标对象: 要给那个类的对象增加功能,这个类就是目标对象
5. advice: 通知 ,通知表示切面功能执行的时间
6. target : 代理的动态对象
7. 织入: 是指将增强应用到目标对象来创建新的代理对象的过程 比如对对象 account 来说 ,原本对象是不支持事务的,我创建一个代理对象,他其中的加入事务的过程叫做织入.
8. Proxy 一个类被AOP织入增强后,就产生一个结果代理类
我们要做什么
编写核心业务代码(开发主线) ,大部分程序员来做,
将公共代码抽取出来,制作成通知(最后阶段去做,一个汇总归纳,去重的过程)
在配置文件中,声明切入点和通知之间的关系,即切面
spring 帮我们去做什么
spring 监控框架的执行,一点监控到切入点方法被运行,使用代理知识,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行.
注意:所有的切入点都是连接点 ,但反之不成立,只有被增强的连接点才叫做切入点
#### 切面的关键要素
1. 切面的功能代码,切面干什么
2. 切面的执行位置,使用PointCut表示切面执行的位置
3. 切面的执行时间是使用advice来表示
前置通知 -> 执行环节 ->后置通知 如果执行失败 被catch到了 就是异常通知 finally中是最终通知
### 动态代理
#### 实现方式 :
1. jdk动态代理,使用jdk中的proxy,method,invocationhandler创建代理对象.jdk动态代理要求目标类必须实现接口
2. cglib动态代理,第三方的工具库,创建dialing对象,原理是继承,通过继承目标类,创建子类,子类就是代理对象,要求目标类不能是final的,方法也不能是final的.
#### 动态代理的作用:
1. 在目标类源代码不改变的情况下,增加功能
2. 减少代码的重复
3. 专注业务逻辑
4. 解耦合 ,让你的业务功能和日志,事务非事务功能分离
### AspectJ
因为spring的aop比较笨重,项目开发中很少使用,spring主要是子啊事务处理的时候使用aop
一个开源的专门做aop的框架,spring框架中集成了aspectJ 框架 ,通过spring就可以使用aspectJ框架的功能了
### 第一个例子(使用xml配置)
#### 目录结构
```shell
├─src
├─main
│ ├─java
│ │ └─tech
│ │ └─haonan
│ │ ├─service
│ │ │ UserService.java
│ │ │ UserServiceImpl.java //需要加强的实现类A
│ │ │
│ │ └─utils
│ │ Logger.java // 加强类B (例如日志打印)
│ │
│ ├─resources
│ spring.xml //spring的配置文件
└─test
├─java
└─tech
└─haonan
└─test
AopTest.java //测试类
```
#### UserServiceImpl.java 需要加强的类
```java
public class UserServiceImpl implements UserService {
@Override
public void saveUser() { // 无参无返回值
System.out.println("saveUser");
}
@Override
public void updateUser(int i) { // 有参 无返回值
System.out.println("updateUser");
}
@Override
public int deleteUser(){ // 无参 有返回值
System.out.println("deleteUser");
return 0;
}
}
```
#### Logger.java 加强类B
```java
package tech.haonan.utils;
import org.aspectj.lang.ProceedingJoinPoint ;
/**
* @Author: yhn
* @Date: 10/11/2020 8:44 PM
* @Description: 记录日志的工具类 提供公共代码
**/
public class Logger {
// /**
// * 前置通知
// */
// public void beforePrintLog(){
// System.out.println("前置通知 Logger类中beforePrintLog打印");
// }
//
// /**
// * 后置通知
// */
// public void afterReturningPrintLog(){
// System.out.println("后置通知 Logger类中afterReturningPrintLog打印");
// }
//
//
// /**
// 异常通知
// */
// public void afterThrowingErrorPrintLog(){
// System.out.println("异常通知 Logger类中afterThrowingErrorPrintLog打印");
// }
//
// /**
// * 最终通知
// */
// public void finallyPrintLog(){
// System.out.println("最终通知 Logger类中finallyPrintLog打印");
// }
/**
* 环绕通知
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
System.out.println("前置通知 Logger类中aroundPrintLog打印");
Object []args = pjp.getArgs();
rtValue = pjp.proceed(args);
System.out.println("后置通知 Logger类中aroundPrintLog打印");
return rtValue;
}catch (Throwable t){
System.out.println("异常通知 Logger类中aroundPrintLog打印");
throw new RuntimeException(t);
}finally {
System.out.println("最终通知 Logger类中aroundPrintLog打印");
}
}
}
```
#### spring.xml的配置 (重点)
```xml
实际开发中 的通常写法
* tech.haonan.service.UserServiceImpl.*(..)
```
```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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring的ioc 把service对象配置进来-->
<bean id="userService" class="tech.haonan.service.UserServiceImpl">
</bean>
<!--spring 中基于xml的aop配置步骤
1. 把通知的bean也交给spring来管理
2. 使用aop:config标签表明开始AOP的配置
3. 使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类的bean的id
4. 在apo:aspect标签的内部使用对应的标签来配置通知的类型
现在的实例是让printlog方法在切入点方法执行之前执行,所以是前置通知
aop:before 表示前置通知
method属性: 用于指定Logger类中哪一个方法是前置通知
pointcut属性: 用于指定切入点表达式,该表达式的含义是指的对业务层那些方法增强
切入表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名...类名.方法名(参数列表)
例子:
public void tech.haonan.service.UserServiceImpl.saveUser()
实际开发中 的通常写法
* tech.haonan.service.UserServiceImpl.*(..)
全通配符写法
1.public , private 或 protected 可以省略
void tech.haonan.service.UserServiceImpl.saveUser()
2. 返回值可以使用通配符 表示任意返回值
* tech.haonan.service.UserServiceImpl.saveUser()
3.包名可以使用通配符 表示任意包 但是有几级包,就需要写几个*
* *.*.*.*.saveUser()
4. 包名可以使用.. 表示当前包及其子包
* *..saveUser()
5.类名和方法都可以使用*来表示任意类和方法
* *..*() 只执行没有参数的方法
方法里
如果是基本类型 例如int 直接写 int
* *..*(int)
如果是引用类型写包名.类名的方式 java.lang.String
* *..*(java.lang.String)
所有有参数函数都执行
* *..*(*)
所有函数都执行
* *..*(..))
* *..*.*(..)
-->
<!--配置logger类-->
<bean id="logger" class="tech.haonan.utils.Logger">
</bean>
<!--配置aop-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger" >
<!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
<!--第一个是只对一个方法进行增强 第二个是对所有方法进行增强-->
<!-- <aop:before method="printLog" pointcut="execution(public void tech.haonan.service.UserServiceImpl.saveUser())"/>-->
<!--前置通知-->
<aop:before method="beforePrintLog" pointcut="execution(* tech.haonan.service.UserServiceImpl.*(..))"/>
<!--后置通知-->
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(* tech.haonan.service.UserServiceImpl.*(..))"/>
<!--异常通知-->
<aop:after-throwing method="afterThrowingErrorPrintLog" pointcut="execution(* tech.haonan.service.UserServiceImpl.*(..))"/>
<!--最终通知-->
<aop:after method="finallyPrintLog" pointcut="execution(* tech.haonan.service.UserServiceImpl.*(..))"/>
</aop:aspect>
</aop:config>
<!--总结一下
1.就是有一个类A需要加强 加强类B和它一块作为Bean 被spring自动注入
2. 配置切面 切面就是要执行的加强代码
3. 配置 切入点 在saveUser() 方法之前执行方法printlog
-->
</beans>
```
#### 测试类
```java
package tech.haonan.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import tech.haonan.service.UserService;
/**
* @Author: yhn
* @Date: 10/12/2020 6:41 PM
* @Description: 测试Aop配置
**/
public class AopTest {
@Test
public void test1(){
//1. 获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
//2. 获取对象
UserService us = (UserService)ac.getBean("userService");
//3.执行方法
us.saveUser();
/* 输出
前置通知 Logger类中beforePrintLog打印
saveUser
后置通知 Logger类中afterReturningPrintLog打印
最终通知 Logger类中finallyPrintLog打印
* */
/*
前置通知 Logger类中aroundPrintLog打印
saveUser
后置通知 Logger类中aroundPrintLog打印
最终通知 Logger类中aroundPrintLog打印
* */
}
}
```
### 第二个例子(使用注解配置)
#### 没有使用Aop的项目
创建maven项目 pom.xml中添加以下代码
```xml
<!--pom.xml-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
```
定义一个计算器接口Cal,定义四个方法
```java
//src/main/java/tech/haonan/utils/Cal.java
package tech.haonan.utils;
public interface Cal {
public int add(int num1,int num2);
public int sub(int num1,int num2);
public int mul(int num1,int num2);
public int div(int num1,int num2);
}
```
完成Cal的实现类
```java
//src/main/java/tech/haonan/utils/Impl/CalImpl.java 文件
package tech.haonan.utils.Impl;
import tech.haonan.utils.Cal;
public class CalImpl implements Cal {
public int add(int num1, int num2) {
System.out.println("add方法是参数是【" + num1 + "," + num2+ "]");
int result = num1 + num2;
System.out.println("add方法的结果是" +result);
return result;
}
public int sub(int num1, int num2) {
System.out.println("sub方法是参数是【" + num1 + "," + num2+ "]");
int result = num1 - num2;
System.out.println("sub方法的结果是" +result);
return result;
}
public int mul(int num1, int num2) {
System.out.println("mul方法是参数是【" + num1 + "," + num2+ "]");
int result = num1 * num2;
System.out.println("mul方法的结果是" +result);
return result;
}
public int div(int num1, int num2) {
System.out.println("div方法是参数是【" + num1 + "," + num2+ "]");
int result = num1 /num2;
System.out.println("div方法的结果是" +result);
return result;
}
}
```
加减乘除现在基本上一样 所以Aop有了应用的位置
#### 使用AOP的项目
上边的代码,日志信息和业务逻辑的耦合性很高,不利于系统的委会,使用AOP可以进行优化,AOP是采用动态代理技术来进行实现的
定义一个计算器接口Cal和上边一样
完成Cal的实现类 可以省略日志的打印
```java
//src/main/java/tech/haonan/utils/Impl/CalImpl.java 文件
package tech.haonan.utils.Impl;
import tech.haonan.utils.Cal;
public class CalImpl implements Cal {
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}
public int sub(int num1, int num2) {
int result = num1 - num2;
return result;
}
public int mul(int num1, int num2) {
int result = num1 * num2;
return result;
}
public int div(int num1, int num2) {
int result = num1 /num2;
return result;
}
}
```
创建一个类叫做 MyInvocationHandler 这个类是帮助我们创建动态代理类的类
把这个过程比喻成买房子
委托对象就是要买房子的人 而代理对象就是房产中介
```java
//src/main/java/tech/haonan/utils/MyInvocationHandler.java 文件
package tech.haonan.utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class MyInvocationHandler implements InvocationHandler {
//帮助我们创建动态代理类的类
//接受委托对象
private Object object = null;
//返回代理对象
public Object bind(Object object){
this.object = object;
//下边三个参数是 1.获取运行时类的加载器 2. 类的接口(也就是获取类的所有功能) 3.this表示MyInvocationHandler
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"方法的参数是"+ Arrays.toString(args));
//proxy的就是传进来的对象
//method 就是方法 下边是使用反射执行完业务层面直接拿到结果
//日志的信息打印是代理对象完成的 真正的业务还是由委托对象完成的
Object result = method.invoke(this.object,args);
System.out.println(method.getName()+"的结果是" + result);
return result;
}
}
```
#### AOP的Spring框架实现
spring方法对代理进行了封装,更方便使用,也更方便理解
Spring框架中不需要创建InvocationHandler,只需要创建一个切面对象,将所有的非业务代码在切面对象中完成即可。底层其实和上方原理一致。
建立一个切面对象 叫做LoggerAspect
```java
//src/main/java/tech/haonan/aop/LoggerAspect.java 文件
package tech.haonan.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect //成为切面对象
@Component //让IoC去管理
public class LoggerAspect {
//下边注解是表明 tech.haonan.utils.Impl.CalImpl中的所有方法(*表示通配符 所有方法) 括号里的".." 代表全部参数
@Before(value = "execution(public int tech.haonan.utils.Impl.CalImpl.*(..))")
public void before(JoinPoint joinPoint){
//获取方法名
String name = joinPoint.getSignature().getName();
//获取参数
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name +"方法的参数是"+ args);
}
@After(value = "execution(public int tech.haonan.utils.Impl.CalImpl.*(..))")
public void after(JoinPoint joinPoint){
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法执行完毕");
}
@AfterReturning(value = "execution(public int tech.haonan.utils.Impl.CalImpl.*(..))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name + "结果是" + result);
}
@AfterThrowing(value = "execution(public int tech.haonan.utils.Impl.CalImpl.*(..))",throwing = "error")
public void afterThrowing(JoinPoint joinPoint,Exception error){
//获取方法名
String name = joinPoint.getSignature().getName();
System.out.println(name + "方法抛出异常");
}
}
```
loggerAspect类定义出添加的两个注解
- `@Aspect` :表示该类是切面类
- `@Componet` : 将该类的对象注入到IoC容器中
具体的方法注解
`@Before`:业务执行之前
`@After`: 业务执行之后
`@AfterReturning`: 业务返回结果之后
`AfterThrowing`:如果出现异常那么就会抛出异常
cal的实现类 CalImpl.java 文件也需要添加`Component`注解
这里注解传进去的就是bean的id名字 这里是`aaaaa`
```java
//src/main/java/tech/haonan/utils/Impl/CalImpl.java 文件
package tech.haonan.utils.Impl;
import org.springframework.stereotype.Component;
import tech.haonan.utils.Cal;
@Component("aaaaa")
public class CalImpl implements Cal {
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}
public int sub(int num1, int num2) {
int result = num1 - num2;
return result;
}
public int mul(int num1, int num2) {
int result = num1 * num2;
return result;
}
public int div(int num1, int num2) {
int result = num1 /num2;
return result;
}
}
```
spring.xml 配置AOP
```xml
<!--src/main/resources/spring_aop.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="tech.haonan"></context:component-scan>
<!--使Aspect注解生效,为目标类自动生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
```
`context:component-scan`将`tech.haonan`中的所有类进行扫描,如果该类同时添加了`@Component`注解,则将该类扫描到IoC容器中,即IoC管理它的对象。
`aop:aspectj-autoproxy`让Spring容器结合切面类和目标类自动生成代理对象。
测试类Test.java 注意 id的默认值是类名的首字母小写
```java
package tech.haonan.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import tech.haonan.utils.Cal;
public class Test {
public static void main(String[] args) {
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_aop.xml");
//获取代理对象
Cal proxy = (Cal)applicationContext.getBean("aaaaa");
proxy.add(1,1);
proxy.sub(1,1);
proxy.mul(1,1);
proxy.div(1,0);
/*输出:
add方法的参数是[1, 1]
add方法执行完毕
add结果是2
sub方法的参数是[1, 1]
sub方法执行完毕
sub结果是0
mul方法的参数是[1, 1]
mul方法执行完毕
mul结果是1
div方法的参数是[1, 1]
div方法执行完毕
div结果是1
*/
}
}
```
spring