SpringAOP--AOP及SpringAOP

一,AOP简介

缘起

在项目中我们都会遇见使用AOP的场景。首先,我们来看一个非常简单的需求,从这个需求我们可以慢慢深入到为什么要使用AOP,以及AOP能做什么?

举例,我们现在要在已有的项目代码中增加程序运行时间打印方面的需求。原始代码如下:

public interface UserDao {
    public int save(String name, int age);

    public int delete(int id);

    public int update(int id, String name, int age);
}

public class UserDaoImpl implements UserDao {
    @Override
    public int save(String name, int age) {
        System.out.println("执行save逻辑.......");
        return 0;
    }

    @Override
    public int delete(int id) {
        System.out.println("执行delete逻辑.......");
        return 0;
    }

    @Override
    public int update(int id, String name, int age) {
        System.out.println("执行update逻辑.......");
        return 0;
    }
}

一般的无脑做法,可能会是这样:

public class UserDaoImpl implements UserDao {
    @Override
    public int save(String name, int age) {
        before();
        System.out.println("执行save逻辑.......");
        after();
        return 0;
    }

    @Override
    public int delete(int id) {
        before();
        System.out.println("执行delete逻辑.......");
        after();
        return 0;
    }

    @Override
    public int update(int id, String name, int age) {
        before();
        System.out.println("执行update逻辑.......");
        after();
        return 0;
    }
    
    private void before(){
        System.out.println("开始时间: " + System.currentTimeMillis());
    }

    private void after(){
        System.out.println("结束时间: " + System.currentTimeMillis());
    }
}

这样做不单修改了原来的代码,而且一旦需求产生变更(变更是常有的事情),比如:某天去掉时间打印。那么重复的工作要再做一遍(取消这些打印代码)。

使用代理

代理模式

代理是一种常用的设计模式,其目的就是为某个对象提供一个代理以控制对该对象的访问。代理类负责请求的预处理、过滤、将请求分派给委托类处理、以及委托类执行完请求后的后续处理。

// 出租接口
public interface Rent {
    public void rent();
}

// 房东
public class Landlord implements Rent {
    @Override
    public void rent() {
        System.out.println("房东出租......");
    }
}

// 中介
public class Mediator implements Rent {
    Rent landlord;

    @Override
    public void rent() {
        // 开张营业
        preRent();
        landlord.rent();
        postRent();
    }

    private void preRent() {
        System.out.println("跟房东谈价格并签约......");
        if (landlord == null)
            landlord = new Landlord();
    }

    private void postRent() {
        System.out.println("加点租金把房子租出去......");
    }
}

// 测试类
public class TestRent {
    public static void main(String[] args) {
        Mediator mediator = new Mediator();
        mediator.rent();
    }
}

静态代理

事先定义好接口的代理对象,并将接口的实例注入到代理对象中。这样就可以通过代理对象去调用真正的实现类。

public class StaticProxy implements UserDao {

    private UserDao userDao;

    public UserDaoStaticProxy(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public int save(String name, int age) {
        before();
        userDao.save(name, age);
        after();
        return 0;
    }

    @Override
    public int delete(int id) {
        before();
        userDao.delete(id);
        after();
        return 0;
    }

    @Override
    public int update(int id, String name, int age) {
        before();
        userDao.update(id, name, age);
        after();
        return 0;
    }

    private void after() {
        System.out.println("开始时间: " + System.currentTimeMillis());
    }

    private void after() {
        System.out.println("结束时间: " + System.currentTimeMillis());
    }
}

静态代理的缺点:需要为每个接口定义一个代理类,一旦项目中有很多类都需要代理类,哪就需要为每一个类维护一个代理类,这样很可能产生大量重复的代码,而且相当不利于后期维护或者需求变更。所以,我们可以使用动态代理来解决这些问题。

动态代理

为了解决静态代理的一些缺点,我们通过一个代理类完成全部的代理功能,这就需要使用到动态代理。

静态代理是在编译期确定被代理的对象,而动态代理类在运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件,因为代理类和委托类的关系是在运行期间确定的。

JDK动态代理

JDK动态代理类可以在运行时才指定它所实现的接口,这些被代理类实现的接口被称为动态接口(Dynamic Interface)。代理类的实例被称为代理实例(Proxy Instance)。每个代理实例都有一个对应的调用处理器(Invocation Handler)对象,该对象实现java.lang.reflect.InvocationHandler接口。

当用户通过代理接口调用代理实例的方法时,该方法调用会被分发到该实例对应的调用处理器(Invocation Handler)的invoke()方法,同时传入动态接口、代表被调用方法的java.lang.reflect.Method对象、以及代表方法调用参数的一个对象数组。调用处理器可以在invoke()方法中对接收到的方法调用进行相应的处理,该方法返回的结果应该是代理实例被调用得到的结果。

public class DynamicProxy implements InvocationHandler {
    private Object target;

