Java基础学习之理解AOP编程

本文介绍了AOP(面向切面编程)的概念,强调其在关注点分离中的作用。通过Java动态代理和注解的方式展示了如何实现AOP,包括使用反射和Proxy创建代理对象,以及通过注解简化代码。文章详细解释了反射机制,并给出了使用注解实现AOP的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是AOP

AOP是Aspect Oriented Programming的缩写,意思是面向切面编程,与OOP(Object Oriented Programming)面向对象编程对等,都是一种编程思想。

AOP主要遵循关注点分离原则,程序总是被分为几块,有核心部分,也有辅助部分。当我们在重点关注核心业务时,此时可以考虑将非核心部分剥离出来,例如订单核心模块时下单、支付,而对于记录日志、通知或触发机制可以考虑通过切面编程实现。而接下来我们将通过几个例子来具体了解怎么实现,看完后你将学习到Java重要的的反射机制,代理等知识。


反射和动态代理

当我们在面试时可能会碰到这样的笔试题:
你开发的核心订单服务,有一个支付方法,现在要为这个支付方法增加统计执行时间的切面。请通过面向切面编程围绕pay方法计算耗费时间, 已有代码:

public interface IOrder {
    void pay() throws InterruptedException;
    void show();
}

public class Order implements IOrder {
    int state = 0;
    @Override
    public void
    pay() throws InterruptedException {
        Thread.sleep(50);
        this.state = 1;
    }
    @Override
    public void show() {
        System.out.println("order status:" + this.state);
    }
}
public class TimeUsageAspect implements  Aspect{
    long start;
    @Override
    public void before()
    {
        start = System.currentTimeMillis();
    }
    @Override
    public void after()
    {
        long usage = System.currentTimeMillis() - start;
        System.out.format("time usage : %dms\n", usage);
    }
}
	IOrder order = Aspect.getProxy(Order.class,
	"coding.proxy.TimeUsageAspect");
	order.pay();
	order.show();

我们重点需要实现getProxy方法,实现如下:

public interface Aspect
{
    void before();
    void after();

    /**
     * 切面实现
     * @param cls
     * @param aspects
     * @param <T>
     * @return
     */
    static <T> T getProxy(Class<T> cls,String ... aspects) 
    {
        List<Aspect> aspectIns=Arrays.stream(aspects).map(aspect->{
            try {
                // 动态加载类
                Class clazz= Class.forName(aspect);
                // 获取代理类实例
                return (Aspect)clazz.getConstructor().newInstance();
            } catch (Exception e) {
                return null;
            }
        }).filter(t->t!=null).collect(Collectors.toList());
        // 获取具体业务类实例,比如new Order()
        T inst = cls.getConstructor().newInstance();
        // 代理的是接口(Interfaces),不是类(Class),也不是抽象类
        return (T)Proxy.newProxyInstance(cls.getClassLoader(),
        cls.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            	// 执行一些pay之前的逻辑
                aspectIns.forEach(aspect -> aspect.before());
                Object result=method.invoke(inst);
                 // 执行一些pay之后的逻辑
                aspectIns.forEach(aspect -> aspect.after());
                return result;
            }
        });
    }

}

动态代理的是接口(Interfaces),不是类(Class),也不是抽象类,所以最后我们都需要通过接口去IOrder 去接受,否则会转换失败。

上述通过Java的反射调用动态代理Proxy的newProxyInstance方法实现切面编码,而其中有三个参数我们需要明白:
loader: 用哪个类加载器去加载代理对象
interfaces:动态代理类需要实现的接口
invocationHandler:动态代理方法在执行时,会调用h里面的invoke方法去执行

什么是反射?也许你应该可以理解到:

JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。


实现AOP注解

对于上面的方式,如何通过注解来实现呢?也许你可以这样:
首先定义一个注解的接口

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Aspect {
    public  Class type();
}

RetentionPolicy.RUNTIME表示在运行中可以加载到
ElementType.TYPE表示注解可以加载到接口、类、枚举上
其它几种类型有

@Target(ElementType.FIELD) //字段、枚举的常量

@Target(ElementType.METHOD) //方法

@Target(ElementType.PARAMETER) //方法参数

@Target(ElementType.CONSTRUCTOR) //构造函数

@Target(ElementType.LOCAL_VARIABLE)//局部变量

@Target(ElementType.ANNOTATION_TYPE)//注解

@Target(ElementType.PACKAGE) ///包

接着将注解添加到Order类上

@Aspect(type=TimeUsageAspect.class)
public class Order implements IOrder {
    int state = 0;

    @Override
    public void
    pay() throws InterruptedException {
        Thread.sleep(50);
        this.state = 1;
    }

    @Override
    public void show() {
        System.out.println("order status:" + this.state);
    }
}

最后实现注解的拦截

public class ObjectFactory {
    public static  <T> T newInstance(Class<T> clazz) throws NoSuchMethodException,InstantiationException,IllegalAccessException,InvocationTargetException{
        Annotation[] annotations=clazz.getConstructor().getAnnotations();
        List<IAspect> aspects=new LinkedList<>();
        for(Annotation annotation:annotations){
            if(annotation instanceof Aspect){
                Class aspect=((Aspect) annotation).type();
                aspects.add((IAspect)aspect.getConstructor().newInstance());
            }
        }
        T inst=clazz.getConstructor().newInstance();
        return (T)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(),new OrderInvocationHandler(aspects,inst));
    }
}

通过添加注解的方式,我们确实发现这样的业务代码逻辑就十分简单了,不需要再去传入多余的参数,只关注业务本身即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值