JavaWeb笔记整理06——AOP

目录


AOP介绍(来自百度)

简单比喻

AOP概述

AOP核心概念

AOP执行流程

AOP进阶

通知类型

@PointCut

通知顺序

切入点表达式

1.execution

2.@annotation

连接点

案例


AOP介绍(来自百度)

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。

简单比喻

Spring AOP(面向切面编程)是一个强大的编程范式,它允许开发者在不修改现有代码的情况下,增加额外的功能。

  1. 想象你有一个餐厅

    • 餐厅的厨师(目标对象)专注于制作美味的菜肴(业务逻辑)。
    • 但是,餐厅还需要一些额外的服务,比如在上菜前检查食物是否安全(日志记录或安全检查),或者在顾客用餐后询问满意度(性能监控)。
  2. AOP就像餐厅的经理

    • 经理(AOP框架)不会直接做厨师的工作,但他会在适当的时候介入,确保餐厅的服务质量。
    • 比如,经理会在厨师完成一道菜后,检查食物是否符合卫生标准(前置通知)。
    • 当顾客用餐时,经理可能会过来询问顾客是否需要额外的调料或者服务(环绕通知)。
    • 用餐结束后,经理可能会再次出现,询问顾客对食物的评价(后置通知)。
  3. 切面就像是一个服务清单

    • 这个清单上写着在哪些情况下需要提供额外服务,比如“每次上菜前”或“顾客用餐后”。
  4. 通知就像具体的服务动作

    • 前置通知就像是在上菜前检查食物的卫生。
    • 后置通知就像是在顾客用餐后询问满意度。
    • 环绕通知就像是在顾客用餐过程中随时提供帮助。
  5. 织入就像是经理的日常工作

    • 经理根据服务清单(切面),在适当的时机提供服务(织入通知)。
  6. 切点就是服务的时机

    • 比如“每次上菜前”或“顾客用餐后”,这些都是服务的时机。

AOP概述

AOP核心概念

AOP执行流程

SpringAOP的底层是基于动态代理技术实现的,在程序运行的时候,会自动的基于动态代理技术为这个目标对象生成一个对应的代理对象,在这个代理对象中就会对目标对象当中的原始方法进行功能增强,具体如何增强,增强的逻辑就是我们所定义的通知。

以这个案例为例,首先在方法运行开始之前,先记录方法运行的开始时间,然后再调用原始的方法执行,上图原始的方法就是那个list()方法,接下来在代理对象中就会来执行原始的list()方法当中的逻辑,原始方法运行完毕后,接下来再来记录方法运行的结束时间,最后再来统计方法执行的耗时。

所以,在代理对象中就已经对目标方法进行了增强。最终程序运行时,controller层中注入就不再是这个目标对象,而是代理对象。调用方法是也调用的是代理对象中的list()方法。

一旦进行AOP开发,那最终运行的就不再是原始的目标对象,而是基于目标对象所生成的代理对象。

AOP进阶

通知类型

@Slf4j
@Component
@Aspect
public class MyAspect1 {

    @Before("execution(* com.mikey.service.impl.DeptServiceImpl.*(..))")
    public void before(){
        log.info("before...");
    }

    @Around("execution(* com.mikey.service.impl.DeptServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around after...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();

        log.info("around after...");
        return result;
    }

    @After("execution(* com.mikey.service.impl.DeptServiceImpl.*(..))")
    public void after(){
        log.info("after...");
    }

    //原始方法执行完毕正常返回才会运行这个通知
    @AfterReturning("execution(* com.mikey.service.impl.DeptServiceImpl.*(..))")
    public void afterReturning(){
        log.info("afterReturning...");
    }

    //秒表方法执行出现异常时才会执行
    @AfterThrowing("execution(* com.mikey.service.impl.DeptServiceImpl.*(..))")
    public void afterThrowing(){
        log.info("afterThrowing...");
    }
}

大量切入点表达式重复了,直接将切入点表达式抽取出来

@Slf4j
@Component
@Aspect
public class MyAspect1 {
    
    @Pointcut("execution(* com.mikey.service.impl.DeptServiceImpl.*(..))")
    private void pt(){
    }

    @Before("pt()")
    public void before(){
        log.info("before...");
    }

    @Around("pt()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();

        log.info("around after...");
        return result;
    }

    @After("pt()")
    public void after(){
        log.info("after...");
    }

    //原始方法执行完毕正常返回才会运行这个通知
    @AfterReturning("pt()")
    public void afterReturning(){
        log.info("afterReturning...");
    }

    //秒表方法执行出现异常时才会执行
    @AfterThrowing("pt()")
    public void afterThrowing(){
        log.info("afterThrowing...");
    }
}

这个切入点表达式不仅可以在当前这个切面类当中来引用,也可以在其他的切面类当中来引用,private改为public就行。

public class TimeAspect {

