Spring架构体系图

image-20240720201545093

Spring(第一版)

Spring两大核心机制

  • IoC:工厂模式
  • AOP:代理模式

IoC

IoC是Spring框架的灵魂,控制反转,

1
Student student = new Student();

程序中的对象不用我们自己手动创建了。

那我们使用Spring的步骤:

开发步骤

1、首先创建maven工程然后导入org.springframework的依赖

lombook可以帮助开发者自动生成实体类相关的方法。在IDEA中使用必须预先安装插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.bitzh</groupId>
<artifactId>spring</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.11</version>
</dependency>
<!--lombook-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>

</dependencies>

</project>

2、在resources路径下创建spring.xml.

1
2
3
4
5
6
7
<?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="com.bitzh.entity.Student"></bean>
</beans>

3、IoC容器通过读取spring.xml配置文件,加载bean标签来创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.bitzh.test;

import com.bitzh.entity.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.sql.SQLOutput;

/**
* @Auther: oyy0v0
* @Date: 2024/7/23 - 07 - 23 - 16:16
* @Description: com.bitzh.test
* @version: 1.0
*/
public class Test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
//传统的开发方式,手动创建对象
// Student student = new Student();
// System.out.println(student);

//IoC容器自动创建对象,开发者只需要取出对象就可以
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
}
}

4、调用API获取IoC容器中已经创建的对象

IoC容器创建bean的两种方式

  • 无参构造函数
1
<bean id="student" class="com.bitzh.entity.Student"></bean>

给成员变量赋值

1
2
3
4
5
<bean id="student" class="com.bitzh.entity.Student">
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
<property name="age" value="23"></property>
</bean>
  • 有参构造函数
1
2
3
4
5
<bean id="student2" class="com.bitzh.entity.Student">
<constructor-arg name="id" value="3"></constructor-arg>
<constructor-arg name="name" value="张三"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
</bean>

如果不写name就会按照顺序填入,或者用index来配置从0开始

从IoC容器中取bean

  • 通过Id取
1
Student student = (Student) applicationContext.getBean("student");
  • 通过类型取:当IoC容器中同时存在两个以上Student的bean的时候就会抛异常,因为此时没有唯一的bean
1
Student student = (Student) applicationContext.getBean(Student.class);

bean的属性中如果包含特殊字符,如下处理即可

image-20240724094353347

IoC DI

DI指bean之间的依赖注入。依赖注入就是一个对象里面包含另一个对象,设置对象之间的级联关系,比如一个班级里面有学生。

学生类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bitzh.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
* @Auther: oyy0v0
* @Date: 2024/7/23 - 07 - 23 - 16:11
* @Description: com.bitzh.entity
* @version: 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer age;
private Classes classes;
}

班级类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.bitzh.entity;

import lombok.Data;

/**
* @Auther: oyy0v0
* @Date: 2024/7/24 - 07 - 24 - 1:04
* @Description: com.bitzh.entity
* @version: 1.0
*/
@Data
public class Classes {
private Integer id;
private String name;
}

依赖注入,把班级注入到学生里面,从学生的角度看是一对一,一个学生对应一个班级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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">

<!--class-->
<bean id="classes" class="com.bitzh.entity.Classes">
<property name="id" value="1"></property>
<property name="name" value="一班"></property>
</bean>
<!--Student-->
<bean id="student" class="com.bitzh.entity.Student">
<property name="id" value="100"></property>
<property name="name" value="张三"></property>
<property name="age" value="22"></property>
<property name="classes" ref="classes"></property>
</bean>
</beans>

bean之间的级联需要使用ref属性来完成映射,而不能使用value,否则会报错类型转换异常。

如果要将班级里面要把学生注入,从班级的角度来看一个班级多个学生,一对多的关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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">

<!--class-->
<bean id="classes" class="com.bitzh.entity.Classes">
<property name="id" value="1"></property>
<property name="name" value="一班"></property>
<property name="studentList">
<list>
<ref bean="student"></ref>
<ref bean="student2"></ref>
</list>
</property>
</bean>
<!--Student-->
<bean id="student" class="com.bitzh.entity.Student">
<property name="id" value="100"></property>
<property name="name" value="张三"></property>
<property name="age" value="22"></property>
</bean>
<bean id="student2" class="com.bitzh.entity.Student">
<property name="id" value="101"></property>
<property name="name" value="李四"></property>
<property name="age" value="18"></property>
</bean>
</beans>

注意不能两边同时注入,逻辑不通,班级里面放学生,学生里面放班级,逻辑不通。

Spring中的bean

bean是根据scope来完成,表示bean的作用域,scope有4中类型。

  • singleton ,单例,表示通过Spring容器获取的对象是唯一的、默认值。image-20240724104124637

  • prototype,原型,表示通过Spring容器获取的对象是不同的,在属性值里面添加scope然后选择prototype就行了。image-20240724104044310

  • request,请求,表示在一次HTTP请求内有效

  • session,会话,表示在一个用户会话内有效

request\session适用于web项目。

singleton模式下只要加载IoC容器,无论是否从容器中取出bean,配置文件中的bean都会被创建。

prototype模式下,如果不从IoC容器中取出就不创建,从容器中取出多少次就创建多少次。

Spring的继承

Spring的继承不同于java中的继承,区别:java中的继承是针对于类的,Spring中的继承是针对于对象(bean)。

Spring的继承中,子bean可以继承父bean中的所有成员变量的值。java是针对结构的,Spring是针对值的。用parent属性来继承父bean。

image-20240724104854846

同时子bean可以覆盖父bean的属性值。

两个不同的类,两个bean能完成继承吗?也是可以的。

Spring的继承是针对对象的,所以子bean和父bean并不需要属于同一个数据类型,只要成员列表一致即可。

Spring的依赖

用来设置两个bean的创建顺序。

IoC容器默认情况下是通过spring.xml中bean的配置顺序来决定创建顺序的,配置在前面的bean会先创建。

在不更改sping.xml配置顺序的前提下,通过设置bean之间的依赖关系来调整bean之间的创建顺序。

image-20240724163746627

这样就先创建User,在创建Account.

Spring 读取外部资源

实际开发中,数据库的配置一般会单独保存到后缀为properties中,方便维护和修改,如果使用Spring来加载数据原,就需要在spring.xml中读取properties中的数据,这就是读取外部资源。

jdbc.properties

image-20240724165022563

spring.xml

image-20240724165054345

Spring p 命名空间

p命名空间可以用来简化bean配置

1
2
3
4
5
6
7
8
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="student" class="com.bitzh.entity.Student" p:id="1" p:name="张三" p:age="18" p:classes-ref="class"></bean>
<bean id="class" class="com.bitzh.entity.Classes" p:id="1" p:name="yiban"></bean>
</beans>

Spring工厂方法

IoC通过工厂模式创建bean有两种模式:

  • 静态工厂方法:
  • 实例工厂方法:
  • 区别在于:静态工厂类不需要实例化,实例工厂类需要实例化

静态工厂方法:

1、创建类

1
2
3
4
5
@Data
public class Car {
private Integer num;
private String brand;
}

2、创建静态工厂类,静态工厂方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.bitzh.factory;

import com.bitzh.entity.Car;

import java.util.HashMap;
import java.util.Map;

