当前位置:   article > 正文

Spring中的IOC和AOP

Spring中的IOC和AOP

Spring两大核心机制:IOC和AOP

一、IOC:控制反转

传统开发中,需要调用对象的时候,需要调用者手动来创建被调用者的实例,即对象是由调用者new出来的;
但在Spring框架中,创建对象的工作不再由调用者来完成,而是交给IOC容器来创建,再推送给调用者,用完再还回来,类似于缓冲池,整个流程完成反转,所以是控制反转。

  • Spring全家桶各个功能模块的基础,是创建对象的容器。
  • IOC的特点是解耦合:比如说A需要用到B,传统的开发,我们要直接创建B的实例,但是在Spring中,IOC这个容器会创建B的实例,然后把这个B注入到A。

IOC的使用:基于xml配置文件、基于注解(主要)


 注:以下内容,获取DataConfig的对象是最终目的

1.基于xml:借助与DataConfig类对应的xml文件-spring.xml,主要靠Bean

1.1 创建Maven项目,在pom.xml配置文件中导入spring依赖
  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-context</artifactId>
  4. <version>5.3.15</version>
  5. </dependency>
1.2 创建一个类-DataConfig

@Data是可以不用手动set、get

  1. package com.circle.ioc;
  2. import lombok.Data;
  3. @Data
  4. public class DataConfig {
  5. private String url;
  6. private String driverName;
  7. private String username;
  8. private String password;
  9. }
1.3 在resources路径下创建与类对应的配置文件,IOC容器通过读取配置文件,加载配置bean标签来创建对象
  1. <bean class="com.circle.ioc.DataConfig" id="config">
  2. <property name="driverName" value="Driver"></property>
  3. <property name="url" value="localhost:3306"></property>
  4. <property name="username" value="root"></property>
  5. <property name="password" value="root"></property>
  6. </bean>
1.3.1配置文件:
  • bean:通过配置bean标签来完成对象的管理
  • id:对象名
  • class:对象的模板类(所有交给IOC容器来管理的类必须要有无参构造函数,因为Spring底层是通过反射机制来创建对象,调用的是无参构造)
  • property:对象的成员变量通过property标签完成赋值
  • name:成员变量名
  • value:成员变量值(基本数据类型,String可以直接赋值,如果是其他引用类型不可以通过value赋值)
  • ref:把IOC中的另一个bean赋给当前成员变量(DI依赖注入),见下图(一个类中有另一个类的对象)此时用ref,不能用value,否则会抛出类型转换异常

1.3.2 IOC容器创建bean的两种方法: 
  • 无参构造函数(需要提供对应的set方法或使用lombok):property
  • 有参构造函数:constructor-arg
        type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型。
        index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始。
        name:用于指定给构造函数中指定名称的参数赋值。
  1. <bean id="stu1" class="com.zyh.pojo.Student">
  2. <constructor-arg name="id" value="1"> </constructor-arg>
  3. <constructor-arg name="name" value="李四"></constructor-arg>
  4. </bean>
  5. <bean id="stu1" class="com.zyh.pojo.Student">
  6. <constructor-arg index=0 value="1"> </constructor-arg>
  7. <constructor-arg index=1 value="李四"></constructor-arg>
  8. </bean>
 1.3.3 bean是根据scope来生成的,表示bean的作用域
  1. singleton,单例,表示通过Spring容器获取的对象是唯一的,是默认值。只要加载IOC容器,不管是否从IOC种取出bean,配置文件中的bean都会被创建,而且只会创建一个对象
  2. prototype,原型,表示通过Spring容器获取的对象是不同的。如果不从IOC中取出bean,则不创建对象,取一次bean,就会创建一个对象
    1. <bean id="user" class="com.zyh.pojo.User" scope="prototype">
    2. <property name="id" value="1"></property>
    3. <property name="name" value="张三"></property>
    4. </bean>
  3. request,请求,表示在异常HTTP请求内有效,一般用于web项目
  4. session,会话,表示在一个用户会话内有效,一般用于web项目
