返回介绍

1.3 面向切面编程

发布于 2025-04-21 20:58:43 字数 13872 浏览 0 评论 0 收藏

AOP(Aspect Oriented Programming)与 OOP(Object Oriented Programming,面向对象编程)相辅相成。AOP 提供了与 OOP 不同的抽象软件结构的视角。在 OOP 中,我们以类(Class)作为基本单元,而在 AOP 中则以切面(Aspect)作为基本单元。AOP 是一种增强的编程方式,可以解耦一些非业务逻辑,如声明式事务管理、日志管理或异常处理等。从底层原理来讲,AOP 实际上是基于 Java 的代理模式实现的。本节首先介绍代理模式的定义,然后介绍 AOP 编程概念,最后使用 @Aspect 注解实现面向切面编程。

1.3.1 代理模式

代理模式是经典的设计模式之一,目的是为了扩展和增强类或接口。代理模式通常可以分为静态代理模式和动态代理模式。

1. 静态代理模式

静态代理模式的实现比较简单,主要的实现原理是:代理类与被代理类同时实现一个主题接口,代理类持有被代理类的引用。

(1)新建一个公共接口 UserInterface,代码如下:

//声明 UserInterface 接口
public interface UserInterface {
    //声明方法
        public abstract void getUser();
}

(2)定义真实执行类 RealUser 并实现公共接口 UserInterface,代码如下:

//声明 RealUser 类,实现 UserInterface 接口
public class RealUser implements UserInterface {
        @Override
        public void getUser() {
                //新建 UserService 对象
                System.out.println("真实用户角色执行!");
                UserService userService = new UserService();
                userService.setId(1);
                userService.setName("zhangsan");
                userService.getUser();
        }
}

(3)定义代理类 UserProxy 实现公共接口 UserInterface,并持有被代理类的实例。在执行时,调用被代理类(RealUser)实例的 getUser() 方法。代码如下:

//声明 UserProxy 代理类,并实现 UserInterface 接口
public class UserProxy implements UserInterface {
        private UserInterface userInterface;
        //构造方法传入 UserInterface 类型参数
        public UserProxy(UserInterface userInterface) {
                this.userInterface = userInterface;
        }
        //实现 getUser() 方法,在执行方法前后进行额外操作
        @Override
        public void getUser() {
                doBefore();
                userInterface.getUser();
                doAfter();
        }
        //真实方法执行前操作
        private void doBefore() {
                System.out.println("代理类开始执行");
        }
        //真实方法执行后操作
        private void doAfter() {
                System.out.println("代理类结束执行");
        }
}

(4)编写测试代码,具体如下:

public class SpringProxyTest {
        public static void main(String[] args) {
                UserInterface realUser = new RealUser();
                //传入真实对象 RealUser
                UserProxy userProxy = new UserProxy(realUser);
                userProxy.getUser();
        }
}

运行结果如下:

代理类开始执行
真实用户角色执行!
id:1
name:zhangsan
代理类结束执行

从打印结果可以看到,代理类实际上是调用了被代理类的方法。

2. 动态代理

顾名思义,动态代理是指在程序运行时动态地创建代理类。动态代理的使用方式主要分为两种:一种是基于接口的代理,另一种则是基于类的代理。基于接口的代理方式是指通过 JDK 自带的反射类来生成动态代理类;基于类的代理方式则是指通过字节码处理来实现类代理,如 CGLIB 和 Javassist 等。

首先我们来看一个基于 JDK 反射生成代理类的例子。

(1)定义一个公共接口 UserServiceInterface,代码如下:

public interface UserServiceInterface {
        public void getUser();
}

(2)定义真实用户角色类 UserServiceImpl 并实现公共接口 UserServiceInterface,代码如下:

public class UserServiceImpl implements UserServiceInterface {
        @Override
        public void getUser() {
                System.out.println("zhangsan");         //实现 getUser() 方法
        }
}

(3)定义代理类 UserServiceProxy,实现 InvocationHandler 接口,并重写 invoke() 方法,代码如下:

//定义实现 InvocationHandler 接口的代理类 UserServiceProxy
public class UserServiceProxy implements InvocationHandler {
        private Object target;
        //构造方法
        public UserServiceProxy(Object target) {
                this.target = target;
        }
        //通过 Proxy 动态生成代理类对象
        public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().get
ClassLoader(), target.getClass().getInterfaces(), this);
    }
    //动态执行方法
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
                System.out.println("JDK before");
                method.invoke(target, args);
                System.out.println("JDK after");
                return null;
        }
}

(4)编写测试代码:

public class SpringProxyTest {
        public static void main(String[] args) {
                //通过代理类生成 UserServiceInterface 接口类型对象
                UserServiceInterface userServiceInterface = new UserService
Proxy(new UserServiceImpl()).getProxy();
                userServiceInterface.getUser();         //调用 getUser() 方法
        }
}

打印结果如下:

JDK proxy before
zhangsan
JDK proxy after

通过上面的代理类的执行结果可以看到,真实用户角色类被屏蔽了,只需要暴露接口即可执行成功。屏蔽内部实现的逻辑就是代理模式的特点。