    public Object newProxy(Object object) {
        this.target = object;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object ret = method.invoke(this.target, args);
        after();
        return ret;
    }

    private void before() {
        System.out.println("开始时间: " + System.currentTimeMillis());
    }

    private void after() {
        System.out.println("结束时间: " + System.currentTimeMillis());
    }

    public static void main(String[] args) {
        UserDao dao = (UserDao) new UserDaoDynamicProxy().newProxy(new UserDaoImpl());
        dao.delete(1);
        dao.save("zhangsan", 28);
    }
}

Cglib动态代理

public class CglibProxy implements MethodInterceptor {

    private Object target;

    public Object newProxy(Object object) {
        this.target = object;
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(this);
        enhancer.setSuperclass(target.getClass());
        return enhancer.create();
    }

    private void before() {
        System.out.println("开始时间: " + System.currentTimeMillis());
    }

    private void after() {
        System.out.println("结束时间: " + System.currentTimeMillis());
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy)
            throws Throwable {
        before();
        Object ret = method.invoke(target, args);
        after();
        return ret;
    }

    public static void main(String[] args) {
        UserDaoImpl dao = (UserDaoImpl) new CglibProxy().newProxy(new UserDaoImpl());
        dao.delete(1);
        dao.save("zhangsan", 28);
    }
}

AOP的概念

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程。它是施乐公司帕洛阿尔托研究中心 (Xerox PARC) 在上世纪 90 年代发明的一种编程范式,目的是使开发人员可以更好地将本不该彼此纠缠在一起的任务(例如数学运算和异常处理)分离开来。

在2004年左右由AOP联盟制定了一套规则通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP联盟主页:AOP Alliance

AOP联盟定义了两个非常重要的概念

  • Joinpoint:连接点。代码中被拦截的地方

  • Advice:通知。拦截后要做的动作

描述

Joinpoint

该接口表示通用的运行时连接点

Invocation

该接口表示程序中的调用

ConstructorInvocation

构造器连接点

MethodInvocation

方法调用连接点

Advice

通知的标记接口,代表将要执行的动作。具体实现可以是任何类型,例如拦截器。

Interceptor

标记接口,代表一个通用的拦截器

ConstructorInterceptor

构造器拦截器

MethodInterceptor

方法拦截器

现在有不少项目都提供了与AOP相关的技术,例如通用代理,拦截器或字节码转换器。

  • ASM:轻量级的字节码转换器

  • AspectJ:源码级别的织入

  • BCEL:字节码转换器

  • CGLIB:用于类加工处理和方法拦截

  • Javassist:具有高级API的字节码转换器

  • JBoss-AOP:拦截和基于元数据的AO框架

二,SpringAOP

 

Spring AOP是Spring框架的关键组件之一。虽然Spring IoC容器不依赖于AOP,这也意味着不需要AOP时可以不使用它,但AOP是对Spring IoC的补充,可以提供功能强大的中间件解决方案

什么是Spring AOP

Spring AOP是在AOP联盟定义的接口之上做了更多的扩展。Spring AOP是用纯Java实现的,不需要特殊的编译过程,不需要控制类加载器的层次结构,因此适合在Servlet容器或应用程序服务器中使用。

Spring AOP当前仅支持方法执行连接点,尽管可以在不破坏核心API的情况下添加对字段拦截的支持,但并未实现字段拦截。如果需要字段拦截的支持,请考虑使用诸如AspectJ之类的语言。Spring AOP的目的不是提供最完整的AOP实现,而是在AOP和Spring IoC之间提供紧密的集成,以帮助解决企业应用程序中的常见问题。

术语

描述

Aspect

一个模块具有一组提供横切需求的 APIs。例如,一个日志模块为了记录日志将被 AOP 方面调用。

Joinpoint

连接点。代码中被拦截的地方

Advice

通知或建议。拦截后要做的动作

Pointcut

切入点。Advice与切入点表达式关联,并在与该切入点匹配的Joinpoint处运行(例如,执行具有特定名称的方法)。默认情况下,Spring使用AspectJ切入点表达式。

Introduction

类型声明其他方法或字段。Spring AOP允许向任何Advice对象引入新接口(和相应的实现)。

Target Object

被一个或者多个切面通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。

Proxy Object

由AOP框架创建的对象,用于实现切面(执行建议方法等)。在Spring Framework中,AOP代理使用JDK动态代理或CGLIB代理。

Weaving

将切面与其他应用程序类型或对象链接以创建Advice对象。可以在编译时(例如,使用AspectJ编译器),加载时或运行时完成。

SpringAOP框架通知类型

通知类型

描述

前置通知

在一个方法执行之前,执行通知。

后置通知

在一个方法执行之后,不考虑其结果,执行通知。

返回后通知

在一个方法执行之后,只有在方法成功完成时,才能执行通知。

抛出异常后通知

在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。

环绕通知

在建议方法调用之前和之后,执行通知。

Spring AOP和成熟的框架(如AspectJ)是互补的,而不是竞争关系。Spring无缝地将Spring AOP和IoC与AspectJ集成在一起,从而能够在基于Spring的应用程序体系结构中满足AOP的使用。因此,在项目中你可以选择AspectJ和/或Spring AOP,也可以选择@AspectJ注释样式方法或Spring XML配置样式方法。

Spring AOP动态代理

Spring AOP默认使用JDK动态代理,这使得Spring AOP可以代理任何接口(或一组接口)。如果业务对象未实现接口,则默认情况下使用CGLIB。换而言之,Spring AOP是基于代理的。

使用示例

ProxyFactory

该类以编程方式使用,主要用于AOP代理的工厂,而不是通过在BeanFactory中进行声明式定义。此类提供一种简单的方式来获取AOP代理示例并在用户代码中进行配置。

public class SpringProxy {
    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new Landlord());
        factory.addInterface(Rent.class);
        factory.addAdvice(new RentAdvice());
        Rent rent = (Rent) factory.getProxy();
        rent.rent();
    }

    private static class RentAdvice
            implements MethodBeforeAdvice, AfterReturningAdvice {
        @Override
        public void before(Method method, Object[] args, Object target)
                throws Throwable {
            System.out.println("before......");
        }

        @Override
        public void afterReturning(Object value, Method method, Object[] args, Object target)
                throws Throwable {
            System.out.println("after......");
        }
    }
}

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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">

    <bean id="userService" class="xxx.xxx.service.UserServiceImpl" />
    <bean id="handler" class="xxx.xxx.aop.TimeHandler" />

    <aop:config>
        <aop:aspect id="time" ref="handler">
            <aop:pointcut id="addAllMethod" expression="execution(* ai.yunxi.service.UserService.*(..))"/>
            <aop:before method="before" pointcut-ref="addAllMethod"/>
            <aop:after method="after" pointcut-ref="addAllMethod"/>
        </aop:aspect>
    </aop:config>
</beans>
public class TimeHandler {
    public void after() {
        System.out.println("执行后.....");
    }

    public void before() {
        System.out.println("执行前.....");
    }
}

注解方式

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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">

    <context:component-scan base-package="xxx.xxx" />
    <aop:aspectj-autoproxy />
</beans>

方式一

// 方式一
@Component
@Aspect
public class TimeAspect {

    @After("execution(* xxx.xxx.service.UserService.*(..))")
    public void after() {
        System.out.println("执行后.....");
    }

    @Before("execution(* xxx.xxx.service.UserService.*(..))")
    public void before() {
        System.out.println("执行前.....");
    }

}

方式二

@Component
@Aspect
public class TimeAspect {

    @Pointcut("execution(* xxx.xxx.service.UserService.*(..))")
    public void printTime(){}

    @After("printTime()")
    public void after() {
        System.out.println("执行后.....");
    }

    @Before("printTime()")
    public void before() {
        System.out.println("执行前.....");
    }
}

测试类

XML方式

public class TestAop {
    public static void main(String[] args) {
        ApplicationContext ctx = new
                ClassPathXmlApplicationContext("spring-aop.xml");

        UserService userService = (UserService) ctx.getBean("userService");
        userService.delete(1);
    }
}

注解方式

@EnableAspectJAutoProxy
@Configuration
@ComponentScan
public class TestAop {
    public static void main(String[] args) {
        ApplicationContext ctx = new
                AnnotationConfigApplicationContext(TestAop.class);

        UserService userService = (UserService) ctx.getBean("userService");
        userService.delete(1);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值