/**
* @Auther: oyy0v0
* @Date: 2024/7/24 - 07 - 24 - 16:58
* @Description: com.bitzh.factory
* @version: 1.0
*/
public class StaticCarFactory {
private static Map<Integer, Car> carMap;
static{
carMap = new HashMap<>();
carMap.put(1,new Car(1,"奥迪"));
carMap.put(2,new Car(2,"奥拖"));
}
public static Car getCar(Integer num){
return carMap.get(num);
}
}

3、spring.xml

1
2
3
4
5
6
7
8
9
<?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="car1" class="com.bitzh.factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="1"></constructor-arg>
</bean>
</beans>

factory-method 指向静态方法

constructor-arg 的 value属性是代用静态方法传入的参数

实力工厂方法

1、创建实例工厂类,工厂方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.bitzh.factory;

import com.bitzh.Main;
import com.bitzh.entity.Car;

import java.util.HashMap;
import java.util.Map;

/**
* @Auther: oyy0v0
* @Date: 2024/7/24 - 07 - 24 - 17:12
* @Description: com.bitzh.factory
* @version: 1.0
*/
public class InstanceCarFactory {
private Map<Integer, Car> carMap;
public InstanceCarFactory(){
carMap = new HashMap<>();
carMap.put(1,new Car(1,"奥迪"));
carMap.put(2,new Car(2,"奥拖"));

}
public Car getCar(Integer num){
return carMap.get(num);
}
}

2、spring.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="car1" class="com.bitzh.factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="1"></constructor-arg>
</bean>
<!--实例工厂-->
<bean id="instatnceCarFactory" class="com.bitzh.factory.InstanceCarFactory"></bean>
<!--通过实例工厂获取Car-->
<bean id="car2" factory-bean="instatnceCarFactory" factory-method="getCar">
<constructor-arg value="2"></constructor-arg>
</bean>
</beans>

区别:

静态工厂方法创建Car对象,不需要实例化工厂对象,因为静态工厂的静态方法,不需要创建对象即可调用。spring.xml中只需要配置一个bean,即最终的结果Car即可。

实力工厂方法创建Car对象,需要实例化工厂对象,因为getCar方法是非静态的,就必须通过实例化对象才能调用,所以必须要创建工厂对象,spring.xml中需要配置两个bean,一个是工厂bean,一个是Car bean.

class + factory-method的形式是直接调用类中的工厂方法

spring.xml中factory-bean+factory-method的形式则是调用工厂bean中的工厂方法,就必须先创建工厂bean。

Spring IoC自动装载autowire

自动装载是Spring提供的一种更加简便的方式来完成DI,不需要手动配置peoperty,IoC会自动选择bean来注入。

自动装载有两种方式:

  • byName,通过属性名完成自动装载
  • byType,通过属性对应的数据类型来完成自动装载

1、创建Person实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.bitzh.entity;

import lombok.Data;

/**
* @Auther: oyy0v0
* @Date: 2024/7/24 - 07 - 24 - 21:40
* @Description: com.bitzh.entity
* @version: 1.0
*/
@Data
public class Person {
private Integer id;
private String name;
private Car car;
}

2、在spring.xml中配置Carhe Person对应的bean、并且通过自动装载完成依赖注入。

byName操作如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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="com.bitzh.entity.Person" autowire="byName">
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
</bean>
<bean id="car" class="com.bitzh.entity.Car">
<property name="num" value="1"></property>
<property name="brand" value="奥迪"></property>
</bean>
</beans>

byType操作如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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="com.bitzh.entity.Person" autowire="byType">
<property name="id" value="1"></property>
<property name="name" value="张三"></property>
</bean>
<bean id="car2" class="com.bitzh.entity.Car">
<property name="num" value="1"></property>
<property name="brand" value="奥迪"></property>
</bean>
</beans>

使用byType自动装载时,必须保证IoC中只有一个符合的bean,否则就会抛出异常。

Spring IoC 基于注解的开发

SpringIoC的作用帮助开发者创建项目中所需要的bean并且完成bean之间的依赖注入关系,DI。

实现该功能有两种方式一种是

  • 基于xml方式
  • 基于注解。

基于注解有两步操作,缺一不可:

1、配置自动扫包

2、添加注解

1
<context:component-scan base-package="com.bitzh.entity"></context:component-scan>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.bitzh.entity;

import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* @Auther: oyy0v0
* @Date: 2024/7/24 - 07 - 24 - 21:57
* @Description: com.bitzh.entity
* @version: 1.0
*/
@Data
@Component
public class Repository {

private Person person;
}

DI

1
2
3
4
5
6
7
8
@Data
@Component
public class Person {
private Integer id;
private String name;
private Car car;
}

1
2
3
4
5
6
@Data
@Component(value="myrepo")
public class Repository {
@Autowired
private Person person;
}

默认是byType的方式。

@Autowired默认是通过byType注入的,如果要改为byName,需要配置@Qualifier注解来完成

1
2
3
4
5
6
@Data
@Component(value = "ds")
public class Repository {
@Autowired
private Person person;
}

表示IoC中id为ds的bean注入到respository中。

实体类中普通的成员变量(String,包装类等)可以通过@Value来进行赋值。

1
2
3
4
5
6
7
8
9
@Data
@Component(value = "ps")
public class Person {
@Value("123")
private Integer id;
@Value("123")
private String name;
private Car car;
}

基于注解怎么配置两个同样的bean呢?利用多态的关系!

写一个接口写两个实现类,这样就等于有了两个实现类,然后注入进去。

但是注入的时候如果把接口注入进去就会发生不是唯一的异常,但是可以把两个实现类注入进去

或者可以用Qualifier来根据名字注入,然后利用多态把接口注入。

实际开发的使用

实际开发中我们会将程序分为三层:

  • Controller
  • Service
  • Repository(DAO)

关系:Contorller调用Service,Service调用Repository

image-20240726192118054

依靠注解的方式完成

image-20240727004747416

1、写Controller层

1
2
3
4
5
6
7
8
9
10
@Setter
public class MyController {
private MyService myService;
/**
* 模拟客户端请求
*/
public String service(Double score){
return myService.doService(score);
}
}

2、service层是接口

1
2
3
public interface MyService {
public String doService(Double score);
}

3、对应的实现层

1
2
3
4
5
6
7
8
@Setter
public class MyServiceImpl implements MyService {
private MyRepository myRepository;
@Override
public String doService(Double score) {
return myRepository.doRepository(score);
}
}

4、完成Repository层

1
2
3
public interface MyRepository {
public String doRepository(Double score);
}

5、对应的实现层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyRepositoryImpl implements MyRepository {
@Override
public String doRepository(Double score) {
String result = "";
if(score < 60){
result = "不及格";
}
if(score >= 60 && score < 80){
result = "合格";
}
if(score >= 80){
result = "优秀";
}
return result;
}
}

6、将Repository层注入到service层,将service层注入到controller层。

1
2
3
4
5
6
7
8
<bean id="controller" class="com.bitzh.controller.MyController">
<property name="myService" ref="service"></property>
</bean>
<bean id="service" class="com.bitzh.service.impl.MyServiceImpl">
<property name="myRepository" ref="repository"></property>
</bean>

<bean id="repository" class="com.bitzh.repository.impl.MyRepositoryImpl"></bean>

基于注解的方式

1、完成代码后在实现类中加上@Component并且加上@Autowired,并且加上扫包的xml

1
<context:component-scan base-package="com.bitzh"></context:component-scan>

