理解Spring的AOP和Ioc/DI就这么简单

2023-11-18

一、什么叫Ioc、DI

Ioc:Inversion of Control —— 控制反转

DI:Dependency Injection —— 依赖注入

其实这两个概念本质上是没有区别的,那我们先来看看什么叫做Ioc?

假设这么一个场景:

在A类中调用B类的方法,那么我们就称 A依赖B,B为被依赖(对象),相信这点大家能够理解。

传统做法:

(1)直接在A(方法)中new出B类对象,然后调用B类方法 —— 硬编码耦合;

(2)通过简单工厂获取B类对象,然后调用B类的方法 —— 摆脱了与B的耦合,却又与工厂产生了耦合;

以上两种做法,都是在A中主动去new或调用简单工厂的方法产生B的对象,注意,关键字是“主动”

Spring框架

在spring中,B的实例对象被看成Bean对象,这个Bean对象由spring容器进行创建和管理,当我们在配置文件中配置<Bean>下的<property>子元素时,spring就会自动执行在A中对B对象的setter方法(即A中需要有对B对象的setter方法),如此一来,A获取B的实例对象就不是由自己主动去获取,而是被动接受spring给它设值,那么,这个主动变为被动,就可以理解为“控制反转”。

而另一种说法,从spring容器的角度上看,它负责把A的依赖对象B(B是被依赖对象)注入给了A,所以我们可以理解为“依赖注入

(spring中依赖注入的方式可不止调用setter方法一种,还有通过调用构造器的方式来实现,这里只是为了说明Ioc和DI,就不再累赘了)

 

<bean id="userAction" class="com.router.action.UserAction" scope="prototype">
		<!-- 注入Service -->
		<property name="userService" ref="userService" />
</bean>
<bean id="userService" class="com.router.serviceimpl.UserServiceImpl"></bean>

 

 

 

// 注入service
private UserService userService;
		
public void setUserService(UserService userService) {
		this.userService = userService;
	}

 

        以上代码,spring通过<property>元素(当然内部有它的实现方法)自动调用userService的setter方法,userAction中就获得了userService对象了。

 

        那么我们来分析一下,通过使用spring的依赖注入功能,是怎么达到解耦了呢?

        首先,我们的编程是面向接口编程(在实际开发开发中也是需要我们面向接口编程的),上面代码中的UserService就是一个接口,UserServiceImpl就是其中的一个实现类。那么当我们通过直接new的方式创建对象,则是UserService userService = new UserServiceImpl();,这句话是写在源代码里头中的,当实现类UserServiceImpl内部放生改变时,或者是不再想使用这个类,而是另一个新的实现类(比如说是UserServiceImpl2),那么我们就得在源代码中将UserService userService = new UserServiceImpl2();,而以后或许需求还会变,那么就得不停地修改源代码。

而使用spring框架后,只需在配置文件中的<Bean>配置所需要的相应接口的实现方法,然后通过setter方法注入进去即可,setter方法不管以后变不变实现类,都不需要修改,要改的只是在spring的配置文件中改掉实现类的全路径即可,如此看来,这确实是达到了解耦!

 

二、什么是AOP?

