Aop的黑马学习笔记

Aop的总结:
1.aop是什么?
aop是面向切面编程,和loc是spring的核心。通过预编译方式和动态代理实现程序维护的技术。简而言之,就是将重复的代码集中统一管理。下面例子中:add方法和select方法存在重复程序:

  public void add(){
        //记录程序执行 开始时间
        long begin = System.nanoTime();
        //执行业务逻辑
        log.info("添加...");
        //录程序执行 结束时间
        long end = System.nanoTime();
        //记录程序执行耗时
        log.info("执行耗时{} ns",(end - begin));
    }

    public String select(){
        long begin = System.nanoTime();
        log.info("查询...");
        long end = System.nanoTime();
        log.info("执行耗时{} ns",(end - begin));

        return "user";
    }
    重复程序有:
      //记录程序执行 开始时间
        long begin = System.nanoTime();
        //调用原始操作
        
        //录程序执行 结束时间
        long end = System.nanoTime();
        //记录程序执行耗时
        log.info("执行耗时{} ns",(end - begin));

2.aop的步骤:
2.1.把依赖导入 2.2定义类,定义方法无返回值,抽取公共代码。(例如:记录执行的开始时间、代用原始操作、记录程序结束时间、记录程序耗时多少)

1.导依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.定义类抽取公共代码(执行耗时统计操作)
public class TimeAspect {