另外,@Component注解是将标注的类加载到IoC容器中,实际开发中,可以根据业务需求,分别使用@Controller,@Service,@Repository注解来注解控制层类,业务层类,持久层类。

Spring IoC的底层原理

核心技术点:xml解析 + 反射

具体的思路:

1、根据需求编写 xml文件,配置需要创建的bean。

2、编写程序读取xml文件,获取bean的相关信息,类,属性,id。

3、根据第二步获取的信息,结合反射机制动态创建对象,同时完成属性的赋值。

4、将创建好的bean存入Map集合,设置key-value映射,key就是bean中的id值,value,就是bean 对象

5、提供方法从Map中通过id获取对应的value。

(利用dom4j解析xml)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class MyClassPathXmlApplicationContext implements ApplicationContext {
private Map<String ,Object> iocMap;

public MyClassPathXmlApplicationContext(String path) {
iocMap = new HashMap<>();
//解析XML
parseXML(path);
}

public void parseXML(String path){
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read("src/main/resources/"+path);
Element root = document.getRootElement();
Iterator<Element> rootIter = root.elementIterator();
while(rootIter.hasNext()){
Element bean = rootIter.next();
String idStr = bean.attributeValue("id");
String className = bean.attributeValue("class");
//反射动态创建对象
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
//给属性赋值
Iterator<Element> beanIter = bean.elementIterator();
while (beanIter.hasNext()){
Element property = beanIter.next();
String propertyName = property.attributeValue("name");
String propertyValue = property.attributeValue("value");
//获取Set方法
String methodName = "set"+propertyName.substring(0,1).toUpperCase()+propertyName.substring(1);
//获取属性
Field field = clazz.getDeclaredField(propertyName);
Method method = clazz.getMethod(methodName,field.getType());
Object value = propertyValue;
//类型转换
switch(field.getType().getName()){
case "java.lang.Integer":
value = Integer.parseInt(propertyValue);
break;
}
//调用方法
method.invoke(object,value);
}
//存入Map
iocMap.put(idStr,object);
}
} catch (DocumentException e) {
e.printStackTrace();
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch(NoSuchMethodException e){
e.printStackTrace();
}catch(InstantiationException e){
e.printStackTrace();
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
}

main方法

1
2
3
4
5
6
7
8
public class Test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
ApplicationContext applicationContext = new MyClassPathXmlApplicationContext("spring-Ioc.xml");
Car car = (Car) applicationContext.getBean("car");
System.out.println(car);
}
}

xml

1
2
3
4
5
6
7
8
9
10
<?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="com.bitzh.entity.Car">
<property name="num" value="1"></property>
<property name="brand" value="奥迪"></property>
</bean>
</beans>

JDK动态代理

代理模式
是通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。
房产中介代替业主卖房

代理模式的目的:在不修改原有代码的 或者没有办法修改原有代码的情况下 增强对象功能 使用代理对象 代替原来的对象去完成功能
进而达到拓展功能的目的

静态代理

静态代理中代理类与被代理类都需要实现同一个接口,这就说明我们的一个静态代理类只能代理一个类,并且还要事先知道我们要代理哪个类才能写代理类,如果我们有其他类还想使用代理那就必须再写一个代理类。然而在实际开发中我们是可能是有非常多的类是需要被代理的,并且事先我们可能并不知道我们要代理哪个类。所以如果继续使用静态代理反而会增加许多的工作量,并且效率低下,代码复用率也不好。

下面用张三打官司举例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.bitzh.test;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/14 2:34
*/
public class Test {
public static void main(String[] args) {
Person person = new Person("张三");
Court court = new Lawyer(person);
court.doCourt();
}
}

//接口
interface Court{
public void doCourt();
}
//代理类
class Lawyer implements Court{
private Person person;

public Lawyer(Person person) {
this.person = person;
}

@Override
public void doCourt() {
System.out.println("律师取证:视频证明张三不在案发现场");
System.out.println("律师总结张三不可能犯罪");
person.doCourt();
}
}
//被代理的类
class Person implements Court{
private String name;
public Person(String name){
this.name = name;
}
@Override
public void doCourt() {
System.out.println(name+"说:我没有犯罪");
}
}

image-20240814025607775

动态代理

动态代理可以针对于一些不特定的类或者一些不特定的方法进行代理,我们可以在程序运行时动态的变化代理的规则,代理类在程序运行时才创建的代理模式成为动态代理。这种情况下,代理类并不是在Java代码中定义好的,而是在程序运行时根据我们的在Java代码中的“指示”动态生成的
Proxy 动态代理 JDK动态代理 面向接口
cglib 动态代理 第三方动态代理 面向父类

下面用张三吃饭的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.bitzh.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* @author oyy0v0
* @version 1.0.0
* @create 2024/8/14 2:59
*/
public class Test2 {
//这是一个main方法,程序的入口
public static void main(String[] args) {
Dinner dinner = new Person1("张三");


//(ClassLoader loader, 被代理对象的类加载器
ClassLoader classLoader = dinner.getClass().getClassLoader();
//Class<?>[] interfaces,被代理对象所实现的所有接口,这里的dinner是多态,new的是哪个实现类就获得哪个实现类的字节码的接口
Class[] interfaces = dinner.getClass().getInterfaces();
//InvocationHandler h) 执行处理器对象,专门用于定义增强的规则
InvocationHandler myInvocationHandler = new InvocationHandler(){
Object res = null;

//在这里自定义增强的规则,invoke方法的参数proxy是代理对象,method是被代理的方法,args是方法的参数被运行时的实参
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoker方法执行了");
if(method.getName().equals("eat")){
System.out.println("饭前洗手");
//让原有的eat方法执行
res = method.invoke(dinner, args);
System.out.println("饭后刷碗");
}else{
//如果是其他方法正常执行就好
res = method.invoke(dinner, args);
}

return res;
}
};
//通过Proxy动态代理获得一个代理对象,在代理对象中,对eat方法或者某个方法进行增强
Dinner dinnerProxy = (Dinner) Proxy.newProxyInstance(classLoader,interfaces , myInvocationHandler);

dinnerProxy.eat("包子");

}
}

interface Dinner{
public void eat(String foodName);

}
class Person1 implements Dinner{
private String name;

public Person1(String name) {
this.name = name;
}

@Override
public void eat(String foodName) {
System.out.println(name+"正在吃"+foodName);
}

}
class Student implements Dinner{
private String name;

public Student(String name) {
this.name = name;
}

@Override
public void eat(String foodName) {
System.out.println(name+"正在吃"+foodName);
}


}

Spring AOP

AOP (Aspect Oriented Programming)面向切面编程。

OOP(Object Oriented Programming)面向对象编程,用对象化的思想来完成程序。

AOP是对OOP的一个补充,是在另外一个维度上抽象出对象。

具体是指程序运行时动态的将非业务代码切入到业务代码中,从而实现程序的解耦合,将非业务代码抽象成一个对象,对对象编程就是面向切面编程。

image-20240727164628389

这种代码复用性不高,维护性差。

image-20240727164953063

使用AOP优化。

AOP的优点:

  • 可以降低模块之间的耦合性
  • 提高代码的复用性
  • 提高代码的维护性
  • 集中管理非业务代码,便于维护
  • 业务代码不受非业务代码的影响,逻辑更加清晰

通过一个例子来理解AOP。

1、创建一个计算器的接口