AOP —— Asepct-Orentid-Programming,面向切面编程

        那么我们该怎么理解AOP呢?我们可以通过OOP —— 面向对象编程来进行比较分析

        相信大家对于OOP的理解不难,就以人(people)来说,我们就可以把它看做一类对象,people有身高、体重、年龄等属性,也有跑步、吃饭、睡觉、娱乐等行为,把这些属于people的属性和行为封装在people类中,然后以统一调用的方式(创建一个people类实例对象,通过这个对象实例来调用这些属性和行为)就叫做OOP思想,OOP给我们的感觉就是结构清晰,高内聚,易维护等。这些属于一种从上到下的关系(即这个类封装的所有属性和方法都是属于people的),而我们的AOP思想就是一种从左到右的关系,以切入的方式将业务逻辑功能应用到每一层结构中(可以理解为类方法,类方法也是一种对象的行为实现)。举个例子,people也可以分为少年、青年、中年、和老年,这几类人除了拥有自己的属性和行为外,生活中,或许还需要去医院看病,但是医院看病这一个逻辑业务功能并不是属于哪一类,而是谁生病了,才需要到医院看病,而基于面向对象编程的思想,我们是不可能把这一个业务逻辑行为加到每一个类中的,这不符合OOP思想,而这个就是AOP所做也可以做到事情了,AOP就是把医院看病这一个业务逻辑功能抽取出来,然后动态把这个功能注入到需要的方法(或行为)中,以后,不管是谁需要看病,就到医院这个第三方机构看病(AOP就是相当于把这个第三方机构独立出来),这样从业务逻辑角度上,AOP达到了更近一步的的解耦,所以我们也称AOP是对OOP的完善和增强。

而我们的编程中,常用到AOP的就是安全校验、日志操作、事务操作等,接下来一张图认识AOP思想

        AOP就是使用上图所示的“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

AOP的相关术语

 

1.通知(Advice)

 

  就是你想要的功能,也就是上面说的 安全,事务,日志等。你给先定义好,然后在想用的地方用一下。

2.连接点(JoinPoint)

  这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点,其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。

3.切入点(Pointcut)

  上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。

4.切面(Aspect)

  切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。

5.引入(introduction)

  允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗

6.目标(target)

  引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。

7.代理(proxy)

  怎么实现整套aop机制的,都是通过代理,也就是说,AOP的实现原理是基于动态代理实现的。

8.织入(weaving)

  把切面应用到目标对象来创建新的代理对象的过程。

 

        在此笔者建议,如果不是很了解java动态代理的代理,可以先去熟悉下动态代理,这样能更好的理解AOP的实现原理

        可看笔者另一篇文章 静态代理与动态代理

 

AOP应用实例1 —— 动态代理的形式模拟AOP

(以下应用实例都是基于接口编程,笔者就不示出接口了)

 

public class UserAImpl implements UserA{

	@Override
	public void save() {
		System.out.println("正在保存A类用户……");
		
	}

	@Override
	public void update() {
		System.out.println("正在更新A类用户……");
		
	}

}

 

public class UserBImpl implements UserB {

	@Override
	public void save() {
		System.out.println("正在保存B类用户……");

	}

	@Override
	public void update() {
		System.out.println("正在更新B类用户……");

	}

}

 

 

AOP业务增强(通知)类

public class DataValidateImpl implements DataValidate {

	@Override
	public void validate() {
		System.out.println("正在进行数据校验……");
		System.out.println("数据校验完毕!");
		
	}

	@Override
	public void advice() {
		System.out.println("操作成功");
		
	}

}

 

 

代理工厂类

public class ProxyFactoryImpl implements ProxyFactory {

	//单例模式创建工厂
	private static ProxyFactoryImpl proxyFactorySingleton;
	
	private ProxyFactoryImpl() {}
	
	public static ProxyFactoryImpl getProxyFactorySingleton() {
		if (proxyFactorySingleton == null) {
			proxyFactorySingleton = new ProxyFactoryImpl();
		}
		return proxyFactorySingleton;
	}
	
	@Override
	public Object newProxyInstance(Object obj, InvocationHandler handler) {
		
		return Proxy.newProxyInstance(obj.getClass().getClassLoader(), 
				obj.getClass().getInterfaces(), 
				handler);
	}

}

 

 

测试类

public class AOPTest {

	public static void main(String[] args) {
		ProxyFactoryImpl proxyFactory = ProxyFactoryImpl.getProxyFactorySingleton();
		
		//操作A类用户数据
		UserA ua = (UserA) proxyFactory.newProxyInstance(new UserAImpl(), 
				new UserAHandler(new UserAImpl(), new DataValidateImpl()));
		//得到的是代理对象
		System.out.println(ua.getClass().getName());
		
		ua.save();
		ua.update();
		
		System.out.println("********************");
		
		//操作B类用户数据
		UserB ub = (UserB) proxyFactory.newProxyInstance(new UserBImpl(), 
				new UserBHandler(new UserBImpl(), new DataValidateImpl()));
		
		//得到的是代理对象
		System.out.println(ub.getClass().getName());
		
		ub.save();
		ub.update();
		
		//如果不用代理来调用,就是这样的结果
		System.out.println("======================");
		UserB ub2 = new UserBImpl();
		ub2.save();
		ub2.update();
	}
}

 

 

运行结果:

 

 

AOP应用实例 —— spring注解方式使用AOP

 

User类

 

public class User {

	public void addUser(){
		System.out.println("添加成功!");
	}
}

 

增强类

@Aspect
public class MyUser {

	@Before(value = "execution(* com.xian.entity.User.*(..))")
	public void before() {
		System.out.println("before……");
	}
	
	@After(value = "execution(* com.xian.entity.User.*(..))")
	public void after() {
		System.out.println("after……");
	}
}

配置文件

 

 

<bean id="user" class="com.xian.entity.User"></bean>
<bean id="myUser" class="com.xian.entity.MyUser"></bean>
	
	<!-- 配置文件方式使用AOP -->
	<!-- <aop:config>
		配置切入点
		<aop:pointcut expression="execution(* com.xian.entity.User.*(..))" id="userPC1"/>
		
		配置切面
			将增强使用于方法上
		
		 <aop:aspect ref="myUser">
		 	配置增强的类型
		 	<aop:before method="before" pointcut-ref="userPC1"/>
		 	<aop:after method="after" pointcut-ref="userPC1"/>
		 </aop:aspect>
	</aop:config> -->
	
	<!-- 注解方式使用AOP -->
	<!-- 开启AOP代理 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>


测试类

 

 

public class UserTest {
	//private static Logger userLog = Logger.getLogger(User.class);
	@Test
	public void testUser(){
		
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		User user = (User) context.getBean("user");//通过bean容器获得 的user其实只是一个代理对象
		User user2 = new User();
		System.out.println(user == user2);
		MyUser mu = (MyUser) context.getBean("myUser");
		//userLog.info("开始调用User的Add方法……");
		user.addUser();//把这里变成user2来调用add,就不会执行切面的增强逻辑功能了
		
		//userLog.info("正常结束……");
	}
}


运行结果:

 

 

希望对读者有帮助!转载请注明出处!

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

理解Spring的AOP和Ioc/DI就这么简单 的相关文章

  • 通过SOCKS代理连接Kafka

    我有一个在 AWS 上运行的 Kafka 集群 我想用标准连接到集群卡夫卡控制台消费者从我的应用程序服务器 应用程序服务器可以通过 SOCKS 代理访问互联网 无需身份验证 如何告诉 Kafka 客户端通过代理进行连接 我尝试了很多事情 包
  • 如何在 Firebase 远程配置中从 JSON 获取值

    我是 Android 应用开发和 Firebase 的新手 我想知道如何获取存储在 Firebase 远程配置中的 JSONArray 文件中的值 String 和 Int 我使用 Firebase Remote Config 的最终目标是
  • 使用 Ant 将非代码资源添加到 jar 文件

    我正在将 java 应用程序打包成 jar 文件 我正在使用 ant 和 eclipse 我实际上需要在 jar 中直接在根文件夹下包含几个单独的非代码文件 xml 和 txt 文件 而不是与代码位于同一位置 我正在尝试使用includes
  • org.hibernate.QueryException:无法解析属性:文件名

    我正在使用休眠Criteria从列中获取值filename在我的桌子上contaque recording log 但是当我得到结果时 它抛出异常 org hibernate QueryException 无法解析属性 文件名 com co
  • Kotlin 未解决的参考:CLI 上 gradle 的 println

    放一个printlnkotlin 函数返回之前的语句会崩溃 堆栈跟踪 thufir dur NetBeansProjects kotlin thufir dur NetBeansProjects kotlin gradle clean bu
  • 需要使用 joda 进行灵活的日期时间转换

    我想使用 joda 解析电子邮件中的日期时间字符串 不幸的是我得到了各种不同的格式 例如 Wed 19 Jan 2011 12 52 31 0600 Wed 19 Jan 2011 10 15 34 0800 PST Wed 19 Jan
  • Jackson XML ArrayList 输出具有两个包装器元素

    我在 Jackson 生成的 XML 输出中得到了两个包装器元素 我只想拥有一个 我有一个 Java bean Entity Table name CITIES JacksonXmlRootElement localName City pu
  • 如何使用 Hibernate (EntityManager) 或 JPA 调用 Oracle 函数或过程

    我有一个返回 sys refcursor 的 Oracle 函数 当我使用 Hibernate 调用该函数时 出现以下异常 Hibernate call my function org hibernate exception Generic
  • 如何在 Spring 属性中进行算术运算?

  • 内部存储的安全性如何?

    我需要的 对于 Android 我需要永久保存数据 但也能够编辑 并且显然是读取 它 用户不应访问此数据 它可以包含诸如高分之类的内容 用户不得对其进行编辑 我的问题 我会 并且已经 使用过Internal Storage 但我不确定它实际
  • GWT 2.3 开发模式 - 托管模式 JSP 编译似乎不使用 java 1.5 兼容性

    无法编译 JSP 类 生成的 servlet 错误 DefaultMessage 上次更新 0 日期 中 0 时间 HH mm ss z 语法 错误 注释仅在源级别为 1 5 时可用 在尝试以开发模式在 Web 浏览器中打开我的 gwt 模
  • Java实现累加器类,提供Collector

    A Collector具有三种通用类型 public interface Collector
  • 流中的非终结符 forEach() ?

    有时 在处理 Java Stream 时 我发现自己需要一个非终端 forEach 来触发副作用但不终止处理 我怀疑我可以用 map item gt f item 之类的方法来做到这一点 其中方法 f 执行副作用并将项目返回到流中 但这似乎
  • 如何在 Java 中创建接受多个值的单个注释

    我有一个名为 Retention RetentionPolicy SOURCE Target ElementType METHOD public interface JIRA The Key Bug number JIRA referenc
  • JMenu 中的文本居中

    好吧 我一直在网上寻找有关此问题的帮助 但我尝试的任何方法似乎都不起作用 我想让所有菜单文本都集中在菜单按钮上 当我使用setHorizontalTextPosition JMenu CENTER 没有变化 事实上 无论我使用什么常量 菜单
  • 是否可以使用 Java Guava 将函数应用于集合?

    我想使用 Guava 将函数应用于集合 地图等 基本上 我需要调整 a 的行和列的大小Table分别使所有行和列的大小相同 执行如下操作 Table
  • Android:无法发送http post

    我一直在绞尽脑汁试图弄清楚如何在 Android 中发送 post 方法 这就是我的代码的样子 public class HomeActivity extends Activity implements OnClickListener pr
  • 泛型、数组和 ClassCastException

    我想这里一定发生了一些我不知道的微妙事情 考虑以下 public class Foo
  • 在浏览器刷新中刷新检票面板

    我正在开发一个付费角色系统 一旦用户刷新浏览器 我就需要刷新该页面中可用的统计信息 统计信息应该从数据库中获取并显示 但现在它不能正常工作 因为在页面刷新中 java代码不会被调用 而是使用以前的数据加载缓存的页面 我尝试添加以下代码来修复
  • 洪水填充优化:尝试使用队列

    我正在尝试创建一种填充方法 该方法采用用户指定的初始坐标 检查字符 然后根据需要更改它 这样做之后 它会检查相邻的方块并重复该过程 经过一番研究 我遇到了洪水填充算法并尝试了该算法 它可以工作 但无法满足我对 250 x 250 个字符的数

随机推荐