一 AOP介绍
1 为什么需要AOP
在不修改源代码的基础上对对象功能进行拓展
例如现有一个公告(通知)业务接口及实现:
public interface NoticeService{
int deleteById(Integer…ids);
}
public class NoticeServiceImpl implements NoticeService{
public int deleteById(Integer…ids){
System.out.println(Arrays.toString(ids));
return 0 ;
}
}
需求:基于OCP(开闭原则-对扩展开放,对修改关闭)设计方式对NoticeServiceImpl类的功能进行扩展,例如在deleteById业务方法执行之前和之后输出一下系统时间.
1.1 解决方式1: 基于继承方式实现其功能扩展
public class CglibLogNoticeService extends NoticeServiceImpl{
public int deleteById(Integer…ids){
System.out.println("Start:"+System.currentTimeMillis());
Int rows=super.deleteById(ids);
System.out.println("After:"+System.currentTimeMillis());
return rows;
}
}
测试类:
public class NoticeServiceTests{
public static void main(String[] args){
NoticeService ns=new CglibLogNoticeService();//多态对象
ns.deleteById(10,20,30);//调用父类的方法
}
}
但是,基于继承方式实现功能扩展,代码简单,容器理解,但是不够灵活,耦合性比较强
1.2 解决方案2: 基于组合方式实现其功能扩展
public class JdkLogNoticeService implements NoticeService{
private NoticeService noticeService;//has a
public JdkLogNoticeService(NoticeService noticeService){
this.noticeService=noticeService;
}
public int deleteById(Integer…ids){
System.out.println("Start:"+System.currentTimeMillis());
int rows=this.noticeService.deleteById(ids);
System.out.println("After:"+System.currentTimeMillis());
return rows;
}
}
测试类编辑:
public class NoticeServiceTests{
public static void main(String[] args){
NoticeService ns=
new JdkLogNoticeService(new NoticeServiceImpl());//装饰模式--功能增强
ns.deleteById(10,20);
}
}
但是,基于组合方式实现功能扩展,代码比较灵活,耦合低,稳定性强,但理解相对比较困难.
1.3 总结
无论是继承,还是组合都是基于OCP方式实现了对象功能扩展,都有相应的优缺点,并且我们都要自己去写这些子类或兄弟类,在这些类中调用目标对象(父类或兄弟类对象)的方法以及扩展业务逻辑.对于这样的模板代码我们能否进行简化呢?例如.由框架实现其共性(创建目录类型的子类类型或兄弟类型),特性交给用户自己实现,这种方法叫做代理.
2 AOP定义
AOP(Aspect Orient Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程(OOP)的一种补充和完善。
实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面理解为一个动态过程(在对象运行时动态织入一些扩展功能或控制对象执行.
AOP 与 OOP 字面意思相近,但其实两者完全是面向不同领域的设计思想。实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理方式,理解为一个动态过程,可以在对象运行时动态织入一些扩展功能或控制对象执行。
3 AOP实现的原理
动态代理对象: AOP可以在系统启动时为目标类型创建子类或兄弟类型对象
为目标类型(XxxServiceImpl)创建其代理对象方式有两种:
- 借助JDK官方API为目标对象类型创建其兄弟类型对象,但是目标对象类型需要实现相应接口.
- 借助CGLIB库为目标对象类型创建其子类类型对象,但是目标对象类型不能使用final修饰.目前使用此方法,因为controller没有实现某个service接口
4 AOP下的知识点
- 切面(aspect): 横切面对象,一般为一个具体类对象。
- 切入点(pointcut):定义了切入扩展业务逻辑的位置(哪些方法运行时切入扩展业务),一般会通过表达式进行相关定义,一个切面中可以定义多个切入点。
- 通知(Advice): 内部封装扩展业务逻辑的具体方法对象,一个切面中可以有多个通知(在切面的某个特定位置上执行的动作(扩展功能)。
- 连接点(joinpoint):程序执行过程中,封装了某个正在执行的目标方法信息的对象,可以通过此对象获取具体的目标方法信息,甚至去调用目标方法
描述:将机场的一个安检口理解为连接点,多个安检口为切入点,安全检查过程看成是 通知
二 Spring AOP 入门案例
1 业务需求
在项目中定义一个日志切面,通过切面中的通知方法为目标业务对象做日志功能增强。
2 添加AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
说明:基于此依赖spring可以整合AspectJ框架快速完成AOP的基本实现。AspectJ 是一个面向切面的框架,他定义了 AOP 的一些语法,有一个专门的字节码生成器来生成遵守 java 规范的 class 文件
3 切面对象的设计
通过设计切面对象,为目标业务方法做功能增强
3.1 创建自定义注解类型,应用于切入点表达式的定义
package com.jt.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**自定义 注解*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredLog {
String operation() default "";
}
3.2 创建切面对象,用于做日志业务增强
package com.jt.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @Aspect注解描述的类称为切面对象
* 1.切入点(在哪些方法执行时,进行功能扩展-锦上)
* 2.通知(所有的扩展逻辑都会写到通过方法中--添花)
* */
@Aspect
@Component
public class LogAspect {
/**
* 通过@Pointcut注解定义切入点,但切入点的定义需要有一个表达式,写法有多种,比较常用
* 有注解方式的表达式(@annotation(我自己定义的注解))
* 当使用自己写的注解方法进行描述时,这个方法就是切入点,在这个方法上要锦上添花*/
@Pointcut("@annotation(com.jt.aop.RequiredLog)")
public void doLog(){}//这个方法没有意义,主要用于承载切入点
/**
* Around通知,在此通知内部可以定义我们扩展业务逻辑,还可以调用目标执行链
* @param joinPoint 连接点(通知方法与目标方法的连接点对象),ProceedingJoinPoint
* 类型的连接点只能应用在Around通知方法中
* @return
* @throws Throwable
* */
@Around("doLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before:"+System.currentTimeMillis());
Object result = joinPoint.proceed();//执行链(其他切面,目标对象方法)
System.out.println("After:"+System.currentTimeMillis());
return result;
}
}
3.3 时间拦截器,限制访问时间
package com.jt.web.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalTime;
public class TimeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,Object handle){
System.out.println("==preHandler==");
LocalTime now =LocalTime.now();//JDK8中的时间对象
int hour=now.getHour();//获取当前时间对应小时
System.out.println("hour="+hour);
if (hour<=6||hour>=23)
throw new RuntimeException("请在6~23点进行访问");
return true;//false表示请求到此结束,true表示执行链中的下一个对象
}
}
3.4 创建控制层,获取请求调用代理对象并响应
package com.jt.web.controller;
import com.jt.aop.RequiredLog;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/demo/")
public class DemoController {//-->AOP-->Controller-->AOP-->Service-->Dao
@RequiredLog//由此注解描述的方法就是切入点方法
@GetMapping
public String doSayHello(){
System.out.println("==doSayHello==");
String result = "hello spring";
return result;
}
}
3.5 url访问页面,测试aop执行步骤
后台步骤: