Spring 框架之「初识AOP 面向切面编程」

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();
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_44471490/article/details/109190104
今日推荐