    public void recordTime() {
        //记录程序执行 开始时间
        long begin = System.nanoTime();
        //调用原始操作
        
        //录程序执行 结束时间
        long end = System.nanoTime();
        //记录程序执行耗时
        log.info("执行耗时{} ns",(end - begin));
    }
}
3.标识当前类是一个AOP类,并被Spring容器管理(@Aspect` 标识当前类是一个AOP类)
@Slf4j
@Component
@Aspect
public class TimeAspect {
    @Around("execution(* com.itheima.service.impl.UserServiceImpl.*(..))")
    public Object  recodeTime(ProceedingJoinPoint joinPoint) throws Throwable {
        //开始执行时间
        long begin = System.nanoTime();
        //目标对象
        Object proceed = joinPoint.proceed();
        //结束执行时间
        long end = System.nanoTime();

        //时间差
        log.info("共耗时:{}",(end-begin));

        return proceed;
    }
}
4.执行目标方法
public class TimeAspect {
@Around("execution(* com.itheima.service.impl.UserServiceImpl.*(..))")
 public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        //记录程序执行 开始时间
        long begin = System.nanoTime();
        //调用原始操作
        Object proceed = joinPoint.proceed();
        //录程序执行 结束时间
        long end = System.nanoTime();
        //记录程序执行耗时
        log.info("执行耗时{} ns",(end - begin));
        return proceed;
   }
}
5.测试运行
@SpringBootTest
class AopDemoApplicationTests {

    @Autowired
    private UserService userService;

    @Test
    void testAdd(){
        userService.add();
    }

}

2.3定义一个aop类,而通过@Aspect标识当前类是一个aop类。而@Component是把该类交给spring容器管理

3.通知类型:
而在配置公共代码的方法时会用到的注解有@Around、@Before、@After、@AfterReturning、@AfterThrowing、@Annotation

- @Before - 此注解标注的通知方法在目标方法`前`被执行
- @After - 此注解标注的通知方法在目标方法`后`被执行,`无论是否有异常`
- @AfterReturning - 此注解标注的通知方法在目标方法`后`被执行,`有异常不会执行`
- @AfterThrowing - 此注解标注的通知方法发生`异常后`执行

4.通知顺序:
4.1是按照类的字母顺序进行排序(不推荐)4.2是按照@Order(数字)
4.2.1:前置通知:执行顺序是从小到大,小的先执行。最终执行通知:从大到小:大的先执行

/**
 * 通知类型测试
 */
@Aspect
@Component
@Slf4j
public class AdviceType {


    // @Before  在目标方法执行前执行
    @Before("execution(* com.itheima.service.impl.UserServiceImpl.update(..))")
    public void BeforeAdvice(){
        log.info("before...");
    }

    // @After  在目标方法执行后执行,且无论是否出现异常
    @After("execution(* com.itheima.service.impl.UserServiceImpl.update(..))")
    public void AfterAdvice(){
        log.info("After...");
    }

    //  在目标方法执行后执行,如果出现异常,则不执行
    @AfterReturning("execution(* com.itheima.service.impl.UserServiceImpl.update(..))")
    public void AfterReturningAdvice(){
        log.info("AfterReturning...");
    }

    //  目标方法出现异常,执行
    @AfterThrowing("execution(* com.itheima.service.impl.UserServiceImpl.update(..))")
    public void AfterThrowingAdvice(){
        log.info("AfterThrowing...");
    }

    //  环绕通知,目标方法执行前执行-->目标方法执行 --> 目标方法执行后执行
    @Around("execution(* com.itheima.service.impl.UserServiceImpl.update(..))")
    public Object AroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Around 前...");
        Object proceed = joinPoint.proceed();
        log.info("Around 后...");
        return proceed;
    }
}

5.切点表达式:
就三个关键字:Execution、@Annotation、Args 特殊符号;*:表示:任意的方法名、包名、类名等等 (…):表示任意参数

@Slf4j
@Component
@Aspect
public class PointCutExpress {

    @Before("execution(* com.itheima.service.impl.UserServiceImpl.select*(..))")
    public void before(){
        log.info("before");
    }
}
- execution(返回值类型 包名.类名.方法名(参数类型))`
  - `*` 可以通配任意返回值类型、包名、类名、方法名、或任意类型的一个参数
  - `..` 可以通配任意层级的包、或任意类型、任意个数的参数
- `@annotation()` 根据注解匹配
- `args()` 根据方法参数匹配

6.什么是连接点?
就相当于目标方法,spring使用它来代替方法名和方法参数类型、方法的实际参数
特别是对@Around注解,他是用的是JoinPoint的子类型:ProceedingJoinPoint,其他四种通知类型只能使用父类型
7.cglib和jdk的区别:
首先至今默认的是cglib,用法是在配置文件添加Spring.aop.proxy.target.class=true,接着就true/false而言,true是CGLIB,而false是jdk
在jdk下又分为目标实现有接口,还是没有接口。有接口则会生成jdk动态代理,没有接口默认还是CGLIB
8.案例:就是监控用户对数据进行增删改等方面没有规律的操作使用aop技术统一操作

自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Log {
  
}

9.aop技术使用:
1.自定义一个注解:具体的忘了不过,先加一个target(),其中括号里面写的是…method.然后其他的使用ctrl看Target底层代码,里面类的注解和target在一起的就是了
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
然后在ControllerServiceImpl中的增删改等能影响数据的操作添加自定义注解,作用是相当于标识的作用

2.数据库创建日志表:字段名就是操作数据用户的相关信息
例如:用户名、访问类名.方法名、请求参数、创建时间、执行时长

CREATE TABLE `sys_log`  (
    `id` int primary key AUTO_INCREMENT,
    `username` varchar(50)   COMMENT '用户名',
    `method` varchar(200)  COMMENT '访问方法名',
    `params` varchar(5000)  COMMENT '请求参数',
    `time` int NOT NULL COMMENT '执行时长(毫秒)',
    `create_time` datetime COMMENT '创建时间'
);

3.在创建日志实体类。

@Data
public class SysLog {
    private Integer id;
    //用户名
    private String username;
    //访问类名.方法名
    private String method;
    //请求参数
    private String params;
    //执行时长(毫秒)
    private Integer time;
    //创建时间
    private LocalDateTime createTime;
}

4.编写日志操作mapper

@Mapper
public interface SysLogMapper {

    @Insert("insert into sys_log (username, method, params, time, create_time) values (   #{username},\n" +
            "    #{method},\n" +
            "    #{params},\n" +
            "    #{time},\n" +
            "    #{createTime})")
    void save(SysLog sysLog);
}

5.编写操作日志的接口service以及serviceImpl类,同时serviceImpl实现该接口
接口中的方法和mapper的方法一致,可直接复制。接着在IMPL中重写方法

接口类
public interface SysLogService {

    void add(SysLog sysLog);
}
接口的实现类
@Service
public class SysLogServiceImpl implements SysLogService {

    @Autowired
    private SysLogMapper sysLogMapper;
    @Override
    public void add(SysLog sysLog) {
        sysLogMapper.add(sysLog);
    }
}

6.开始编写日志切面类
首先在类上添加可以标志该类是aop类的注解
@Aspect
以及将其交给spring管理的注解
@Component
接者注入接口,这里的注解是@Autowired
接着就编写执行目标方法
操作流程:1.操作人2.执行时长3.执行方法全类名4.执行方法参数5.操作时间6.最后将数据保存到数据库

1.创建日志对象

2.操作时间

3.执行时长

4.开始时间、执行目标方法、结束时间、时长计算

5.执行方法全类名:分别从获取方法名和类名操作=>方法名使用到的方法有:参数名.getSignature().getName()

类名涉及方法:参数名.getTarget().getClass().getName()
最后是用mapper中方法的参数的setMethod方法,将方法名和类名进行拼接

6.执行方法参数:也是用方法和参数两个方面考虑:首先使用参数名中的获取参数方法(getArgs),返回一个数组,然后导入依赖hutool工具,接着在使用json的toJsonString方法
返回json合适的字符串。
然后使用mapper下方法的参数调用设置任意参数的方法setParams。将上面返回的字符串放入其中

6.操作人登陆成功后,数据存储到session,需要从session中获取:
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpSession session=attr.getRequest().getSession(true);
User user = (User) session.getAttribute(“loginUserSuccess”);
sysLog.setUsername(user.getUsername());
注意点:这里的loginUserSuccess必须和userController中的登陆成功标志(session.setAttribute(“loginUserSuccess”,u))一致

7.调用方法存储数据到数据库:
使用注入的变量名调用mapper下的方法。
例如: sysLogService.add(sysLog);
具体代码如下:

@Component
@Aspect
public class LogAspect {
    @Autowired
    private SysLogService sysLogService;

    @Around("@annotation(com.itheima.anno.Log)")
    public Object rol(ProceedingJoinPoint joinPoint) throws Throwable {
        //1.操作日志对象
        SysLog sysLog = new SysLog();
        //2.操作时间
        sysLog.setCreateTime(LocalDateTime.now());
        //2.1方法执行时长
        long begin = System.currentTimeMillis();
        //执行目标方法
        Object proceed = joinPoint.proceed();
        long end = System.currentTimeMillis();
        //时间差
        sysLog.setTime((int) (end-begin));
        //执行类名.方法名
        //获取方法名
        String name = joinPoint.getSignature().getName();
        //获取类名
        String className = joinPoint.getTarget().getClass().getName();
        sysLog.setMethod(className+"."+name);
        //执行方法参数
        //获取参数
        Object[] args = joinPoint.getArgs();
        //将其转为json格式
        String jsonString = JSON.toJSONString(args);
        //String toString = Arrays.toString(args);
        sysLog.setParams(jsonString);
        //操作人登陆成功后将用户名,登录用户存储在session
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpSession session=attr.getRequest().getSession(true);
        User user = (User) session.getAttribute("loginUserSuccess");
        sysLog.setUsername(user.getUsername());
        //保存到数据库
        sysLogService.add(sysLog);

        return proceed;
    }

}

此外在使用session之前建议有登录代码:

第一个:用户登录的controller

@RestController
@RequestMapping("/users")
public class UserController {
    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public R login(@RequestBody User user, HttpSession session){
        User u = userService.login(user.getUsername(), user.getPassword());
        if (u==null){
            return R.error("用户名或密码输入错误");
        }else{
            //登陆成功
            //存储登陆成功标记
            session.setAttribute("loginUserSuccess",u);
            return R.success();
        }
    }
}

第二个:拦截器代码:

package com.itheima.filter;

import com.alibaba.fastjson.JSON;
import com.itheima.domain.R;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * 登录校验
 * 1.判断标记是否存在
 * 2.从session中取出来
 * 2.
 * 存在。放行
 * 不存在。则拦截。跳转到登录界面
 */
@WebFilter("/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        /**
         *判断登录标记是否存在:session=> servletRequest(有的是request),而因为这里的类型是ServletRequest,所以要进行强转为HttpServletRequest
         */
        HttpServletRequest hsr = (HttpServletRequest)servletRequest;
        HttpSession session = hsr.getSession();
        Object loginUserSuccess = session.getAttribute("loginUserSuccess");
        //放行对应资源文件:前端资源(html、css、js、image)、登录接口、注册接口、
        //定义换一个目标数组,把放心的
        //判断请求路径,是否包含request.js
        String[] ignoreUrls={".html",".css",".js","/img","/element-ui","/login"};
        //建个循环

        String url = hsr.getRequestURI();
        for (String ignoreUrl : ignoreUrls) {
            if (url.contains(ignoreUrl)){
                //放行
                filterChain.doFilter(servletRequest,servletResponse);
                return;
            }
        }
        if (loginUserSuccess!=null){
            //登录成功,放行
            filterChain.doFilter(servletRequest,servletResponse);
            //不想让其继续往下执行,所以使用return,结束改方法
            return;
        }
        //如果用户没有登录成功,就跳转回登录界面
        //方法一:response,此方法不推荐,因为不知道跳转界面的地址
      /*  HttpServletRequest resp=(HttpServletRequest) servletResponse;
        resp.senndRedirect("");*/

        //方法二:返回R对象
        R r = R.error("NO LOGIN");
        //只能通过手动将R转为json,返回
        //使用json工具的toJSONString方法,将R对象转为JSON字符串
        String jsonString = JSON.toJSONString(r);
        //返回数据:responce有一个方法:getWriter=》writer
        servletResponse.getWriter().write(jsonString);

    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值