1
2
3
4
5
6
7
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);
}

2、创建接口的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class CalImpl implements Cal {
@Override
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}

@Override
public int sub(int num1, int num2) {
int result = num1 - num2;
return result;
}

@Override
public int mul(int num1, int num2) {
int result = num1 * num2;
return result;
}

@Override
public int div(int num1, int num2) {
int result = num1 / num2;
return result;
}
}

日志打印

  • 在每个方法开始位置输出参数信息。
  • 在每个方法结束位置输出结果信息。

对于计算器来讲、加减乘除就是业务代码,日志打印就是非业务代码。

AOP如何实现?使用动态代理方式来实现。

代理首先应该具备CalImpl的所有功能,并再次基础上,扩展出打印日志的功能。

1、删除CalImple方法中的所有打印日志代码,只保留业务代码。

2、创建MyInvocationHandler类,实现InvocationHandler接口,生成动态代理类。

动态代理类,需要动态生成,需要获取到委托类的接口信息,根据这些接口信息动态生成一个代理类,然后再ClassLoader用来将动态生成的类加载到JVM中

就是说:代理类不是手动创建的,所以需要通过ClassLoader加载到内存中,同时,代理类具有被代理类的功能(接口实现)。

类加载器是统一的,每个类都是通过系统的类加载器加载到虚拟机中的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyInvocationHandler implements InvocationHandler {
//委托对象
private Object object = null;

//返回代理对象
public Object bind(Object object){
this.object = object;
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));
Object result = method.invoke(this.object, args);
System.out.println(method.getName()+" "+result);
return result;
}
}
1
2
3
4
5
6
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);
}
1
2
3
4
5
6
7
8
9
10
11
public class Test {
//这是一个main方法,是程序的入口
public static void main(String[] args) {
Cal cal = new CalImpl();

//获取代理对象
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
Cal proxy = (Cal) myInvocationHandler.bind(cal);
proxy.add(10,3);
}
}

上述代码通过动态代理机制实现了业务代码和非业务代码的解耦合,这是SpringAOP的底层实现机制,真正在使用SpringAOP进行开发时,不需要这么复杂,可以更好的理解的方式来开发。

1、创建切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.bitzh.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
* @Auther: oyy0v0
* @Date: 2024/7/27 - 07 - 27 - 18:32
* @Description: com.bitzh.aop
* @version: 1.0
*/
@Component
@Aspect
public class LoggerAspect {
@Before("execution(public int com.bitzh.aop.impl.CalImpl.add(..))")
public void before(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name+" "+args);

}
}

2、委托类也需要加入@Component

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.bitzh.aop.impl;

import com.bitzh.aop.Cal;
import org.springframework.stereotype.Component;

/**
* @Auther: oyy0v0
* @Date: 2024/7/27 - 07 - 27 - 16:54
* @Description: com.bitzh.aop.impl
* @version: 1.0
*/
@Component
public class CalImpl implements Cal {
@Override
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}

@Override
public int sub(int num1, int num2) {
int result = num1 - num2;
return result;
}

@Override
public int mul(int num1, int num2) {
int result = num1 * num2;
return result;
}

@Override
public int div(int num1, int num2) {
int result = num1 / num2;
return result;
}
}

3、spring.xml

1
2
3
4
5
<!--自动扫包-->
<context:component-scan base-package="com.bitzh"></context:component-scan>

<!--为委托对象自动生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  • @Component,将切面类加载到IoC容器中
  • @Aspect,表示该类是一个切面类
  • @Before,表示方法的实行实际是在业务方法之前。execution表达式表示切入点是CalImpl类中的add方法
  • aop:aspectj-autoproxy,Spring IoC容器会结合切面对象和委托对象自动生成动态代理对象,AOP底层就是通过动态代理机制来实现的。
  • @After,表示方法的实行实际是在业务方法之后。execution表达式表示切入点是CalImpl类中的add方法
  • @AfterReturning,表示方法的执行时机是在业务方法返回结果之后,returning是将业务方法的返回值与切面类方法的形参进行绑定
  • @AfterThrowing,表示方法的执行时机是在业务方法抛出异常后execution表达式表示切入点,throwing是将业务方法的返回值与切面类方法的形参进行绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Component
@Aspect
public class LoggerAspect {
@Before("execution(public int com.bitzh.aop.impl.CalImpl.*(..))")
public void before(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name+" "+args);

}
@After("execution(public int com.bitzh.aop.impl.CalImpl.*(..))")
public void after(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println(name+"执行完毕");

}
@AfterReturning(value = "execution(public int com.bitzh.aop.impl.CalImpl.*(..))",returning = "result")
public void afterReturn(JoinPoint joinPoint,Object result){
String name = joinPoint.getSignature().getName();
System.out.println(name + result);

}
@AfterThrowing(value = "execution(public int com.bitzh.aop.impl.CalImpl.*(..))",throwing = "ex")
public void afterThrowing(JoinPoint joinPoint,Exception ex){
String name = joinPoint.getSignature().getName();
System.out.println(name + ex);
}

}

AOP的概念:

  • 切面对象:根据切面抽象出来的一个对象,CalImpl 所有方法中需要加入日志的部分,抽象成一个切面类LoggerAspect
  • 通知:切面对象具体执行的代码,非业务代码,LoggerAspect对象打印日志的代码。
  • 目标:被切面对象,即CalImpl,将通知加入其中。
  • 代理:切面对象、通知、目标混合之后的结果,即我们使用JDK动态代理机制创建的对象
  • 连接点:需要被横切的位置,即通知要插入业务代码的具体位置。

Spring(第二版和第一版有重复)

image-20240217093926972

Spring_IOC(控制反转)

简单的说就是,创建对象的权利,或者是控制的位置,由JAVA代码转移到spring容器,由spring的容器控制对象的创建,就是控制反转,spring创建对象时,会读取配置文件中的信息,然后使用反射给我们创建好对象之后在容器中存储起来,当我们需要某个对象时,通过id获取对象即可,不需要我们自己去new.
一句话:创建对象交给容器

传统的是这样的

image-20240217110151361

现在是这样

image-20240217112605759

IOC代码测试

新建项目然后导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.5</version>
</dependency>
</dependencies>

image-20240217121048149

对应jar包

四个依赖介绍
spring-context 上下文,容器
spring-beans 创建对象
spring-core 核心jar
spring-expression 表达式jar

但是事实上,我们导入spring-context的时候,会自动导入其他依赖的jar,自动进行了依赖传递
所以,导入一个spring-context 依赖也可以

image-20240217121652168

为了方便测试,我们导入Junit测试依赖

1
2
3
4
5
6
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>

测试代码

image-20240217124939974

实现类

image-20240217125038293

接口

image-20240217125052418

这样写当调用不同的对象或者创建对象就只用改变配置文件了,创建对象的权利就转移给容器了。

IOC实现原理

image-20240217183227052

1 XML解析技术读取配置文件

1
<bean id="empDao" class="com.msb.dao.impl.EmpDaoImpl"></bean>

将上面的信息读取进入程序 对象的ID ,一个是对象的类的全路径名

2 反射技术实例化对象,放到容器中
获得类的字节码
Class clazz =Class.forName(“com.msb.dao.impl.EmpDaoImpl”)
通过字节码实例化对象
Object obj = clazz.newInstance();
将对象放到一个map集合中
map.put(“empDao”,obj)