上面主要讲的是基于 JDK 反射的例子。下面来看一下 CGLIB 实现动态代理的原理。它是通过继承父类的所有公共方法,然后重写这些方法,并在重写方法时对这些方法进行增强处理来实现的。根据里氏代换原则(LSP),父类出现的地方子类都可以出现,因此 CGLIB 实现的代理类也是可以被正常使用的。

CGLIB 的基本架构如图 1.3 所示,代理类继承自目标类,每次调用代理类的方法时都会被拦截器拦截,然后在拦截器中调用真实目标类的方法。

027-1

图 1.3 CGLIB 动态代理实现原理

CGLIB 实现动态代理的方式比较简单,具体如下:

(1)直接实现 MethodInterceptor 拦截器接口,并重写 intercept() 方法。代码如下:

//继承 MethodInterceptor 类并实现 intercept() 方法
public class UserMethodInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
                System.out.println("Cglib before");
                proxy.invokeSuper(obj, args);
                System.out.println("Cglib after");
                return null;
        }
}

(2)新建 Enhancer 类,并设置父类和拦截器类。代码如下:

public class SpringProxyTest {
        public static void main(String[] args) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(UserServiceImpl.class);             //设置父类
                //设置拦截器
                enhancer.setCallback(new UserMethodInterceptor());
                UserServiceImpl userServiceImpl = (UserServiceImpl) enhancer.
create();                                                                                                               //创建对象
                userServiceImpl.getUser();                              //调用 getUser 方法
        }
}

打印结果如下:

Cglib before
zhangsan
Cglib after

JDK 实现动态代理是基于接口,其中,目标类与代理类都继承自同一个接口;而 CGLIB 实现动态代理是继承目标类并重写目标类的方法。在项目开发过程中,可根据实际情况进行选择。

1.3.2 AOP 中的术语

Spring AOP 就是负责实现切面编程的框架,它能将切面所定义的横切逻辑织入切面所指定的连接点中。AOP 是一种面向切面的编程,有很多独有的概念,如切面、连接点和通知等,它们组合起来才能完成一个完整的切面逻辑。因此,AOP 的工作重心在于如何将增强织入目标对象的连接点上。

1. 切面

切面(Aspect)通常由 Pointcut(切点)和 Advice(通知)组合而成。通常是定义一个类,并在类中定义 Pointcut 和 Advice。定义的 Pointcut 用来匹配 Join point(连接点),也就是对那些需要被拦截的方法进行定义。定义的 Advice 用来对被拦截的方法进行增强处理。在 Spring AOP 中,切面定义可以基于 XML 配置定义,也可以用 @Aspect 注解定义。我们可以简单地认为,使用 @Aspect 注解的类就是一个切面类。

2. 连接点

连接点是程序执行过程中的一个明确的点,如方法的执行或者异常处理。在 Spring AOP 中,一个连接点一般代表一个方法的执行。

3. 通知

通知是切面在特定的连接点上执行的特殊逻辑。通知可以分为方法执行前(Before)通知、方法执行后(After)通知和环绕(Around)通知等。包括 Spring AOP 在内的许多 AOP 框架通常会使用拦截器来增强逻辑处理能力,围绕着连接点维护一个拦截器链。

Spring AOP 的 Advice 类型如表 1.2 所示。

表 1.2 Advice 类型

029-1

4. 切点

切点是一种连接点的声明。通知是由切点表达式连接并匹配上切点后再执行的处理逻辑。切点用来匹配特定连接点的表达式,增强处理将会与切点表达式产生关联,并运行在匹配到的连接点上。通过切点表达式匹配连接点是 AOP 的核心思想。Spring 默认使用 AspectJ 的切点表达式。

5. 引入

Spring AOP 可以引入一些新的接口来增强类的处理能力。例如,可以使用引入(Introduction)让一个 Bean 实现 IsModified 接口,从而实现一个简单的缓存功能。

6. 目标类

目标类(Target Class)是指被切面增强的类。被一个或多个切面增强的对象也叫作增强对象。Spring AOP 采用运行时代理(Runtime Proxies),目标对象就是代理对象。

7. AOP 代理

Spring 框架中的 AOP 代理(AOP Proxy)指的是 JDK 动态代理或者 CGLIB 动态代理。为了实现切面功能,目标对象会被 AOP 框架创建出来。在 Spring 框架中,AOP 代理的创建方式包括两种:如果有接口,则使用基于接口的 JDK 动态代理,否则使用基于类的 CGLIB 动态代理。也可以在 XML 中通过设置 proxy-target-class 属性来完全使用 CGLIB 动态代理。

8. 织入

在编译期、加载期和运行期都可以将增强织入(Weaving)目标对象中,但 Spring AOP 一般是在运行期将其织入目标对象中。织入可以将一个或多个切面与类或对象连接在一起,然后创建一个被增强的对象。

1.3.3 @AspectJ 注解

