AOP学习记录

带着问题去学习知识


目录

先问在学习

问:AOP是什么?解决了什么问题?应用场景?

问:AOP编程中核心对象以及应用关系?

问:AOP思想在spring中实现原理,代理方式有哪些,有什么区别?

问:AOP应用掌握了哪些切入点表达式?

问:AOP中切面可以有多个吗?可以作用于同一个切入点方法吗?

问:AOP中常用的注解以及解释和使用。

下面一个关于切面的业务代码(非常暴力)


问题:

问:AOP是什么?解决了什么问题?应用场景?

答:AOP是什么?

AOP ,意为面向切面编程,我个人的理解是动态嵌入其他代码,是oop的延续。是通过JDK或CGLIB的代理对象来进行功能通知,通过动态代理的方式来实现统一维护的一种技术,具有低耦合性,灵活性和可扩展性。

拓展知识:

而oop意为面向程序编程,大概描述是把客观存在的类抽象成相互独立的类,然后把相关的属性或行为封装到类里面,通过继承和多态来定义彼此之间的关系,最后通过操作类的实例来完成实际业务逻辑的功能需求(百度...)。

二者关系应该为相互补充和完善的关系。

二者区别:

1.面向目标不同:OOP是面向名词领域,AOP是面向动词领域。

2.思想结构不同:OOP是纵向结构,AOP是横向结构。

3.注重方面不同:OOP注重业务逻辑单元的划分,AOP注重偏向业务处理逻辑的某个步骤或阶段过程。

解决了什么问题?

解决了不改变原业务代码的同时增加功能模块,提高了开发效率。

知识拓展:这可以从底层逻辑,也就是代理方式说起,比如说,如何在不改变源代码的情况下增加业务呢?这里有两种方式,继承该源代码增加业务,还有就是组合的方式。继承我就不多讲了,java基础的内容。组合我附上伪代码方便大家理解。

首先先定义一个接口:

public interface NoticeService {
    void sayH();
}

然后实现这个接口的方法

public class NoticeServiceImpl implements NoticeService{
    @Override
    public void sayH() {
        System.out.println("源业务逻辑代码---H");
    }
}

以上就是我们正常的功能业务逻辑的实现,接下来就是重头戏了

我们在现在的基础上在创建一个NoticeService的实现类去实现方法,通过构造方法直接去代理NoticeServiceImpl对象,通过实现方法去进行功能通知(功能增强)。

public class JdkNoticeImpl implements NoticeService {
    //组合NoticeServiceImpl
    private NoticeServiceImpl noticeService = new NoticeServiceImpl();

    //代理NoticeServiceImpl
    public JdkNoticeImpl(NoticeServiceImpl noticeService){
        this.noticeService = noticeService;
    }

    //实现方法
    @Override
    public void sayH() {
        System.out.println("前置通知...");
        noticeService.sayH();
        System.out.println("后置通知...");
    }
}

最后我们来测试一下

public class Test {
    public static void main(String[] args) {
        //引用代理对象
        JdkNoticeImpl jdkNotice = new JdkNoticeImpl(new NoticeServiceImpl());
        jdkNotice.sayH();
    }
}

结果为

以上就是为了帮助理解JDK代理的伪代码,下面帮助理解手写关系图

 应用场景?

日志切面,权限切面,事务切面,性能统计,安全控制,异常处理等等,自行理解吧

问:AOP编程中核心对象以及应用关系?

答:首先先了解AOP中的核心对象有哪些,

代理对象:是指AOP的底层实现,主要为JDK代理和CGLIB代理,是AOP中的核心对象。

切面对象:切面对象就是指包含通知的一个切面类,进行功能拓展的部分。

通知:通知指的是针对切入点共性功能的实现逻辑代码功能,具体有Around,Before,AfterReturning,AfterThrowing,After等。

连接点:业务层接口的所有方法都叫连接点

切入点:被增强的业务层接口的方法叫切入点

这里的话连接点和切入点概念有点类似,但连接点不一定是切入点,切入点一定是连接点,需注意。

应用关系是是aop通过代理对象实现连接点,然后切入点是匹配连接点的式子,通知就是功能方法,在切入点处执行,最终由单个或者多个方法组成的类也就是一个切面。

问:AOP思想在spring中实现原理,代理方式有哪些,有什么区别?

代理方式有JDK,CGLIB两种方式,区别
JDK:基于反射机制生成的一个实现代理接口的匿名类,但只能对实现接口的类生成代理,通过反射动态实现接口类,JDK创建代理对象效率较高,执行效率较低,并且JDK不需要依赖第三方库,只需要JDK环境就可以满足了,但是代理对象必须要实现接口。


CGLIB:使用的继承机制,针对类实现代理,被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,因为是继承机制,不能代理final修饰的类(也就是不能是太监类),CGLIB创建代理对象效率较低,执行效率高,并且必须依赖于CGLib的类库。

问:AOP应用掌握了哪些切入点表达式?

1. execution(返回值 全限定类名.方法名.(方法参数)),本表达式可粗可细,能用通配符代替,例如*代表所有

2. @within(com.spring.service.BusinessObject),一般为全限定类名,也可用通配符代替m,较粗

3. @annotation(com.spring.annotation.BusinessAspect),@annotation的使用方式与@within的相似,表示匹配使用@annotation指定注解标注的方法将会被环绕.

4.等等,自己想办法去拓展吧,还有更多惊喜等你来发现。

问:AOP中切面可以有多个吗?可以作用于同一个切入点方法吗?

AOP是允许多个切面存在的,且可以作用于同一个切入点。但是这里就有一个问题了,那就是执行顺序是什么样的,怎么自己DIY切面执行顺序呢,这里Spring提供了一个注解@Order和一个接口Ordered

@Order注解使用实例

@Aspect
@Order(1)
public class MyAspect {}

Ordered接口使用实例

package cn.hctech2006.boot.bootaop.aspect;

import org.aspectj.lang.annotation.*;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

@Aspect
//@Order(1)
public class MyAspect implements Ordered {
    @Override
    public int getOrder() {
        return 1;
    }
    @Pointcut("execution(* cn.hctech2006.boot.bootaop.service.impl.UserServiceImpl.manyAspects())")
    public void pointCut(){

    }
    @Before("pointCut()")
    public void before(){
        System.out.println("MyAspect before....");
    }
    @After("pointCut()")
    public void after(){
        System.out.println("MyAspect after......");
    }
    @AfterReturning("pointCut()")
    public void afterReturning(){
        System.out.println("MyAspect afterReturning......");
    }

}

问:AOP中常用的注解以及解释和使用。

@Aspect:用于类上,描述此刻为一个切面类

@Before:该注解是声明此方法为前置通知 (目标方法执行之前就会先执行被此注解标注的方法)

@After:该注解是声明此方法为后置通知 (目标方法执行完之后就会执行被此注解标注的方法)

@AfterReturning:该注解是声明此方法为返回通知 (目标方法正常执行返回后就会执行被此注解标注的方法)

@AfterThrowing:该注解是声明此方法为异常通知 (目标方法在执行出现异常时就会执行被此注解标注的方法)

@Around: 该注解是环绕通知是动态的,可以在前后都设置执行

@PointCut: 该注解是声明一个公用的切入点表达式(通知行为的注解的都可以直接拿来复用)

@EnableAspectJAutoProxy:是一个配置注解,意为开启spring对注解aop的支持

Joinpoint:连接点,不是注解。

下面一个关于切面的业务代码(非常暴力)

package com.liu.dbsys.aspect;

import com.google.gson.Gson;
import com.liu.dbsys.annotation.SysLogAnnotation;
import com.liu.dbsys.pojo.SysLogs;
import com.liu.dbsys.service.SysLogsService;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;


//此切面记录日志
@Aspect
@Component
public class SysLogAspect {

    @Autowired
    private SysLogsService service;

    /**
     * 细粒度切入点解释:
     * execution(返回值 类全名.方法名(参数列表类型))
     */
    /*前置通知--1*/
    @Before("execution(public * com.liu.dbsys.service.impl.*.*(..))")
    public void dobefore() {

    }

    /*后置通知前--2*/
    @AfterReturning("execution(public * com.liu.dbsys.service.impl.*.*(..))")
    public void AfterReturning() {

    }

    /*后置异常通知--2*/
    @AfterThrowing("execution(public * com.liu.dbsys.service.impl.*.*(..))")
    public void AfterThrowing() {

    }

    /*后置通知--3*/
    @After("execution(public * com.liu.dbsys.service.impl.*.*(..))")
    public void doafter(JoinPoint joinPoint) throws IllegalAccessException {

    }

    /*环绕通知--1,--n*/
    @Around("execution(public * com.liu.dbsys.service.impl.*.*(..))")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        Object proceed = jp.proceed();
        return proceed;

    }


    @Around(value = "@annotation(com.liu.dbsys.annotation.SysLogAnnotation)")
    public Object aroundAnnotation(ProceedingJoinPoint pjp) throws Throwable {
        /**
         * SysMethod:访问地址
         * SysIp:访问的ip地址
         * SysCreatedTime:访问时间
         * username:访问者
         * operation:操作名称
         * SysTime:方法执行时间
         * */
        //获取访问的Servlet
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //获取自定义注解的昵称
        //1.通过连接点获取方法的签名
        MethodSignature ms = (MethodSignature) pjp.getSignature();
        //2.获取目标类的字节码对象
        /*
         * 获取目标字节码对象的三种方法(Java基础内容)
         * obj.getClass();
         * 类名.class;
         * Class.forName("目标类的全限定类名");
         * */
        Class<?> targetCls = pjp.getTarget().getClass();
        //3.通过目标类的字节码对象基于暴力反射获取目标方法
        Method method = targetCls.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
        //4.获取到目标方法上的注解
        SysLogAnnotation requiredLog = method.getAnnotation(SysLogAnnotation.class);
        String operation = requiredLog.name();
        //访问方法
        String SysMethod = pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName();
        //访问IP
        String SysIp = request.getRemoteAddr();
        //访问参数
        Object[] obj = pjp.getArgs();
        Gson gson = new Gson();
        String ParamList = gson.toJson(obj);
        //访问用户
        String username = "admin";
        //记录方法执行时间
        long TimeOne = System.currentTimeMillis();
        //执行目标方法前
        Object proceed = pjp.proceed();
        //执行目标方法后
        long TimeTwo = System.currentTimeMillis();
        //执行时间
        Long SysTime = (TimeTwo - TimeOne);
        //打印日志参数
        System.out.println("访问者:" + username);
        System.out.println("访问方法名称:" + operation);
        System.out.println("访问方法地址:" + SysMethod);
        System.out.println("访问Ip:" + SysIp);
        System.out.println("访问参数:" + ParamList);
        System.out.println("执行时间:" + SysTime);
        //封装日志数据包
        SysLogs logs = new SysLogs();
        logs.setParams(ParamList);
        logs.setOperation(operation);
        logs.setUsername(username);
        logs.setIp(SysIp);
        logs.setTime(SysTime);
        logs.setMethod(SysMethod);
        //存储日志操作
        if (operation != null || !operation.equals("")) {
            service.setLogsObjects(logs);
            System.out.println("日志记录成功");
        }
        return proceed;
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行呗头发还尚存

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值