3 工厂模式返回Bean对象 getBean方法

其实那个name其实就是bean id

​ public Object getBean(String name){
​ Object obj =map.get(name);
​ return obj;
​ }

IOC接口
BeanFactory 接口: IOC容器基本功能接口,是spring内部使用的接口,我们在处理业务时一般不直接使用该接口
ApplicationContext 接口: BeanFactory的子接口,提供更多更强大的功能,研发人员一般使用的接口

按住ctrl+h可以看到下面的子接口和类

XML实现方式DI

spring中的Bean的管理:
Bean(汉译咖啡豆). 又称JAVABean.其实就是JAVA程序程序中的一个个对象,所以Bean的管理其实就是spring对于JAVA程序中的对象的管理

管理的内容是什么

1 对象的创建 IOC
IOC 叫做控制反转,就是Spring给我们创建对象,然后我们直接用,不用自己NEW,前面已经解释过
IOC处理的是对象如何创建的问题

2 属性的赋值 DI
DI Dependency Injection,即“依赖注入” 就是创建属性时给对象属性赋值
对象功能的实现往往要依赖属性的值,那么给对象属性赋值就可以说成是依赖注入
由于对象属性不仅仅是基本数据类型,还可能是其他类,或者引用类型
那么依赖注入将会把更多的对象之间的关系整理到一起,可以行程一个庞大的依赖关系
DI处理的是对象的属性赋值和互相依赖的关系

spring给我们提供了两种关于bean的方式

1 基于XML方式的Bean管理

2 基于注解方式的Bean管理

依赖

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>

创建spring配置文件,一般spring的配置文件很多人约定俗称为application**.xml

image-20240217204216958

准备一个要实例化的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class User {
private Integer userid;
private String username;
private String password;
@Override
public String toString() {
return "User{" +
"userid=" + userid +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public User() {
System.out.println("noArgConstructor");
}
public User(Integer userid, String username, String password) {
System.out.println("allArgConstructor");
this.userid = userid;
this.username = username;
this.password = password;
}
public void setUserid(Integer userid) {
System.out.println("setUserid");
this.userid = userid;
}
public void setUsername(String username) {
System.out.println("setUsername");
this.username = username;
}
public void setPassword(String password) {
System.out.println("setpassword");
this.password = password;
}
}

IOC创建对象
通过无参构造方法构造对象

1
<bean id="user1" class="com.msb.bean.User"></bean>

标签的常见属性

1
<bean id="user1" class="com.msb.bean.User" name="user1" scope="prototype" lazy-init="true" ></bean>

id 对象的id
class 类的全路径名
name 和id类似,一般不用
scope 控制对象单例多例和使用范围
singleton作用域(scope 默认值), Spring IOC容器中只会存在一个共享的bean实例
prototype作用域部署的bean,每一次获取都会产生一个新的bean实例,相当与一个new的操作
request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义
lazy-init 懒加载 调用getBean的时候再去实例化对象

DI给对象属性赋值
1 通过set方法给对象属性赋值

1
2
3
4
5
6
<!--property 就是在使用set方法实现依赖注入-->
<bean id="user1" class="com.msb.bean.User">
<property name="userid" value="1"></property>
<property name="username" value="张三"></property>
<property name="password" value="abcdefg"></property>
</bean>

2 通过有参构造给对象属性赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--
constructor-arg 就是在使用构造方法实现依赖注入
constructor-arg 的个数必须和某个构造方法的参数个数向对应
name指的是参数名
index指的是参数的索引
value指的是参数值
-->
<bean id="user2" class="com.msb.bean.User">
<constructor-arg name="userid" value="2"></constructor-arg>
<constructor-arg name="username" value="小明"></constructor-arg>
<constructor-arg name="password" value="123456789"></constructor-arg>
</bean>
<bean id="user3" class="com.msb.bean.User">
<constructor-arg index="0" value="3"></constructor-arg>
<constructor-arg index="1" value="小黑"></constructor-arg>
<constructor-arg index="2" value="987654321"></constructor-arg>
</bean>

3 通过p名称空间和c名称空间给对象属性赋值

添加约束

1
2
3
4
5
6
7
<?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">

配置对象

1
2
3
4
<!--p名称空间,就是对property的简化处理-->
<bean id="user4" class="com.msb.bean.User" p:userid="4" p:username="小东" p:password="111111" ></bean>
<!--c名称空间,就是对constructor-arg的简化-->
<bean id="user5" class="com.msb.bean.User" c:userid="5" c:username="小西" c:password="222222" ></bean>

4 注入空值和特殊符号

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="user1" class="com.msb.bean.User">
<!--null值-->
<property name="userid">
<null></null>
</property>
<!--特殊符号 转译字符 < &lt; >&gt; & &amp; -->
<property name="username" value="&amp;xiaoming&lt;&gt;"></property>
<!-- 特殊符号 <![CDATA[内容]]> -->
<property name="password">
<value><![CDATA[&<123456>]]></value>
</property>
</bean>

5关于bean引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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">

<!--告诉容器准备一个Date对象-->
<bean id="date1" class="java.util.Date"></bean>
<bean id="Mouse1" class="com.bitzh.bean.Mouse">
<property name="name" value="Jerry"></property>
<!--bean的引用,引用外部的bean-->
<property name="birthday" ref="date1"></property>
</bean>
<bean id="cat1" class="com.bitzh.bean.Cat">
<property name="name" value="Tom"></property>
<!--引用了外部的bean-->
<!--<property name="mouse1" ref="Mouse1"></property>-->
<!--引用内部的bean-->
<property name="mouse1">
<bean class="com.bitzh.bean.Mouse">
<property name="name" value="Jerry2"></property>
<property name="birthday" ref="date1"></property>
</bean>
</property>
</bean>

</beans>

6 关于集合注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?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"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd
">
<!--定义公共集合-->
<util:list id="outerbookList">
<!--声明多个Book对象-->
<bean id="b1" class="com.bitzh.bean.Book" p:bname="JAVA" p:author="oyy"></bean>
<bean id="b2" class="com.bitzh.bean.Book" p:bname="Go" p:author="oyy"></bean>
<bean id="b3" class="com.bitzh.bean.Book" p:bname="JVM" p:author="oyy"></bean>
</util:list>
<bean id="student1" class="com.bitzh.bean.Student">
<!--数组属性注入-->
<property name="books">
<array>
<value>JAVA</value>
<value>MySQL</value>
<value>Spring</value>
</array>
</property>
<!--set集合注入-->
<property name="bookSet">
<set>
<value>JAVA</value>
<value>MySQL</value>
<value>Spring</value>
</set>
</property>
<!--list集合注入-->
<property name="bookList">
<list>
<value>JAVA</value>
<value>MySQL</value>
<value>Spring</value>
</list>
</property>
<!--map集合注入-->
<property name="bookMap">
<map>
<entry key="JAVA" value="oyy"></entry>
<entry key="Go" value="oyy"></entry>
<entry key="JVM" value="oyy"></entry>
</map>
</property>
<!--List对象集合注入-->
<property name="bookList2" ref="outerbookList"></property>
</bean>
</beans>

7 工厂方式获取bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BookFactory implements FactoryBean<Book> {


public Book getObject() throws Exception {
Book book = new Book();
book.setBname("Java");
book.setAuthor("oyy");
return book;
}

public Class<?> getObjectType() {
return null;
}


}
1
<bean id="bookFactory" class="com.bitzh.bean.BookFactory">
1
2
3
4
5
6
7
8
public class Test3 {
@Test
public void testGetBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext3.xml");
Book bookFactory = applicationContext.getBean("bookFactory", Book.class);
System.out.println(bookFactory);
}
}