在 spring-aspects 模块中引入了 AspectJ 工程。@AspectJ 可以通过注解声明切面。为了能够使用 @AspectJ 注解,必须要开启 Spring 的配置支持。@AspectJ 注解支持以 XML 的方式进行切面声明配置,如<aop:aspectj-autoproxy/>标签配置,也可以通过 Java 配置的方式进行切面声明配置。@EnableAspectJAutoProxy 注解用于开启 AOP 代理自动配置。AspectJ 的重要注解如表 1.3 所示。

表 1.3 AspectJ 的重要注解

030-1

@Pointcut 切点注解是匹配一个执行表达式。表达式类型如表 1.4 所示。

表 1.4 @Pointcut 表达式类型

031-1

@Pointcut 切点表达式可以组合使用&&、||或!三种运算符。示例代码如下:

@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation()
&& args(account,..)")

@Around 通知注解的方法可以传入 ProceedingJoinPoint 参数,ProceedingJoinPoint 类实例可以获取切点方法的相关参数及实例等。

1.3.4 基于 XML 配置的 AOP

下面的例子是基于 XML 方式配置的切面。

(1)定义一个类,在类中定义一些切点,代码如下:

//声明切面类
public class AspectTest {
    //方法执行前操作
        public void before() {
                System.out.println("before");
        }
    //方法执行后操作
        public void after() {
                System.out.println("after");
        }
    //方法环绕操作
        public void around() {
                System.out.println("around");
        }
}

(2)定义目标对象,代码如下:

//定义目标类
public class UserService {
        private Integer id;
        private String name;
        public Integer getId() {
                return id;
        }
        public void setId(Integer id) {
                this.id = id;
        }
        public String getName() {
                return name;
        }
        public void setName(String name) {
                this.name = name;
        }
        //执行方法
        public void getUser() {
                System.out.println("id:"+this.id);
                System.out.println("name:"+this.name);
        }
}

(3)基于 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <aop:aspectj-autoproxy/>
    <bean id="aspectTest" class="com.spring.boot.AspectTest" />
    <bean id="userService" class="com.spring.boot.UserService">
        <property name="id" value="1"/>
        <property name="name" value="zhangsan"/>
    </bean>
    <aop:config>
        <aop:pointcut expression="execution(public * com.spring.boot.
UserService.getUser(..))"
                      id="pointcut"/>
        <aop:aspect order="1" ref="aspectTest" >
            <aop:before method="before" pointcut-ref="pointcut"/>
        </aop:aspect>
        <aop:aspect order="2" ref="aspectTest" >
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
        <aop:aspect order="3" ref="aspectTest" >
            <aop:after method="around" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

测试程序,代码如下:

public class SpringXmlTest {
    public static void main(String[] args) {
        //通过 spring.xml 获取 Spring 应用上下文
        ApplicationContext context = new ClassPathXmlApplication
Context("spring.xml");
        UserService userService = context.getBean("userService",
UserService.class);
        userService.getUser();                          //打印结果
    }
}

在以上代码中,<aop:aspectj-autoproxy>标签开启了全局 AspectJ,<aop:config>标签定义了 Pointcut 和 Aspect。

1.3.5 基于 @Aspect 注解的 AOP

基于 @Aspect 注解的切面编程完全可以通过注解的形式完成。

(1)定义一个 @User 注解,代码如下:

//定义 @User 注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface User {
}

(2)定义切面类,代码如下:

//定义切面
@Aspect
@Component
public class AspectTest {
    //定义切点,表示使用了 @User 注解的方法将会被增强处理
        @Pointcut("@annotation(com.spring.boot.User)")
        public void pointCut() {}
    //在切点方法之前执行
        @Before("pointCut()")
        public void before() {
                System.out.println("before");
        }
    //处理真实的方法
        @Around("pointCut()")
        public void around(ProceedingJoinPoint proceedingJoinPoint)
throws Throwable {
                System.out.println("around before");
                proceedingJoinPoint.proceed();
                System.out.println("around after");
        }
    //在切点方法之后执行
        @After("pointCut()")
        public void after() {
                System.out.println("after");
        }
}

(3)新建配置类,代码如下:

//配置类开启切面配置
@EnableAspectJAutoProxy
@Configuration
@ComponentScan("com.spring.boot")
public class SpringConfigTest {
        @Bean
        public UserService userService() {
                return new UserService();
        }
    public static void main(String[] args) {
        //通过配置类获取 Spring 应用上下文
        ApplicationContext context = new AnnotationConfigApplication
Context(SpringConfigTest.class);
        UserService userService = context.getBean(UserService.class);
        userService.setId(1);
        userService.setName("zhangsan");
        userService.getUser();                                  //打印属性值
    }
}

(4)在目标类中增加 @User 注解,代码如下:

@Service
public class UserService {
        private Integer id;
        private String name;
        public Integer getId() {
                return id;
        }
        public void setId(Integer id) {
                this.id = id;
        }
        public String getName() {
                return name;
        }
        public void setName(String name) {
                this.name = name;
        }
    //添加了 @User 注解的方法
        @User
        public void getUser() {
                System.out.println("id:"+this.id);
                System.out.println("name:"+this.name);
        }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。