1.3.4 复杂类型的依赖注入:
  1. public class Student {
  2. private String name;
  3. private Person person;
  4. private String[] arr;
  5. private List<String> myList;
  6. private Map<String,String> myMap;
  7. private Set<String> mySet;
  8. private String wife;
  9. private Properties myPro;
  10. }
  1. <bean id="student" class="com.kang.pojo.Student">
  2. <!--普通值注入,value:具体属性值-->
  3. <property name="name" value="jerry"/>
  4. <!--Bean注入,ref:对象-->
  5. <property name="person" ref="person"/>
  6. <!--数组注入-->
  7. <property name="arr">
  8. <array>
  9. <value>AAA</value>
  10. <value>BBB</value>
  11. <value>CCC</value>
  12. </array>
  13. </property>
  14. <!--List注入-->
  15. <property name="myList">
  16. <list>
  17. <value>111</value>
  18. <value>222</value>
  19. <value>333</value>
  20. </list>
  21. </property>
  22. <!--Map注入-->
  23. <property name="myMap">
  24. <map>
  25. <entry key="aaa" value="aaaa"></entry>
  26. <entry key="bbb" value="bbbb"></entry>
  27. <entry key="ccc" value="cccc"></entry>
  28. </map>
  29. </property>
  30. <!--Set注入-->
  31. <property name="mySet">
  32. <set>
  33. <value>111</value>
  34. <value>222</value>
  35. <value>333</value>
  36. </set>
  37. </property>
  38. <!--null注入-->
  39. <property name="wife">
  40. <null/>
  41. </property>
  42. <!--Properties注入-->
  43. <property name="myPro">
  44. <props>
  45. <prop key="aaa">aaaa</prop>
  46. <prop key="bbb">bbbb</prop>
  47. <prop key="ccc">cccc</prop>
  48. </props>
  49. </property>
  50. </bean>
1.3.5 bean的属性如果包含特殊字符:使用CDATA

1.3.6 依赖注入之p、c命名空间
p命名空间是set注入的一种快捷实现方式,想要使用p命名空间注入,需要注意一下几点。
1. 实体类中必须有set方法;
2. 实体类中必须有无参构造器(默认存在);
3. 必须导入p命名空间注入方式依赖。(类对应的xml配置文件)
xmlns:p="http://www.springframework.org/schema/p"
4. 导入后即可使用:
<bean id="user" class="com.yd.pojo.User" p:age="18" p:name="老王"/>
c命名空间是构造器注入的一种快捷实现方式,想要使用c命名空间,需要注意一下几点。
1. 实体类中必须存在有参构造器;
2. 必须导入c命名空间注入方式依赖。
xmlns:c="http://www.springframework.org/schema/c"

3. 导入后即可使用:

<bean id="user2" class="com.yd.pojo.User" c:age="23" c:name="中王"/>
1.4 创建一个test文件,调用API,从IOC获取对象
  1. package com.circle.ioc;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. import javax.xml.crypto.Data;
  5. public class Test {
  6. public static void main(String[] args) {
  7. // // 不用ioc,所有对象开发者自己创建
  8. // DataConfig dataConfig = new DataConfig();
  9. // dataConfig.setDriverName("Driver");
  10. // dataConfig.setUrl("localhost::3306/dbname");
  11. // dataConfig.setUsername("root");
  12. // dataConfig.setPassword("root");
  13. // 使用ioc,对象不用开发者创建,交给spring框架完成
  14. // 两种方式:基于注解和XML(bean),主要是注解
  15. // 基于xml:把需要的对象在xml中进行配置,spring框架读取这个配置文件,根据配置文件的内容和反射机制来创建对象
  16. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); // ioc容器
  17. System.out.println(context.getBean("config")); // 从ioc中取出bean,通过id
  18. }
  19. }
1.4.1从IOC容器中取bean的方法:
  • 通过id取值:
 Student stu = (Student)applicationContext.getBean("stu");
  • 通过类型取值
 Student stu = applicationContext.getBean(Student.class);
   当IOC容器中存在两个以上Student Bean的时候就会抛出异常,因为此时没有唯一的bean
1.5 在XML方式中有三种方式来实例化bean
  • 反射模式
  • 工厂方法模式:静态工厂类不需要实例化,实例工厂类需要实例化
静态工厂类:
  1. 创建一个目标类对应的静态工厂类,写一个静态方法
  2. 在目标类对应的配置文件中
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id="car" class="com.zyh.factory.StaticCarFactory" factory-method="getCar">
  6. <constructor-arg name="num" value="1"></constructor-arg>
  7. </bean>
  8. </beans>

                factory-method 指向静态方法

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

  • 静态工厂方法创建对象,不需要实例化工厂对象,因为静态工厂的静态方法,不需要创建对象就可以调用了