bean生命周期

bean从创建到销毁经历的各个阶段以及每个阶段所调用的方法
1 通过构造器创建bean实例 执行构造器
2 为bean属性赋值 执行set方法
3 初始化bean 调用bean的初始化方法,需要配置指定调用的方法
4 bean的获取 容器对象 getBean方法
5 容器关闭销毁bean 调用销毁方法,需要配置指定调用的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class User {
private Integer userid;
private String username;
private String password;
public void initUser(){
System.out.println("第三步:User初始化");
}
public User() {
System.out.println("第一步:User构造");
}
public void destoryUser(){
System.out.println("第五步:User销毁");
}
@Override
public String toString() {
return "User{" +
"userid=" + userid +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public User(Integer userid, String username, String password) {
this.userid = userid;
this.username = username;
this.password = password;
}
public void setUserid(Integer userid) {
System.out.println("setUserid");
this.userid = userid;
}
public void setUsername(String username) {
System.out.println("第二步:User属性赋值");
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}

配置bean

1
2
3
4
5
6
7
8
9
10
11
<?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.msb.bean.User" init-method="initUser" destroy-method="destoryUser">
<property name="username" value="xiaoming"></property>
</bean>
</beans>

测试代码

1
2
3
4
5
6
7
8
9
10
11
public class Test1 {
@Test
public void testGetBean(){
ClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user",User.class);
System.out.println("第四步:User对象从容器中获取");
// 关闭容器
context.close();
}
}

关于后置处理器

简而言之就是要是想在bean初始化之前还有点操作就可以创建一个后置处理器,提前操作。

1 通过构造器创建bean实例 执行构造器
2 为bean属性赋值 执行set方法
3 把bean实例传递给bean的后置处理器的方法
4 初始化bean 调用bean的初始化方法,需要配置指定调用的方法
5 把bean实例传递给bean的后置处理器的方法
6 bean的获取 容器对象 getBean方法
7 容器关闭销毁bean 调用销毁方法,需要配置指定调用的方法

BeanPostProcessor接口作用:

如果我们想在Spring容器中完成bean实例化、配置以及其他初始化方法前后要添加一些自己逻辑处理。我们需要定义一个或多个BeanPostProcessor接口实现类,然后注册到Spring IoC容器中。

1、接口中的两个方法都要将传入的bean返回,而不能返回null,如果返回的是null那么我们通过getBean方法将得不到目标。
2、ApplicationContext会自动检测在配置文件中实现了BeanPostProcessor接口的所有bean,并把它们注册为后置处理器,然后在容器创建bean的适当时候调用它,因此部署一个后置处理器同部署其他的bean并没有什么区别。而使用BeanFactory实现的时候,bean 后置处理器必须通过代码显式地去注册,在IoC容器继承体系中的ConfigurableBeanFactory接口中定义了注册方法

1 创建后置处理器 实现 BeanPostProcesser 重写两个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyBeanProcesser implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//Object bean 实例化的bean
//String beanName bean的id
System.out.println("bean:初始化方法之前");
return bean;// 这里必须return bean
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean:初始化方法之后");
return bean;// 这里必须returnbean
}
}

2 配置后置处理器,对容器中的所有bean添加后置处理器的生命周期

1
2
3
4
5
6
7
8
9
10
11
12
<?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.msb.bean.User" init-method="initUser" destroy-method="destoryUser">
<property name="username" value="xiaoming"></property>
</bean>
<bean id="myBeanProcesser" class="com.msb.beanProcesser.MyBeanProcesser"></bean>
</beans>

bean的自动装配

通过property标签可以手动指定给属性进行注入
我们也可以通过自动转配,完成属性的自动注入,就是自动装配,可以简化DI的配置

简单来说就是可以在bean里面完成对于实体类的属性和值的注入

准备实体类

