讲到AOP,Aspect Orient Program 面向切面编程。)要先从代理模式入手,毕竟spring AOP的底层是动态代理!
所谓代理:个人理解就像是歌星经纪人,为歌星代理各种功能!没有用到代理之前,要扩展功能还要修改源码,有违开闭原则,而且不安全!所以需要用到代理模式。
实现代理模型的三种方式:
1)静态代理
2)动态代理
2.1) JDK动态代理
2.2 )CGLIB动态代理
###静态代理:
优点:在不修改目标类的情况下对目标的方法进行扩展
缺点:
1)一个静态代理类只能代理一个目标类
2)静态代理类的每个方法都需要编写重复的代理逻辑,代码比较冗余
要求:
1)和目标(类)实现同样的接口
2)在静态代理类中传入目标对象实例,以便调用目标对象的方法
3)可以在静态代理类的方法中添加代理逻辑代码
public class LogProxy implements UserService{
//接收目标对象实例
private UserService userService;
//使用构造方法传入目标对象实例
public LogProxy(UserService userService){
this.userService = userService;
}
@Override
public void save() {
System.out.println("before=====save");
//调用目标对象的方法
userService.save();
System.out.println("after=====save");
}
JDK动态代理
######优点:
1)一个工具类可以生成任何目标对象的代理对象(更加通用啦)
2)代理逻辑只需要编写一次,就可以应用到所有目标对象的方法上(代码简化)
#####缺点:
目标对象必须有接口,没有接口生成不了JDK动态代理
查阅API可以看出,newpRroxyInstance 方法是用于创建动态代理对象。
static Object newProxyInstance(
ClassLoader 类加载器
Class[] 目标对象实现的接口列表
InvocationHandler 用于编写 代理对象的代理逻辑代码 的接口)
该方法返回的是代理对象!!!
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* 用于生成JDK动态代理对象的工具类
*/
public class LogProxy {
/**
* 生成JDK动态代理对象的方法
*
* 返回值:生成的JDK动态代理对象
* 参数:target, 传入目标对象
*/
public static Object getProxy(Object target){
/**
* 参数一:类加载器,JDK动态代理的底层使用类加载器来生成的一个动态类的。通常传入当前类的类加载器即可!!!(LogProxy.class.getClassLoader())
* 参数二:目标对象的接口列表(所有接口),通常使用目标对象获取接口列表(target.getClass().getInterfaces())
* 参数三:接口。 该用于编写 代理类的代理逻辑代码。通常我们要提供InvocationHandler接口的实现类(匿名内部类的方式提供)
*/
return Proxy.newProxyInstance(
LogProxy.class.getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
/**
* invoke方法:用于编写 代理类的代理逻辑代码。
* invoke方法在什么时候会被调用?
* 该方法会在调用JDK代理对象的每个方法的时候被执行!!!!!
*
* @param proxy: 生成JDK动态代理对象
* @param method: 目标对象的执行方法的对象
* @param args: 目标对象的方法参数列表
* @return 返回值:目标对象方法执行后的返回结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取目标对象的方法名称
String methodName = method.getName();
System.out.println("before======"+methodName);
//获取方法的参数列表
/* if(args!=null)
System.out.println(Arrays.asList(args));*/
//调用目标对象的方法
/**
* 参数一:执行的对象(必须传入目标对象,不能传入代理对象,否则会死循环)
* 参数二:方法的参数列表
*/
Object result = method.invoke(target,args);
System.out.println("after======"+methodName);
return result;
}
}
);
}
}
###CGLIB动态代理
优点: 解决目标对象没有实现接口,就不能使用jdk接口代理的问题
记得cglib依赖(spring-core依赖)
编写生成Cglib代理对象的工具类
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 用于生成Cglib代理对象的工具类
*/
public class LogProxy {
/**
* 生成Cglib代理对象
* 返回值:生成的Cglib子类代理对象
* 参数:目标对象(目标对象没有接口)
*/
public static Object getProxy(Object target){
/**
* 方法返回值:生成的Cglib子类代理对象
* 参数一:目标对象的类型(target.getClass()) (其实目标对象的类型就是Cglib代理对象 的 父类)
* 参数二:MethodInterceptor接口,用于编写 代理对象的代理逻辑代码。通常提供MethodInterceptor接口的匿名内部即可
*/
return Enhancer.create(
target.getClass(),
new MethodInterceptor() {
/**
* intercept方法:在调用代理对象的每个方法的时候会执行
* @param proxy: 生成的代理对象
* @param method: 目标对象的方法对象
* @param args: 目标对象的方法参数列表
* @param methodProxy: 代理对象的方法对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//获取目标对象的方法名称
String methodName = method.getName();
System.out.println("before======"+methodName);
/**
* 调用目标对象的方法
*/
//方式一:直接使用目标对象 调用 目标对象的方法
Object result = method.invoke(target,args);
//方式二:使用代理类(子类)调用 目标对象(父类)的方法
//invokeSuper: 调用父类的方法
//Object result = methodProxy.invokeSuper(proxy,args);
System.out.println("after====="+methodName);
return result;
}
}
);
}
}
编写测试类
/**
* 测试Cglib动态代理
*
*/
public class Demo {
public static void main(String[] args) {
//1.创建目标对象
UserServiceImpl userService = new UserServiceImpl();
//2.创建Cglib代理
UserServiceImpl proxy = (UserServiceImpl)LogProxy.getProxy(userService);
//3.调用代理的方法
proxy.save("eric",999);
proxy.update();
proxy.delete();
}
}
#####AOP编程
Spring的AOP编程,底层就是用的Java动态代理模式!
1)如果目标对象实现了接口,可以使用JDK动态代理(推荐)或者Cglib动态代理
2)如果目标对象没有接口,只能使用Cglib动态代理
基本概念:连接点(JointPoint),切入点(Pointcut),通知(Advice),切面(Apsect)目标对象(Target),代理(Proxy)
Joinpoint(连接点):
在 spring 中,连接点指的都是方法(指的是那些要被增强功能的候选方法),spring 只支持方法类型
的连接点。
Pointcut(切入点):
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。 @Pointcut(“execution(* com.huihui.*ServiceImpl.save(…))”) 之拦截了 save 方法。 save 就是切入点
Advice(通知/增强):
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。
Weaving(织入):
织入指的是把增强用于目标对象,创建代理对象的过程。spring 采用动态代理织入,AspectJ 采用编译期织入和类装载期织入。
Target(目标对象):
被代理的对象。比如动态代理案例中的演员。
Proxy(代理):
一个类被 AOP 织入增强后,即产生一个结果代理类。比如动态代理案例中的经纪人。
Aspect(切面): 切面=切入点+通知
基于XML的AOP配置:
创建项目先导相关包:
<!-- spring-aop sprirng自身aop编程包 -->
<!-- spring依赖的第三方工具包(提供切入点表达式语法) -->
1.创建UserService接口和实现
2.创建切面类(需要扩展的功能)
3.配置切面类
4.测试
bean.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1.创建目标对象 -->
<bean id="userService" class="com.huihui.service.impl.UserServiceImpl"/>
<!-- 2.创建切面对象 -->
<bean id="logAspect" class="com.huihui.aop.LogAspect"/>
<!-- 3.让切面对象对象目标对象进行切入(切面配置) -->
<aop:config>
<!-- 切面配置 = 通知(advice)+切入点(pointcut)-->
<!--
ref: 引用切面类对象
-->
<aop:aspect ref="logAspect">
<!-- 定义切入点 -->
<!--
id: 定义切入点的别名
expression: 切入点表达式(用于定义需要切入的方法)
-->
<aop:pointcut id="pt" expression="execution(* com.huihui.service.impl.UserServiceImpl.*(..))"/>
<!-- 定义通知 -->
<!--
method: 使用切面类的哪个方法作为通知方法
pointcut-ref: 关联切入点
-->
<aop:before method="writeLog" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
####Aop编程:通知类型
前置通知:在执行目标对象方法之前执行
后置通知:在执行目标对象方法之后执行 (异常不执行)
异常通知:在执行目标对象方法发生异常时候执行
最终通知:在执行完目标对象方法后始终执行的方法就是最终通知(异常也执行)
环绕通知:
try{
*前置通知
执行目标方法
*后置通知
}catch(Exception e){
*异常通知
}finally{
- 最终通知
}
测试类:
- 在LogAspect添加环绕通知方法
- bean.xml配置各种通知
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.huihui.service.impl.UserServiceImpl"/>
<bean id="logAspect" class="com.huihui.aop.LogAspect"/>
<aop:config>
<aop:aspect ref="logAspect">
<aop:pointcut id="pt" expression="execution(* com.huihui.service.impl.UserServiceImpl.*(..))"/>
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pt"/>
<!-- 后置通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
<!-- 最终通知 -->
<aop:after method="after" pointcut-ref="pt"/>
<!-- 环绕通知 -->
<!-- <aop:around="around" pointcut-ref="pt"/> -->
</aop:aspect>
</aop:config>
</beans>
亦可通过注解与xml的形式进行AOP:
1.创建模块在LogAspect上添加注解
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* 日志切面类
*/
@Component // 代替 <bean id="logAspect" class="com.huihui.aop.LogAspect"/>
@Aspect // 代替 <aop:aspect ref="logAspect">...
public class LogAspect {
//切入点方法
//注意:切入点的id就是该方法的名称 (public void pt())
//代替:<aop:pointcut id="pt" expression="execution(* com.huihui.service.impl.UserServiceImpl.*(..))"/>
@Pointcut(value = "execution(* com.*.service.impl.UserServiceImpl.*(..))")
public void pt(){ }
/**
* 前置通知
*/
@Before("pt()")
public void before(){
System.out.println("前置通知=======");
}
/**
* 后置通知
*/
@AfterReturning("pt()")
public void afterReturning(){
System.out.println("后置通知=======");
}
/**
* 异常通知
*/
@AfterThrowing("pt()")
public void afterThrowing(){
System.out.println("异常通知======");
}
/**
* 最终通知
*/
@After("pt()")
public void after(){
System.out.println("最终通知=======");
}
/**
* 环绕通知
*/
@Around("pt()")
public void around(ProceedingJoinPoint jp){
//前置
System.out.println("前置通知");
//调用目标对象的方法
try {
jp.proceed();
System.out.println("后置通知");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常通知");
}finally {
System.out.println("最终通知");
}
}
}
2.修改bean.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userService" class="com.huihui.service.impl.UserServiceImpl"/>
<!-- IOC注解扫描 : 扫描的是@Component, @Service,@Controller, @Repistory-->
<context:component-scan base-package="com.huihui"/>
<!-- AOP注解扫描 : 扫描@Aspect @Pointcut @Before.... 默认从整个项目扫描-->
<aop:aspectj-autoproxy/>
</beans>
3.测试
XML方式配置
aop:config
aop:aspect
aop:pointcut
aop:before / aop:after-returning…
注解:
@Aspect
@Pointcut
@Before @AfterRetuning…
…
本文仅作个人总结!