实例工厂类:
  1. ​​​​​​​创建一个目标类对应的实例工厂类,写一个方法
  2. 在目标类对应的配置文件中
  1. <!-- 实例工厂类-->
  2. <bean id="instanceCarFactory" class="com.zyh.factory.InstanceCarFactory"></bean>
  3. <!-- 通过实例工厂获取Car-->
  4. <bean id="car1" factory-bean="instanceCarFactory" factory-method="getCar">
  5. <constructor-arg value="2"></constructor-arg>
  6. </bean>
  • 实例工厂方法创建对象,需要实例化工厂对象,因为方法是非静态的,就必须通过实例化对象才能调用,所以必须创建工厂对象,spring.xml需要配置两个bean,一个是工厂类bean,一个是方法Bean

  • FactoryBean模式

2. 基于注解:两种方式(配置类、扫包+注解)

2.1 配置类:用一个java文件(配置类)替代xml文件,将xml中的配置内容放在配置类中

在类前加@Configuration即为配置类,整个配置类可以类比为xml文件;在类中写一个方法,方法前加@Bean,返回一个对象存入IOC容器中,类比为bean。

  1. package com.circle.configuration;
  2. import com.circle.ioc.DataConfig;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. // 基于注解
  6. // 1. 配置类:用一个java类替代xml文件,把在xml中配置的内容放在配置类中
  7. // 2. 扫包 + 注解
  8. @Configuration // 配置类
  9. public class BeanConfiguration {
  10. // 整个BeanConfiguration类对应spring.xml文件
  11. // 方法返回的对象对应xml中的bean
  12. // 加载配置类时需调用这个方法,把返回的对象存入ioc
  13. // value或name取别名,但不能再使用原方法名了,id唯一
  14. @Bean(value = "config")
  15. public DataConfig dataConfig(){
  16. DataConfig dataConfig = new DataConfig();
  17. dataConfig.setDriverName("Driver");
  18. dataConfig.setUrl("localhost::3306/dbname");
  19. dataConfig.setUsername("root");
  20. dataConfig.setPassword("root");
  21. return dataConfig;
  22. }
  23. }
 2.2 测试
  1. package com.circle.ioc;
  2. import com.circle.configuration.BeanConfiguration;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. import org.springframework.context.support.ClassPathXmlApplicationContext;
  6. import javax.xml.crypto.Data;
  7. public class Test {
  8. public static void main(String[] args) {
  9. // 基于注解:配置类
  10. // 读取配置类初始化IOC容器
  11. ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfiguration.class);
  12. // System.out.println(context.getBean("dataConfig")); // 方法名作为id
  13. System.out.println(context.getBean("config")); // @Bean后加上value或name赋值,也可用value的值作为id,但取了value后,不能再用原方法名了,因为只有一个id
  14. }
  15. }
存在问题:new哪行代码BeanConfiguration.class只能传一个类,实际应用中会有很多配置类。 
解决:扫包(配置类的包)
  1. package com.circle.ioc;
  2. import com.circle.configuration.BeanConfiguration;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. import org.springframework.context.support.ClassPathXmlApplicationContext;
  6. import javax.xml.crypto.Data;
  7. public class Test {
  8. public static void main(String[] args) {
  9. // 扫包:配置类的包
  10. ApplicationContext context = new AnnotationConfigApplicationContext("com.circle.configuration");
  11. System.out.println(context.getBean("dataConfig"));
  12. }
  13. }

2.2 扫包+注解:直接将Bean的创建交给目标类-DataConfig,在目标类中添加注解@Component来创建
@Component注解告诉Spring框架,这个类需要创建对象注入到IOC容器
@value注解赋值
  1. package com.circle.ioc;
  2. import lombok.Data;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.stereotype.Component;
  5. @Data
  6. @Component
  7. public class DataConfig {
  8. @Value("localhost:3306")
  9. private String url;
  10. @Value("Drive")
  11. private String driverName;
  12. @Value("root")
  13. private String username;
  14. @Value("root")
  15. private String password;
  16. }
测试:
  1. package com.circle.ioc;
  2. import com.circle.configuration.BeanConfiguration;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. import org.springframework.context.support.ClassPathXmlApplicationContext;
  6. import javax.xml.crypto.Data;
  7. public class Test {
  8. public static void main(String[] args) {
  9. // 扫包:目标类的包
  10. ApplicationContext context = new AnnotationConfigApplicationContext("com.circle.ioc");
  11. System.out.println(context.getBean(DataConfig.class));
  12. }
  13. }

