Java程序运行在JVM中的特征
public class Demo01 {
public static void main(String[] args) {
}
}
当我们执行Demo01 的时候,JVM会先创建一个主线程,这个主线程以main()方法作为入口开始执行你的代码。每一个线程再内存中都有属于自己的栈(Stack),记录整个程序的执行流程。栈中的每一个元素被称为栈帧(Stack Frame ),栈帧表示一个方法的调用,记录着方法的调用信息。我们在实际代码中调用一个方法时,在内存中对应着一个栈帧的进栈和出栈。
- 在某个特定的时间点,一个Main线程内的栈会呈现如下图所示的情况:
从线程的角度来看,JVM处理Java程序的基本单位是方法调用(宏观角度)。但实际中,JVM执行的基本单位的指令是汇编语言性质的机器字节码。
Java程序执行流
从虚拟机线程栈的角度考虑Java程序执行,Java中程序的执行过程其实就是方法的调用过程。我们按照方法的执行顺序,将方法调用排成一个串,这样就构成了Java程序流。
如上图所示,我们将基于时间序列,把方法的调用排成一条竖线。其中每一个方法的调用可以看成Java执行流中的一个节点。该节点在AOP中的专业术语为连接点(Join Point)。一个Java程序在执行过程中,就是若干个连接点连接起来依次执行的过程。
面向切面的编程AOP
AOP编程思路和机制
我们通常接触的面向对象的程序,代码都是按照时间列纵向展开的(Conntroller层–>Service层–>Dao层):以方法调用作为基本执行单位展开。将方法的调用作为一个连接点,那么由连接点串起来的程序执行流程就是整个程序的执行过程。
AOP则是从另一个角度来考虑整个程序,AOP把每一个方法的调用即连接点作为编程的入口,针对方法的调用进行编程。 从执行的逻辑上来看,相当于在之前纵向的按照时间轴执行的程序横向切入。相当于将之前的程序横向切割成若干的面,即Aspect.每个面被称为切面。
切入点
切面本质上是每一个方法调用,选择切面的过程实际上就是选择方法的过程。那么,被选择的切面(Aspect)在AOP术语里被称为切入点(Point Cut). 切入点实际上也是从所有的连接点(Join point)挑选自己感兴趣的连接点的过程。
AOP实现的机制(代理)
- 在程序中加入Proxy对象后,Java执行流示意图:
从上图可以看出,我们要调用某一实例对象的方法时,都会经过这个实例对象相对应的代理对象, 即执行的控制权先交给代理对象。
代理模式
代理模式属于Java代码中经常用到的、也是比较重要的设计模式。代理模式可以为某些对象除了实现本身的功能外,提供一些额外的功能,大致作用如下图所示:
加入了代理模式的Java程序执行流,使得所有的方法调用都经过了代理对象。对于Spring AOP框架而言,它负责控制着正个容器内部的代理对象。当我们调用了某一个实例对象的任何一个非final的public方法时,整个Spring框架都会知晓。
此时的SpringAOP框架在某种程度上扮演着一个上帝的角色:它知道你在这个框架内所做的任何操作,你对每一个实例对象的非final的public方法调用都可以被框架察觉到!
所有对象的非final的public方法的调用,都会经过Spring的代理层,Spring代理层知道每一个方法调用的详细信息。Spring代理层统一管理这些proxy,控制着这些proxy的行为
Spring AOP的工作原理
Spring通过切入点进行编程
AOP对这个方法调用的编程,就是针对这三个阶段插入自己的业务代码。
- 在调用真正对象的方法之前:
- prox会告诉SpringAOP自己将调用哪个类的哪个方法,在调用之前SpringAOP有没有要做的操作
- Spring AOP这时根据proxy提供的类名和方法签名,然后拿这些信息尝试匹配是否在其感兴趣的切入点内,如果在感兴趣的切入点内,Spring AOP会返回 MethodBeforeAdvice处理建议,告诉proxy应该执行的操作
- 在调用真正对象的方法过程中,如果抛出了异常:
- proxy告诉Spring AOP: “我调用某个类的某个方法过程中抛出了异常,你有什么处理建议?”
- Spring AOP根据proxy提供的类型和方法签名,确定了在其感兴趣的切入点内,则返回相应的处理建议ThrowsAdvice,告诉proxy这个时期应该采取的操作。
- .在调用真正对象的方法后,返回了结果了:
- proxy告诉Spring AOP:“我调用某个类的某个方法结束了,并返回了结果你现在有什么处理建议?”;
- Spring AOP 根据proxy提供的类型名和方法签名,确定了在其感兴趣的切入点内,则返回AfterReturingAdivce处理建议,proxy得到这个处理建议,然后执行建议;
上述的示意图中已经明确表明了Spring AOP应该做什么样的工作:根据proxy提供的特定类的特定方法执行的特定时期阶段给出相应的处理建议。要完成该工作,Spring AOP应该实现:
1.确定自己对什么类的什么方法感兴趣? -----即确定 AOP的切入点(Point Cut),这个可以通过切入点(Point Cut)表达式来完成;
- 对应的的类的方法的执行特定时期给出什么处理建议?------这个需要Spring AOP提供相应的建议 ,即我们常说的Advice。
AOP(Aspect Oriented Programming)
底层实现:代理模式
什么是AOP
在不影响我们原来的业务类的情况下,实现动态的增强
- 面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 狂神说解释
Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义
- 连接点(JointPoint):与切入点匹配的执行点。
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
即 Aop 在 不改变原有代码的情况下 , 去增加新的功能
使用Spring实现Aop
方式一: 通过 Spring API 实现
- 编写业务接口和实现类
//业务接口
public interface UserService {
void add();
void delete();
void update();
void select();
}
//实现类
@Service
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加一个用户");
}
@Override
public void delete() {
System.out.println("删除一个用户");
}
@Override
public void update() {
System.out.println("更新一个用户");
}
@Override
public void select() {
System.out.println("查找一个用户");
}
}
- 编写增强类
//前置增强
@Component
public class BeforeLog implements MethodBeforeAdvice {
/**
*
* @param method //要执行的目标对象的方法
* @param objects //被调用的方法的参数
* @param o //目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"方法被执行了");
}
}
//后置增强
@Component
public class AfterLog implements AfterReturningAdvice {
/**
*
* @param returnValue//返回值
* @param method//被调用的方法
* @param args//被调用的方法的对象的参数
* @param target//被调用的目标对象
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target ) throws Throwable {
System.out.println("执行了"+target.getClass().getName()+"的"+method.getName()+"方法,返回值"+returnValue);
}
}
- spring的文件中注册 , 并实现aop切入实现 , 注意导入约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.phx"></context:component-scan>
<!--方式一:使用原生的SpringAPI接口-->
<!--aop的配置:需要导入aop的约束-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.phx.service.UserServiceImpl.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"></aop:advisor>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
</beans>
- 测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context= new ClassPathXmlApplicationContext("beans.xml");
//动态代理代理的是接口
UserService userService=context.getBean("userServiceImpl", UserService.class);
userService.add();
userService.delete();
userService.update();
userService.select();
}
}
Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .
方式二:自定义类来实现Aop
- 自定义切入类
@Component
//自定义切入类
public class DiyPointcut {
public void before(){
System.out.println("-----方法执行前执行-----");
}
public void after(){
System.out.println("-----方法执行后执行-----");
}
}
- 实现类
//实现类
@Service
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("添加一个用户");
}
@Override
public void delete() {
System.out.println("删除一个用户");
}
@Override
public void update() {
System.out.println("更新一个用户");
}
@Override
public void select() {
System.out.println("查找一个用户");
}
}
- 去spring中配置
<!--第二种方式自定义实现-->
<!--aop的配置-->
<aop:config>
<!--自定义切面 ref:要引用的类 -->
<aop:aspect ref="diyPointcut" >
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.phx.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="pointcut" method="before"></aop:before>
<aop:after method="after" pointcut-ref="pointcut"></aop:after>
</aop:aspect>
</aop:config>
<aop:aspectj-autoproxy proxy-target-class="true"/>
- 测试
public class MyTest02 {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("beans2.xml");
UserServiceImpl userServiceImpl = context.getBean("userServiceImpl", UserServiceImpl.class);
userServiceImpl.add();
}
}
方式三:使用注解实现
- 编写一个注解实现的增强类
@Component
@Aspect//标志这个类是一个切面
public class AnnotationPointcut {
@Before("execution(* com.phx.service.UserServiceImpl.* (..))")
public void before(){
System.out.println("-----方法执行之前执行");
}
@After("execution(* com.phx.service.UserServiceImpl.* (..))")
public void after(){
System.out.println("-----方法执行之后执行");
}
@Around("execution(* com.phx.service.UserServiceImpl.*(..))")
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
- 配置文件
<!--第三种方式:注解实现-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--aop:aspectj-autoproxy:说明-->
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
- 测试类
public class MyTest03 {
public static void main(String[] args) {
ApplicationContext context=new ClassPathXmlApplicationContext("beans3.xml");
UserServiceImpl userServiceImpl = context.getBean("userServiceImpl", UserServiceImpl.class);
userServiceImpl.add();
}
}