AOP简介
在软件行业,AOP为Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理 功能的统一维护的一种技术。
AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
Why AOP ?
AOP解决的是 非业务代码抽取的问题。
-
对重复的非业务代码进行抽取,在运行的时候往业务方法上动态植入“切面类代码”。
-
采用横向抽取机制,取代了传统纵向继承体系重复性代码。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率。
Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码。
AspectJ 是一个基于Java语言的AOP框架,Spring2.0 开始,Spring AOP 引入对 Aspect 的支持,AspectJ 扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入 。
AOP应用场景
经典应用:事务管理、性能监视、安全检查、缓存、日志等。
在业务系统里除了要实现业务功能之外,还要实现如权限拦截、性能监控、事务管理等非业务功能。
通常的作法是非业务的代码穿插在业务代码中,从而导致了业务组件与非业务组件的耦合。
aop面向切面编程,就是将这些分散在各个业务逻辑代码中的非业务代码,通过横向切割的方式抽取到一个独立的模块中,从而实现业务组件与非业务组件的解耦。
高内聚:单一责任原则
但是日志一般不用AOP写,日志一般都是写死的,一个方法里,不同位置处,有可能用的日志不同,它需要判断语句,另外日志是很常用的,一般占整体代码的4%。
AOP术语
Target(目标类):需要被代理的类
- 例如: 上面的 UserSerrvice
Joinpoint(连接点):官方说法(指那些被拦截到的点),即那些可能被连接的目标类的方法
- 例如: UserSerrvice的所有方法
PointCut(切入点):官方说法(我们要对哪些Joinpoint进行拦截),即已经被增强的连接点
- 例如:addUser( )
Advice(通知/增强):增强代码
- 例如:before、after
Weaving(织入):指把增强advice应用到目标对象target来创建新的代理对象的过程
Proxy(代理):一个类被AOP增强后,就产生一个结果代理类
Aspect(切面):是切入点PointCut和通知advice的集合
- 一条线是一个特殊的面
AOP代码示例
下面通过具体的例子来讲解一下AOP的实现…
例如有下面这个方法:
//保存一个用户
public void add(User user) {
Session session = null;
Transaction trans = null;
try{
session = HibernateSessionFactoryUtils.getSession(); //关注点代码
trans = session.beginTransaction(); //关注点代码
session.save(user); //『核心业务代码』
trans.commit; //关注点代码
}catch (Exception e){
e.printStackTrace();
if(trans != null){
trans.rollback(); //关注点代码
}
}finally{
HibernateSessionFactoryUtils.closeSession(session); //关注点代码
}
}
上面的代码其实最核心的就一行代码:保存 user 对象到数据库中
session.save(user);
我们的数据库表肯定不止 user 一张表,对数据库的操作除了 add() 方法还有 删、改、查等。
所以我们可以想象到:对数据库的每次操作,都要写「开启事务」和「关闭事务」这种代码。
这种代码对我们来说是重复的,于是我们会想把这种代码给「抽取」出来。
如果我们单纯用 OOP(面向对象)的思想去把非业务代码封装起来,最终我们的效果可能是这样的:
public class UserDao() {
AOP aop;
public void save() {
aop.begin();
System.out.println("DB:保存用户");
aop.close();
}
}
即使这样看起来代码已经很少了,但我们细想会发现,这样做只是对原来的非业务代码进行了封装,但是最终 update()/delete() 方法同样也会有aop.begin();
这样的重复的代码。
而我们的目的就是彻底消除这样的代码(明面上看不到),但是仍然可以实现原来的功能,那么就要用到「动态代理」,通过动态代理,将非业务代码写在要「增强」的逻辑上。
public class ProxyFactory {
//维护目标对象
private static Object target;
//维护关键点代码的类
private static AOP aop;
public static Object getProxyInstance(Object target_, AOP aop_) {
//目标对象和关键点代码的类都是通过外界传递进来
target = target_;
aop = aop_;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
aop.begin();
Object returnValue = method.invoke(target, args);
aop.close();
return returnValue;
}
}
);
}
}
完了以后,我们就可以通过「代理对象」去调用方法,最终屏蔽掉「重复代码」
public class App {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("dao/applicationContext.xml");
IUser iUser = (IUser)ac.getBean("proxy");
iUser.save();
}
}
上面是 DIY 的代理来实现对「非业务代码的」的抽取,类似这样的场景还有很多,比如权限控制、对参数进行校验等等。
Spring AOP让我们可以不用自己手动去写代理对象,达到将「非业务代码」的抽取的效果。
下面是使用Spring AOP实现的代码:
@Component
@Aspect //指定为切面类
public class AOP{
//里面的值为切入点表达式
@Before("execution(* dao.*.*(..))")
public void begin() {
System.out.println("开始事务");
}
@After("execution(* dao.*.*(..))")
public void close() {
System.out.println("关闭事务");
}
}
其中:
//接口(在dao包下)
public nterface IUser {
void save();
}
//实现类(在dao包下)
@Component
public class UserDao implements IUser {
@Override
public void save(){
System.out.println("DB:保存用户");
}
}
测试:
public class APP {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("dao/applicationContext.xml");
//这里得到的是代理对象
IUser iUser = (IUser)ac.getBean("userDao");
System.out.println(iUser.getClass());
iUser.save();
}
}