3. 依赖注入:两个类A和B,A中有B的对象,创建AB两个对象,自动将B装入A

A~GlobalConfig  B~DataConfig
1. @Autowired 自动装载注解,自动去ioc中找DataConfig类型的Bean,默认通过类型注入(ByType),找到了就拿过来赋值(DataConfig类也一定要有@Component注解)。
2. 如果想通过名字注入(ByName),使用@Qualifier注解
 1. 类型注入@Autowired
  1. package com.circle.ioc;
  2. import lombok.Data;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.beans.factory.annotation.Value;
  5. import org.springframework.stereotype.Component;
  6. import java.security.SecureRandom;
  7. @Data
  8. @Component
  9. public class GlobalConfig {
  10. @Value("8080")
  11. private String port;
  12. @Value("/")
  13. private String path;
  14. @Autowired
  15. private DataConfig dataConfig;
  16. }
  1. package com.circle.ioc;
  2. import lombok.Data;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.stereotype.Component;
  5. @Data
  6. @Component
  7. public class DataConfig {
  8. @Value("localhost:3306")
  9. private String url;
  10. @Value("Drive")
  11. private String driverName;
  12. @Value("root")
  13. private String username;
  14. @Value("root")
  15. private String password;
  16. }
测试:
  1. package com.circle.ioc;
  2. import com.circle.configuration.BeanConfiguration;
  3. import org.springframework.context.ApplicationContext;
  4. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  5. import org.springframework.context.support.ClassPathXmlApplicationContext;
  6. import javax.xml.crypto.Data;
  7. public class Test {
  8. public static void main(String[] args) {
  9. // 扫包:目标类的包
  10. ApplicationContext context = new AnnotationConfigApplicationContext("com.circle.ioc");
  11. System.out.println(context.getBean(DataConfig.class));
  12. }
  13. }

2.  使用名字注入:@Qualifier,注意DataConfig类中要取别名,与@Qualifier一致
  1. package com.circle.ioc;
  2. import lombok.Data;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.beans.factory.annotation.Qualifier;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.stereotype.Component;
  7. import java.security.SecureRandom;
  8. @Data
  9. @Component
  10. public class GlobalConfig {
  11. @Value("8080")
  12. private String port;
  13. @Value("/")
  14. private String path;
  15. // @Autowired
  16. // private DataConfig dataConfig; // 类型注入
  17. @Qualifier("config")
  18. private DataConfig dataConfig; // 名字注入
  19. }
  1. package com.circle.ioc;
  2. import lombok.Data;
  3. import org.springframework.beans.factory.annotation.Value;
  4. import org.springframework.stereotype.Component;
  5. @Data
  6. @Component(value = "config")
  7. public class DataConfig {
  8. @Value("localhost:3306")
  9. private String url;
  10. @Value("Drive")
  11. private String driverName;
  12. @Value("root")
  13. private String username;
  14. @Value("root")
  15. private String password;
  16. }

二、AOP:以IOC为基础,面向切面编程,是抽象的面向对象。

        多个方法在相同位置有相同的操作,切一刀,将切面抽象为一个对象,把相同的操作代码写进对象,对对象进行编程,底层使用动态代理机制。
        可以打印日志、事务、权限管理。

1. 原代码:代码维护性、复用性差

接口:

  1. package com.circle.aop;
  2. public interface cal {
  3. public int add(int num1, int num2);
  4. public int sub(int num1, int num2);
  5. public int mul(int num1, int num2);
  6. public int div(int num1, int num2);
  7. }

实现接口的类:

  1. package com.circle.aop;
  2. public class calculate implements cal{
  3. @Override
  4. public int add(int num1, int num2) {
  5. System.out.println("add方法的参数为:" + num1 + "、" + num2);
  6. int result = num1 + num2;
  7. System.out.println("add方法的结果为:" + result);
  8. return result;
  9. }
  10. @Override
  11. public int sub(int num1, int num2) {
  12. System.out.println("sub方法的参数为:" + num1 + "、" + num2);
  13. int result = num1 - num2;
  14. System.out.println("sub方法的结果为:" + result);
  15. return result;
  16. }
  17. @Override
  18. public int mul(int num1, int num2) {
  19. System.out.println("mul方法的参数为:" + num1 + "、" + num2);
  20. int result = num1 * num2;
  21. System.out.println("mul方法的结果为:" + result);
  22. return result;
  23. }
  24. @Override
  25. public int div(int num1, int num2) {
  26. System.out.println("div方法的参数为:" + num1 + "、" + num2);
  27. int result = num1 / num2;
  28. System.out.println("div方法的结果为:" + result);
  29. return result;
  30. }
  31. }