1
2
public class Dept {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Emp {
private Dept dept;
@Override
public String toString() {
return "Emp{" +
"dept=" + dept +
'}';
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
public Emp() {
}
public Emp(Dept dept) {
this.dept = dept;
}
}

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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="dept" class="com.bitzh.bean.Dept"></bean>
<!--
autowire 属性控制自动将容器中的对象注入到当前对象的属性上
byName 根据目标id值和属性值注入,要保证当前对象的属性值和目标对象的id值一致
byType 根据类型注入,要保证相同类型的目标对象在容器中只有一个实例
-->
<bean id="emp" class="com.bitzh.bean.Emp" autowire="byName"></bean>
</beans>

测试代码

1
2
3
4
5
6
7
8
public class Test2 {
@Test
public void testGetBean(){
ClassPathXmlApplicationContext context =new ClassPathXmlApplicationContext("applicationContext2.xml");
Emp emp = context.getBean("emp", Emp.class);
System.out.println(emp);
}
}

读取属性配置文件

之前我们的值已经写死了,现在可不可以在mybatis中的jdbc.properties中读取

数据库连接池技术

实际开发中我们不需要自己写,直接用别人写好的 比如使用spring中写好的c3p0连接池还有德鲁伊连接池Druid

现在我们使用德鲁伊连接池但是一开始有些基本属性需要我们自己去配置

现在在使用连接池初始化之后我们需要对里面的driver,url,password中进行赋值但是我们对这些信息不会直接写在Context中而是写在property中。

以后得连接池不叫连接池了而是叫数据源 然后再数据原里面配置数据库信息

首先先导入德鲁伊的依赖还有mysql-connecter的依赖

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.21</version>
</dependency>

image-20240218202641104

applicationContext中添加context名称空间 并读取属性配置文件
配置druid数据源将属性配置文件中的信息注入到连接池中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc_username}"></property>
<property name="password" value="${jdbc_password}"></property>
<property name="url" value="${jdbc_url}"></property>
<property name="driverClassName" value="${jdbc_driver}"></property>
</bean>
</beans>

测试代码

1
2
3
4
5
6
7
8
9
10
public class Test4 {
@Test
public void testGetBean() throws SQLException {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext4.xml");
DruidDataSource dataSource = applicationContext.getBean("dataSource", DruidDataSource.class);
System.out.println(dataSource);

}
}

注解方式实现IOC

将来实际研发中用的更多的是注解而不是用xml来写

1注解方式创建对象IOC

导入依赖 aop

@Component 放在类上,用于标记,告诉spring当前类需要由容器实例化bean并放入容器中
该注解有三个子注解
@Controller 用于实例化controller层bean
@Service 用于实例化service层bean
@Repository 用于实例化持久层bean
当不确定是哪一层,就用Component
这几个注解互相混用其实也可以,但是不推荐

第一步:在applicationContext.xml中配置开启注解扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<!--添加注解扫描,扫描指定的包,将包中的所有有注解的类实例化
base-package 后面放要扫描的包
如果有多个包需要扫描,可以使用逗号隔开 com.msb.bean,com.msb.service
或者可以写上一层包路径 com.msb
可以通过注解指定bean的id@Component("user1")
如果不指定,则id默认是 类名首字母小写
-->
<context:component-scan base-package="com.msb.bean"></context:component-scan>
</beans>

第二步:在类上添加注解,让spring容器给我们创建bean实例并存储于容器中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.bitzh.bean;

import org.springframework.stereotype.Component;

/**
* @Auther: oyy
* @Date: 2024/2/18 - 02 - 18 - 20:31
* @Description: com.bitzh.bean
* @version: 1.0
*/
@Component(value = "user")
public class User {
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bitzh.test;

import com.bitzh.bean.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* @Auther: oyy
* @Date: 2024/2/18 - 02 - 18 - 20:55
* @Description: com.bitzh.test
* @version: 1.0
*/
public class Test1 {
@Test
public void testGetBean(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext4.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
}
}

组件扫描配置注解识别

2注解方式依赖注入DI

@Autowired 根据属性数据类型自动装配 这个用的最多
@Qualifier 根据属性名称注入依赖
@Resources 可以根据类型,也可以根据名称注入
@Value 注入普通数据类型(8+String)

其实就是有一个接口,还有一个实现类,当需要用到接口的某个功能时,直接利用注解让接口的属性由实现类去赋值

Dao层
接口

image-20240219105138400

实现类

image-20240219105719687

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UserDao userDao;
//基本属性赋值 8 +String是这样的
@Value("张三")
private String name;
private Integer sage;
@Override
public void add() {
System.out.println("UserServiceImpl");
System.out.println(name);
userDao.add();
}
}

这样的话呢要是不想写死就可以创建一个properties然后把值放在里面,然后配置文件的时候用

1
2
<context:property-placeholder location="classpath:*.properties"></context:property-placeholder>
<context:component-scan base-package="com.bitzh" ></context:component-scan>

这样就可以把配置文件中的基本属性赋值直接读取,然后用${}来赋值了

1
2
@Value("${username}")
private String sname;

中文乱码问题

image-20240219112135303

配置类方式实现IOC和DI

配置文件现在变成这样了就是之前告诉包扫描文件的xml变成了java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.bitzh.config;

import org.springframework.context.annotation.ComponentScan;

/**
* @Auther: oyy
* @Date: 2024/2/19 - 02 - 19 - 11:23
* @Description: com.bitzh.config
* @version: 1.0
*/
@ComponentScan(basePackages = "com.bitzh")
@PropertySource("classpath:*.properties")
public class SpringConfig {
}

测试代码就要变成扫描SpringConfig的字节码文件了

1
2
3
4
5
6
@Test
public void testGetBean2(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserServiceImpl userService = context. getBean("userServiceImpl",UserServiceImpl.class);
userService.add();
}

代理模式概念和静态代理

代理模式
是通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能,如添加权限,访问控制和审计等功能。
房产中介代替业主卖房

image-20240219113034645

静态代理
静态代理中代理类与被代理类都需要实现同一个接口,这就说明我们的一个静态代理类只能代理一个类,并且还要事先知道我们要代理哪个类才能写代理类,如果我们有其他类还想使用代理那就必须再写一个代理类。然而在实际开发中我们是可能是有非常多的类是需要被代理的,并且事先我们可能并不知道我们要代理哪个类。所以如果继续使用静态代理反而会增加许多的工作量,并且效率低下,代码复用率也不好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.bitzh.test;

/**
* @Auther: oyy
* @Date: 2024/2/19 - 02 - 19 - 11:35
* @Description: com.bitzh.test
* @version: 1.0
*/
public class Test01 {
//这是一个main方法,是程序的入口:
public static void main(String[] args) {
Person person = new Person("张三");
Court court = new Lawyer(person);
court.doCourt();
}

}
//接口
interface Court{
void doCourt();
}
//代理类
class Lawyer implements Court{
private Person person;

public Lawyer(Person person) {
this.person = person;
}

@Override
public void doCourt() {
System.out.println("律师取证");
person.doCourt();
}
}

//被代理的类
class Person implements Court{
private String name;

public Person(String name) {
this.name = name;
}

@Override
public void doCourt() {
System.out.println(name+"说:我没杀人");
}
}

动态代理
动态代理可以针对于一些不特定的类或者一些不特定的方法进行代理,我们可以在程序运行时动态的变化代理的规则,代理类在程序运行时才创建的代理模式成为动态代理。这种情况下,代理类并不是在Java代码中定义好的,而是在程序运行时根据我们的在Java代码中的“指示”动态生成的
Proxy 动态代理 JDK动态代理 面向接口
cglib 动态代理 第三方动态代理 面向父类

张三吃饭的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.bitzh.test.testProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
* @Auther: oyy
* @Date: 2024/2/19 - 02 - 19 - 15:34
* @Description: com.bitzh.test.testProxy
* @version: 1.0
*/
public class Test1 {
public static void main(String[] args) {
final Dinner dinner=new Person("张三");
// 通过Porxy动态代理获得一个代理对象,在代理对象中,对某个方法进行增强
// ClassLoader loader,被代理的对象的类加载器
ClassLoader classLoader = dinner.getClass().getClassLoader();
// Class<?>[] interfaces,被代理对象所实现的所有接口
Class[] interaces= dinner.getClass().getInterfaces();
// InvocationHandler h,执行处理器对象,专门用于定义增强的规则
InvocationHandler handler = new InvocationHandler(){
// invoke 当我们让代理对象调用任何方法时,都会触发invoke方法的执行
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Object proxy, 代理对象
// Method method,被代理的方法
// Object[] args,被代理方法运行时的实参
Object res=null;
if(method.getName().equals("eat")){
System.out.println("饭前洗手");
// 让原有的eat的方法去运行
res =method.invoke(dinner, args);
System.out.println("饭后刷碗");
}else{
// 如果是其他方法,那么正常执行就可以了
res =method.invoke(dinner, args);
}
return res;
}
};
Dinner dinnerProxy =(Dinner) Proxy.newProxyInstance(classLoader,interaces,handler);
dinnerProxy.eat("包子");
//dinnerProxy.drink();
}
}
interface Dinner{
void eat(String foodName);
void drink();
}
class Person implements Dinner{
private String name;
public Person(String name) {
this.name = name;
}
@Override
public void eat(String foodName) {
System.out.println(name+"正在吃"+foodName);
}
@Override
public void drink( ) {
System.out.println(name+"正在喝茶");
}
}
class Student implements Dinner{
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void eat(String foodName) {
System.out.println(name+"正在食堂吃"+foodName);
}
@Override
public void drink( ) {
System.out.println(name+"正在喝可乐");
}
}

使用代理技术 获得代理对象 代替张三 增强打官司的方法

总结

1 在不修改原有代码的 或者没有办法修改原有代码的情况下 增强对象功能 使用代理对象 代替原来的对象去完成功能
进而达到拓展功能的目的
2 JDK Proxy 动态代理面向接口的动态代理 一定要有接口和实现类的存在 代理对象增强的是实现类 在实现接口的方法重写的方法
生成的代理对象只能转换成 接口的不能转换成 被代理类
代理对象只能增强接口中定义的方法 实现类中其他和接口无关的方法是无法增强的
代理对象只能读取到接口中方法上的注解 不能读取到实现类方法上的注解

Spring_CGLIB动态代理

proxy 动态代理
面向接口
1必须有接口和实现类
2增强接口中定义的方法
3只能读取接口中方法的上注解

