Spring之AOP
【什么是aop?】
AOP,Aspect-Oriented Programming 面向切面编程
【AOP的由来】
OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需 要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。
AOP技术利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为; 那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手 将这些剖开的切面复原,不留痕迹。
【知识铺垫】
Spring对AOP的支持,底层采用的是动态代理。所以我们要先来了解一下代理模式。
什么是代理模式?
Proxy Pattern,23种常用的面向对象软件的设计模式之一。
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的组成
- 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
- 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
为什么要用代理模式?
-
中介隔离
在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。 -
开闭原则,增加功能
代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。
代理模式分类
1. 静态代理模式
优点:符合开闭原则,可以实现功能的增加,并起到中介隔离的作用
缺点:动态拓展性不好,一个代理类只能代理一个实际组件
实现:
抽象角色 UserService.java
package com.neusoft.SpringAop.Service;
public interface UserService {
void add();
void delete();
void query();
void modify();
}
真实角色 UserImpl .java
package com.neusoft.SpringAop.Impl;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import com.neusoft.SpringAop.Service.UserService;
@Service
public class UserImpl implements UserService{
@Override
public void add() {
// int a = 2/0;
System.out.println("this is add method");
}
@Override
public void delete() {
System.out.println("this is delete method");
}
@Override
public void query() {
System.out.println("this is query method");
}
@Override
public void modify() {
System.out.println("this is modify method");
}
}
代理角色 UserProxyStatic.java
package com.neusoft.SpringAop.proxy;
import com.neusoft.SpringAop.Impl.UserImpl;
import com.neusoft.SpringAop.Service.UserService;
public class UserProxyStatic implements UserService{
private UserService us = new UserImpl();
@Override
public void add() {
System.out.println(System.currentTimeMillis());
us.add();
}
@Override
public void delete() {
System.out.println(System.currentTimeMillis());
us.delete();
}
@Override
public void query() {
System.out.println(System.currentTimeMillis());
us.query();
}
@Override
public void modify() {
System.out.println(System.currentTimeMillis());
us.modify();
}
}
测试类 Test1 .java
package com.neusoft.SpringAop.Test;
import com.neusoft.SpringAop.Service.UserService;
import com.neusoft.SpringAop.proxy.UserProxyStatic;
public class Test1 {
public static void main(String[] args) {
UserService us = new UserProxyStatic();
us.add();
}
}
2.动态代理模式
优点: 相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度
缺点: 无法摆脱仅支持interface代理的桎梏
实现:
抽象角色 UserService.java 如上
真实角色 UserImpl .java 如上
代理角色 UserProxyDynamic.java
package com.neusoft.SpringAop.proxy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class UserProxyDynamic implements InvocationHandler{
private Object target;
public UserProxyDynamic(Object object) {
this.target = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//功能添加区
System.out.println(System.currentTimeMillis());
//功能添加区
//反射,获取对应方法
Object result = method.invoke(target, args);
return result;
}
//获取代理对象
public <T> T getProxy(){
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
}
测试类 Test.java
package com.neusoft.SpringAop.Test;
import java.util.HashMap;
import java.util.Map;
import com.neusoft.SpringAop.Impl.UserImpl;
import com.neusoft.SpringAop.Service.UserService;
import com.neusoft.SpringAop.proxy.UserProxyDynamic;
import com.neusoft.SpringAop.proxy.UserProxyStatic;
public class Test {
public static void main(String[] args) {
UserService us = new UserProxyDynamic(new UserImpl()).getProxy();
Map<String, String> map = new UserProxyDynamic(new HashMap<String,String>()).getProxy();
us.add();
System.out.println(map.size());
}
}
【Spring中AOP】
Spring中AOP的使用,分两种形式:
- 声明式aop(依赖xml配置文件配置关系)
- 注解式aop
在这里我们只介绍声明式AOP,因为在xml配置文件中会使相关的配置显的简单明了,若采用注解式,将会让人变的头大。
实现:
抽象角色 UserService.java 如上
真实角色 UserImpl .java 如上
配置文件 app.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"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id = "userImpl" class = "com.neusoft.SpringAop.Impl.UserImpl"></bean>
<bean id = "timer" class = "com.neusoft.SpringAop.util.Timer"></bean>
<aop:config>
<!-- 要切入的对象 -->
<aop:aspect id = "test1" ref="timer">
<!-- 切面 -->
<aop:pointcut expression="execution(* com.neusoft.SpringAop.Service.*.*(..))" id="service"/>
<aop:after-throwing method="showTime" pointcut-ref="service"/>
</aop:aspect>
</aop:config>
</beans>
其中,
<aop:aspect id = “test1” ref=“timer”> 声明要切入的对象
<aop:pointcut 中 expression 的值表示切面,即被代理对象
上述代码中expression 值表示,任意返回值 com.neusoft.SpringAop.Service中任意类 任意方法 任意参数
<aop:after-throwing method=“showTime” pointcut-ref=“service”/>代表切入对象切入的位置,这里又分5种情况
- 前置通知 <aop:before method=""/>
- 后置通知 <aop:after method=""/>
- 环绕通知(前+后) <aop:around method=""/>
- 最终通知 <aop:after-returning method=""/>
- 异常通知 <aop:after-throwing method=""/>
下面伪代码,可表示其位置
try{
前置通知
目标方法
后置通知
}catch(){
异常通知
}finally{
最终通知
}
测试类:Test.java
package com.neusoft.SpringAop.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.neusoft.SpringAop.Impl.UserImpl;
import com.neusoft.SpringAop.Service.UserService;
import com.neusoft.SpringAop.proxy.UserProxyStatic;
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("app.xml");
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("app3.xml");
UserService us = (UserService) applicationContext.getBean("userImpl");
us.add();
}
}
补充:
个人认为,使用AOP时,在获取bean采用标注形式,aop配置采用xml声明配置,效果最佳。
【AOP应用场景】
- 日志
- spring提供事务管理器,利用aop将事务管理器切入到我们的代码逻辑上
- springmvc中的拦截器