把参数和结果部分的日志代码抽离出业务代码,统一处理,使核心业务代码与非业务代码解耦合。

2. 使用AOP

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

2.1 在pom.xml中引入依赖

  1. <dependency>
  2. <groupId>org.springframework</groupId>
  3. <artifactId>spring-aop</artifactId>
  4. <version>5.3.15</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.aspectj</groupId>
  8. <artifactId>aspectjweaver</artifactId>
  9. <version>1.9.7</version>
  10. </dependency>

2.2 创建切面类

将切面对象注入IOC容器,目标类对象也注入IOC,目标类对象与切面对象整合形成一个代理对象(动态代理机制),操作代理对象

  1. package com.circle.aop;
  2. import org.aspectj.lang.JoinPoint;
  3. import org.aspectj.lang.annotation.AfterReturning;
  4. import org.aspectj.lang.annotation.Aspect;
  5. import org.aspectj.lang.annotation.Before;
  6. import org.springframework.stereotype.Component;
  7. import java.util.Arrays;
  8. // 切面类
  9. @Component
  10. @Aspect
  11. public class LoggerAspect {
  12. @Before("execution(public int com.circle.aop.calculate.*(..))")
  13. public void before(JoinPoint joinPoint){ // 切面对象与方法之间有个连接点对象Joinpoint,需要被横切的位置,即通知要插入业务代码的具体位置
  14. String name = joinPoint.getSignature().getName();
  15. System.out.println(name + "方法的参数是:" + Arrays.toString(joinPoint.getArgs()));
  16. }
  17. @AfterReturning(value = "execution(public int com.circle.aop.calculate.*(..))", returning = "result")
  18. public void afterReturning(JoinPoint joinPoint, Object result){
  19. String name = joinPoint.getSignature().getName();
  20. System.out.println(name + "方法的结果是:" + result);
  21. }
  22. }
  • @Before,表示方法的执行时机是在业务方法之前,execution表达式表示切入点是calculate中的所有方法
  • @AfterReturning,表示方法的执行时机是在业务方法返回结果后,execution表达式表示切入点是calculate类中的方法,returning是把业务方法的返回值和切面类方法的形参进行绑定
  • @AfterThrowing,表示方法的执行时机是在业务方法抛出异常后,execution表达式表示切入点是calculate类中的方法,throwing是把业务方法的异常和切面类方法的形参进行绑定
  • @After,表示方法的执行时机是在业务方法结束以后,execution表达式表示切入点是calculate类中的方法

2.3 实现类

  1. package com.circle.aop;
  2. import org.springframework.stereotype.Component;
  3. // 目标类
  4. @Component
  5. public class calculate implements cal{
  6. @Override
  7. public int add(int num1, int num2) {
  8. int result = num1 + num2;
  9. return result;
  10. }
  11. @Override
  12. public int sub(int num1, int num2) {
  13. int result = num1 - num2;
  14. return result;
  15. }
  16. @Override
  17. public int mul(int num1, int num2) {
  18. int result = num1 * num2;
  19. return result;
  20. }
  21. @Override
  22. public int div(int num1, int num2) {
  23. int result = num1 / num2;
  24. return result;
  25. }
  26. }

2.4 配置自动扫包,开启自动生成代理对象

在spring.xml里配

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. 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">
  7. <!-- 自动扫包-->
  8. <context:component-scan base-package="com.circle.aop"></context:component-scan>
  9. <!-- 开启自动生成代理对象-->
  10. <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  11. </beans>

2.5 测试

  1. package com.circle.aop;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class Test {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
  7. cal bean = context.getBean(cal.class); // 代理对象和目标类对象都是实现了接口的类
  8. bean.add(9, 8);
  9. bean.sub(9, 8);
  10. bean.mul(9, 8);
  11. bean.div(9, 8);
  12. }
  13. }

当有多个实现类,会报错,因为切面对象不知道与谁整合成代理对象,目标类对象和切面对象和代理对象是一对一对一的关系。

参考:http://t.csdnimg.cn/EOXh8 

        b站楠哥教你学java-1、什么是IoC和AOP_哔哩哔哩_bilibili

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/307873?site
推荐阅读
相关标签
  

闽ICP备14008679号