    //@Around("execution(* com.mikey.service.*.*(..))")//切入点表达式
    @Around("com.mikey.aop.MyAspect1.pt()")

@PointCut

通知顺序

切入点表达式

1.execution

2.@annotation

如果要匹配两个不同的切入点方法,用||拼接很繁琐,可以用特定注解,简化。

连接点

@Around("execution(* com.itheima.service.DeptService.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    String className = joinPoint.getTarget().getClass().getName(); //获取目标类名
    Signature signature = joinPoint.getSignature(); //获取目标方法签名
    String methodName = joinPoint.getSignature().getName(); //获取目标方法名
    Object[] args = joinPoint.getArgs(); //获取目标方法运行参数 
    Object res = joinPoint.proceed(); //执行原始方法,获取返回值(环绕通知)
    return res;
}
@Before("execution(* com.itheima.service.DeptService.*(..))")
    public void before(JoinPoint joinPoint) {
    String className = joinPoint.getTarget().getClass().getName(); //获取目标类名
    Signature signature = joinPoint.getSignature(); //获取目标方法签名
    String methodName = joinPoint.getSignature().getName(); //获取目标方法名
    Object[] args = joinPoint.getArgs(); //获取目标方法运行参数 
}

案例

-- 操作日志表
create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
    operate_user int unsigned comment '操作人ID',
    operate_time datetime comment '操作时间',
    class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
    method_params varchar(1000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
    cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
    private Integer id; //ID
    private Integer operateUser; //操作人ID
    private LocalDateTime operateTime; //操作时间
    private String className; //操作类名
    private String methodName; //操作方法名
    private String methodParams; //操作方法参数
    private String returnValue; //操作方法返回值
    private Long costTime; //操作耗时
}
//自定义注解
@Retention(RetentionPolicy.RUNTIME)//指定当前这个注解什么时候有效,现在是运行时有效
@Target(ElementType.METHOD)//指定当前注解能作用在哪些地方 作用在方法上 不是类上也不是接口上
public @interface Log {
}
@Mapper
public interface OperateLogMapper {

    //插入日志数据
    @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
    public void insert(OperateLog log);

}
@Component
@Aspect//切面类
@Slf4j
public class LogAspect {

    @Autowired
    private OperateLogMapper operateLogMapper;

    @Autowired
    private HttpServletRequest request;

    @Around("@annotation(com.mikey.anno.Log)")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {

        //操作人的id -当前登陆人的id
        //获取请求头中的jwt令牌 解析令牌
        String jwt = request.getHeader("token");
        Claims claims = JwtUtils.parseJWT(jwt);//获取数据
        Integer operateUser = (Integer) claims.get("id");

        //操作时间
        LocalDateTime operateTime = LocalDateTime.now();

        //操作类名
        String className = joinPoint.getTarget().getClass().getName();

        //操作方法名
        String methodName = joinPoint.getSignature().getName();

        //操作方法参数
        Object[] args = joinPoint.getArgs();
        String methodParams = Arrays.toString(args);

        long begin = System.currentTimeMillis();
        //调用原始目标方法运行
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        //方法返回值
        String rerurnValue = JSONObject.toJSONString(result);

        //操作耗时
        Long costTime = end-begin;

        //记录操作日志
        OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,rerurnValue,costTime);
        operateLogMapper.insert(operateLog);

        log.info("AOP记录操作日志:{}",operateLog);
        return result;

    }
}
    /*
    删除部门
    */
    @Log
    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id){
        log.info("根据id删除部门",id);
        //调用service删除部门
        deptService.delete(id);
        return Result.success();
    }

    /*
    * 新增部门
    * */
    @Log
    @PostMapping
    public Result add(@RequestBody Dept dept){
        log.info("新增部门:{}",dept);
        //调用service新增部门操作
        deptService.add(dept);
        return Result.success();
    }
    /*
    * 修改部门信息
    * */
    @Log
    @PutMapping
    public Result update(@RequestBody Dept dept){
        log.info("更新部门信息");
        deptService.update(dept);
        return Result.success();
    }
    @Log
    @DeleteMapping("/{ids}")
    public Result delete(@PathVariable List<Integer> ids){
        log.info("批量删除操作,ids:{}",ids);
        empService.delete(ids);
        return Result.success();

    }

    @Log
    @PostMapping
    public Result save(@RequestBody Emp emp){
        log.info("新增员工,emp:{}",emp);
        empService.save(emp);
        return Result.success();
    }
    @Log
    @PutMapping
    public Result update(@RequestBody Emp emp){
        log.info("更新员工信息:{}",emp);
        empService.update(emp);
        return Result.success();
    }

获取当前登录对象挺关键的,需要从请求头中获取到用户的jwt令牌,然后解析令牌取出用户的id。

JavaWeb中,实现网站用户登录功能通常涉及到以下几个关键步骤: 1. **前端页面设计**:创建登录界面,包括用户名输入框(username)、密码输入框(password),以及登录按钮。可以使用HTML、CSS和JavaScript进行前端开发。 2. **后端服务器处理**:后端使用Java和Servlet或Spring MVC框架接收用户的登录请求。通常会涉及以下步骤: - 用户名和密码的验证:检查输入的用户名和密码是否匹配数据库中的记录。你可以使用JDBC连接数据库查询用户信息,也可以使用ORM框架如Hibernate或MyBatis。 - 使用Session或Cookie管理用户状态:如果验证通过,为用户创建一个Session,存储用户标识(通常是登录凭据的哈希值)或其他重要信息,这样后续请求可以识别用户。 3. **安全性考虑**:确保密码安全,一般会采用哈希+盐的方式加密存储,登录时对比哈希值。同时,防止SQL注入和XSS攻击。 4. **错误处理和反馈**:对输入错误或验证失败的情况,返回合适的错误消息给前端,并可能显示错误提示。 5. **登录/登出功能**:除了登录,还需要提供登出功能,清除Session或Cookie,结束用户会话。 6. **登录日志记录**:为了审计和安全,应该记录用户的登录尝试和结果。 相关问题: 1. 如何在JavaWeb中防止跨站脚本攻击(XSS)? 2. 什么是Session和Cookie的区别,它们在用户登录中的作用是什么? 3. 如何在Java中使用Spring Security来增强登录系统的安全性?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值