带着问题去学习知识
目录
问:AOP思想在spring中实现原理,代理方式有哪些,有什么区别?
问: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;
}
}