cglib动态代理模式
面向父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Test1 {
@Test
public void testCglib(){
Person person =new Person();
// 获取一个Person的代理对象
// 1 获得一个Enhancer对象
Enhancer enhancer=new Enhancer();
// 2 设置父类字节码
enhancer.setSuperclass(person.getClass());
// 3 获取MethodIntercepter对象 用于定义增强规则
MethodInterceptor methodInterceptor=new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
/*Object o, 生成之后的代理对象 personProxy
Method method, 父类中原本要执行的方法 Person>>> eat()
Object[] objects, 方法在调用时传入的实参数组
MethodProxy methodProxy 子类中重写父类的方法 personProxy >>> eat()
*/
Object res =null;
if(method.getName().equals("eat")){
// 如果是eat方法 则增强并运行
System.out.println("饭前洗手");
res=methodProxy.invokeSuper(o,objects);
System.out.println("饭后刷碗");
}else{
// 如果是其他方法 不增强运行
res=methodProxy.invokeSuper(o,objects); // 子类对象方法在执行,默认会调用父类对应被重写的方法
}
return res;
}
};
// 4 设置methodInterceptor
enhancer.setCallback(methodInterceptor);
// 5 获得代理对象
Person personProxy = (Person)enhancer.create();
// 6 使用代理对象完成功能
personProxy.eat("包子");
}
}
class Person {
public Person( ) {
}
public void eat(String foodName) {
System.out.println("张三正在吃"+foodName);
}
}

AOP面向切面的编程

AOP切面编程一般可以帮助我们在不修改现有代码的情况下,对程序的功能进行拓展,往往用于实现 日志处理,权限控制,性能检测,事务控制等
AOP实现的原理就是动态代理,在有接口的情况下,使用JDK动态代理,在没有接口的情况下使用cglib动态代理

image-20240219201933876

为Dao层所有的add方法添加一个性能记录功能

1 连接点 Joint point:
类里面那些可以被增强的方法,这些方法称之为连接点
表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point

2 切入点 Pointcut:
实际被增强的方法,称之为切入点
表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方

3 通知 Advice:
实际增强的逻辑部分称为通知 (增加的功能)
Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
通知类型:

1 前置通知 (放在前面的通知)

2 后置通知 (无论异常都执行)

3 环绕通知 (前后都有)

4 异常通知 (出了异常才执行)

5 最终通知 (不出异常才通知)

image-20240219203123559

4 目标对象 Target:被增强功能的对象(被代理的对象)
织入 Advice 的目标对象

5 切面Aspect: 表现为功能相关的一些advice方法放在一起声明成的一个Java类
Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。

6 织入 Weaving:
创建代理对象并实现功能增强的声明并运行过程
将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

AOP注解方式实现——掌握

实现的两种方式
1 基于注解方式实现 (熟练)
2 基于XML配置方式 (了解)

1导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<dependencies>
<!--spring核心容器包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.5</version>
</dependency>
<!--spring切面包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.5</version>
</dependency>
<!--织入包 spring-aspects 已经导入该包,这里可以不导入-->
<!--<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>-->
<!--aop联盟包-->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!--Apache Commons日志包-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!--德鲁伊连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!--Junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!--lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>

image-20240220000859234

开启注解扫描和AOP切面编程自动生成代理对象配置,

然后再配置文件中配置包扫描

准备接口
UserDao和EmpDao

1
2
3
public interface EmpDao {
int addEmp(Integer empno,String ename,String job);
}
1
2
3
public interface UserDao {
int addUser(Integer userid,String username);
}

接口实现类

1
2
3
4
5
6
7
8
@Repository
public class UserDaoImpl implements UserDao {
public int addUser(Integer userid,String username){
System.out.println("userdao add ... ...");
//int i =1/0;
return 1;
}
}
1
2
3
4
5
6
7
@Repository
public class EmpDaoImpl implements EmpDao {
public int addEmp(Integer empno,String ename,String job){
System.out.println("empDao add ... ...");
return 1;
}
}

准备切面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
@Component
@Aspect
public class DaoAspect {
//定义公共切点
@Pointcut("execution(* com.msb.dao.*.add*(..))")
public void addPointCut(){}
/*
* 前置通知: 切点方法执行之前先执行的功能
* 参数列表可以用JoinPoint接收切点对象
* 可以获取方法执行的参数
* */
@Before("addPointCut()")
public void methodBefore(JoinPoint joinPoint){
System.out.println("Before invoked");
}
/*
* 后置通知:方法执行之后要增强的功能
* 无论切点方法是否出现异常都会执行的方法
* 参数列表可以用JoinPoint接收切点对象
* */
@After("addPointCut()")
public void methodAfter(JoinPoint joinPoint){
System.out.println("After invoked");
}
/*
* 返回通知:切点方法正常运行结束后增强的功能
* 如果方法运行过程中出现异常,则该功能不运行
* 参数列表可以用 JoinPoint joinPoint接收切点对象
* 可以用Object res接收方法返回值,需要用returning指定返回值名称
* */
@AfterReturning( value = "addPointCut()",returning = "res")
public void methodAfterReturning(JoinPoint joinPoint,Object res){
System.out.println("AfterReturning invoked");
}
/*
* 异常通知:切点方法出现异常时运行的增强功能
* 如果方法运行没有出现异常,则该功能不运行
* 参数列表可以用Exception ex接收异常对象 需要通过throwing指定异常名称
* */
@AfterThrowing( value = "addPointCut()",throwing = "ex")
public void methodAfterThrowing(Exception ex){
System.out.println("AfterThrowing invoked");
}
/*环绕通知:在切点方法之前和之后都进行功能的增强
* 需要在通知中定义方法执行的位置,并在执行位置之前和之后自定义增强的功能
* 方法列表可以通过ProceedingJoinPoint获取执行的切点
* 通过proceedingJoinPoint.proceed()方法控制切点方法的执行位置
* proceedingJoinPoint.proceed()方法会将切点方法的返回值获取到,并交给我们,可以做后续处理
* 我们在环绕通知的最后需要将切点方法的返回值继续向上返回,否则切点方法在执行时接收不到返回值
* */
@Around("addPointCut()")
public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("aroundA invoked");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("aroundB invoked");
return proceed;
}
}

AOPXML方式实现

1、创建两个类,增强类和被增强类,创建方法

见之前的代码

2、在spring配置文件中创建两个类对象

1
2
3
<!--创建对象--> 
<bean id="userDao" class="com.com.msb.UserDaoImpl"></bean>
<bean id="daoAspect" class="com.com.aspect.DaoAspect"></bean>

3、在spring配置文件中配置切入点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--配置aop增强-->
<aop:config>
<!--切入点-->
<aop:pointcut id="pointCutAdd" expression="execution(* com.msb.dao.UserDao.add*(..))"/>
<!--配置切面-->
<aop:aspect ref="daoAspect">
<!--增强作用在具体的方法上-->
<aop:before method="methodBefore" pointcut-ref="pointCutAdd"/>
<aop:after method="methodAfter" pointcut-ref="pointCutAdd"/>
<aop:around method="methodAround" pointcut-ref="pointCutAdd"/>
<aop:after-returning method="methodAfterReturning" pointcut-ref="pointCutAdd" returning="res"/>
<aop:after-throwing method="methodAfterThrowing" pointcut-ref="pointCutAdd" throwing="ex"/>
</aop:aspect>
</aop:config>

Spring_JdbcTemplate概述

JdbcTemplate概述

JdbcTemplate是spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据的JdbcTemplate和,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。