AOP全称为Aspect Oriented Programming的缩写,也意为:面向切面编程,通过预编译手段和运行期动态代理实现程序功能的统一维护技术。采用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。AOP采取横向抽取机制,取代了纵向继承体系重复的代码。
AOP典型的应用场景有:事务管理,性能监控、安全监测、日志等。
AOP底层采用代理的机制进行实现的。代理的方式有两种:jdk动态代理、cglib代理(字节码增强)
1.target:目标类。需要被代理的类,也是需要通过代理增强的类。
2.JoinPoint:连接点。连接点就是指需要增强类(target)可能拦截到的方法。如同target中。
3.PointCut:切入点。已经被增强的连接点(在众多连接点中已经被增强的方法,就叫切入点。切入点是连接点的子集)。
4.advice:通知(增强)。增强的代码(在切入点中增强所需的代码就是通知,例如:事务管理就是在切入点方法中前后开启事务和提交事务)。
5.Weaving:织入。把增强代码(advice)应用到目标类(target)来创建新的代理对象(proxy)的过程就叫做织入。
6.proxy:代理类。
7.Aspect:切面。是切入点(PointCut)和通知(advice)的结合。
采用jdk动态代理需要的条件是:需要代理的类是采用接口+实现类的方式。(因为代理是采用目标类所实现的接口来生成目标类的代理)。
第一步:准备目标类(target):
接口:
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
实现类:
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("addUser");
}
@Override
public void updateUser() {
System.out.println("updateUser");
}
@Override
public void deleteUser() {
System.out.println("deleteUser");
}
}
第二步:准备切面类(也是增强代码所在的类advice)
切面类代码:
public class MyAspect {
public void before() {
System.out.println("前方法");
}
public void after() {
System.out.println("后方法 ");
};
}
第三步:创建生成代理类工厂实现对目标类的代理
创建代理类通过Proxy类中的静态方法newProxyInstance(类加载器,代理类实现的接口,InvacationHandler处理类)
public class MyBeanFactory {
//应为这个类是生成代理类,所以就通过这个方法产生代理类
public static UserService createService() {
//首先需要目标类
UserService userService = new UserServiceImpl();
//切面类
MyAspect myAspect = new MyAspect();
/* 3 代理类:将目标类(切入点)和 切面类(通知) 结合 --> 切面
* Proxy.newProxyInstance
* 参数1:loader ,类加载器,动态代理类 运行时创建,任何类都需要类加载器将其加载到内存。
* 一般情况:当前类.class.getClassLoader();
* 目标类实例.getClass().get...
* 参数2:Class[] interfaces 代理类需要实现的所有接口
* 方式1:目标类实例.getClass().getInterfaces() ;注意:只能获得自己接口,不能获得父元素接口
* 方式2:new Class[]{UserService.class}
* 例如:jdbc 驱动 --> DriverManager 获得接口 Connection
* 参数3:InvocationHandler 处理类,接口,必须进行实现类,一般采用匿名内部
* 提供 invoke 方法,代理类的每一个方法执行时,都将调用一次invoke
* 参数31:Object proxy :代理对象
* 参数32:Method method : 代理对象当前执行的方法的描述对象(反射)
* 执行方法名:method.getName()
* 执行方法:method.invoke(对象,实际参数)
* 参数33:Object[] args :方法实际参数
*
*/
UserService obj = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader()
, userService.getClass().getInterfaces()
, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//执行前
myAspect.before();
//执行目标类的方法
Object obj = method.invoke(userService, args);
//执行后
myAspect.after();
return obj;
}
} );
return obj;
}
}
使用cglib字节码增强方式实现代理,需要导入相关的jar包(cglib.jar和asm.jar),但是一般Spring的核心包总已经提供了对cglib的支持。
使用cglib字节码生成代理类不需要实现目标类的接口,而是通过目标类直接生成代理类(cglib生成代理类是采用继承的方式 实现代理)
第一步:准备目标类(target)
目标类代码:
public class UserServiceImpl{
public void addUser() {
System.out.println("addUser");
}
public void updateUser() {
System.out.println("updateUser");
}
public void deleteUser() {
System.out.println("deleteUser");
}
}
第二步:准备切面类(advice)
切面类代码:
public class MyAspect {
public void before() {
System.out.println("前方法");
}
public void after() {
System.out.println("后方法 ");
};
}
第三步:创建自定义工厂对目标类进行代理
使用cglib创建代理采用的核心类是Enhancer,通过Enhancer类中create方法创建目标类的代理。但是在执行create方法之前需要确定代理目标类(也就是代理类的父类),确定父类是通过Enhancer类中的setSupperClass(父类的字节码)。也需要对Enhancer设置回调函数,也就是调用setCallback(Callback)。
代码:
public class MyBeanFactory {
//应为这个类是生成代理类,所以就通过这个方法产生代理类
public static UserServiceImpl createService() {
//首先需要目标类
UserServiceImpl userService = new UserServiceImpl();
//切面类
MyAspect myAspect = new MyAspect();
//代理类: 采用cglib,底层创建目标类的子类
//核心类
Enhancer enhancer = new Enhancer();
//确定父类
enhancer.setSuperclass(userService.getClass());
/**
* 设置回调函数:MethodInterceptor接口 等效于jdk中 InvacationHandler接口
* intercept()方法等效于jdk 的invoke()方法
* 参数1、参数2、参数3和invoke方法一样。
* 参数4调用代理类父类的目标方法
*/
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//执行前方法
myAspect.before();
//执行目标方法
//Object object = method.invoke(userService, args);
//methodProxy 执行代理类父类的目标方法相当于执行userServiceImpl中的目标方法
//Object object = methodProxy.invokeSuper(proxy, args);
Object object = methodProxy.invoke(userService, args);
//执行后方法
myAspect.after();
return object;
}
});
//创建代理类
UserServiceImpl userServiceProxy = (UserServiceImpl) enhancer.create();
return userServiceProxy;
}
}
1.AOP联盟定义的通知类型在org.aopalliance.aop.Advice中。
2.Spring按照通知(advice)在目标方法的前后分为五类:
前置通知org.springframework.aop.MethodBeforeAdvice:在目标方法执行前进行增强。
后置通知org.springframework.aop.AfterReturningAdvice:在目标方法执行后进行增强。
环绕通知org.aopalliance.intercept.MethodInterceptor:在目标方法执行前后进行增强,但是必须手动执行目标方法。
异常抛出通知org.springframework.aop.ThrowsAdvice:在方法执行过程中抛出异常后进行增强。
引介通知org.springframework.aop.IntroductionInterceptor:在目标类中添加一些新的属性和方法时进行增强。
需要导入aop(org.aopalliance.jar)规范和Spring对规范的实现(spring-aop.jar)。
目标类所实现的接口:
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
目标类:
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("addUser");
}
@Override
public void updateUser() {
System.out.println("updateUser");
}
@Override
public void deleteUser() {
System.out.println("deleteUser");
}
}
第二步:准备切面类(advice)
这里用我们经常用到的环绕通知进行演示。
使用aop联盟方式进行aop编程,切面类需要按照aop联盟规范进行定义,使用环绕通知,advice类就需要实现接口MethodInterceptor。
代码:
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
//执行前方法
System.out.println("前方法");
//手动执行目标方法
Object object = mi.proceed();
//执行后方法
System.out.println("后方法");
return object;
};
}
第三步:在Spring配置文件中进行配置
Spring配置文件配置,首先需要向配置文件中配置目标类和切面类bean,然后再通过ProxyFactoryBean来对目标类进行自动生成代理(也就是对目标类进行加强)。由于是通过Spring容器来自动对目标类进行代理,所以需要给ProxyFactoryBean注入目标类所实现的接口(通过接口可以通过jdk方式生成代理,如果没有配置接口,就会采用cglib方式进行代理。这种代理方式也是Spring的默认方式),也需要注入目标类(不管是jdk代理还是cglib代理都需要目标类),同时也需要注入切面类。如果不管是否有注入接口,都需要采用cglib方式进行代理,就给ProxyFactoryBean类中注入optimize,并且设置为true。
配置文件如下:
这种方式也是通过Spring提供的一个特殊的Bean来自动对目标类进行代理或者增强。所以我们使用的时候是直接使用的代理类,而不是使用目标类。
目标类接口:
public interface UserService {
public void addUser();
public String updateUser();
public void deleteUser();
}
目标类:
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("addUser");
//int a = 1/0;
}
@Override
public String updateUser() {
System.out.println("updateUser");
return "超哥";
}
@Override
public void deleteUser() {
System.out.println("deleteUser");
}
}
第二步:准备切面类(advice)
这里也是用环绕通知演示:
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
//执行前方法
System.out.println("前方法");
//手动执行目标方法
Object object = mi.proceed();
//执行后方法
System.out.println("后方法");
return object;
};
}
第三步:在Spring配置文件中进行配置
在配置前需要给配置文件中加入aop的头信息。
然后进行后面的配置:也是先配置目标类和切面类bean
最后进行aop编程:
这样在使用过程中就直接使用目标类就可以了,因为已经对目标类进行了加强。(其实采用这种方式进行增强原理是:在执行目标方法前通过了后处理bean(BeanPostProcessor)调用来对目标类进行代理,并返回。这样在执行目标类中的方法,而不是执行的是目标类中的方法,而是执行的是代理类中的方法)。
AspectJ是一个基于Java语言的AOP框架。
Spring在2.0的时候提供了对AspectJ的切入点(PointCut)表达式的支持(切入点表达式就是从target(或者是说JoinPoint)中去选出需要增强的方法)
通过execution()来描述方法,也就是从target中(或者说是JoinPoint中)选出需要增强的方法。至于如何选出就是在execution()中写入选择的表达式。
execution()语法介绍:execution(需要筛选方法的修饰符 筛选方法的返回值 包.类.方法名(方法参数) throws 异常)。
修饰符:一般可以省略。
public 公共方法(private protect.....)
* 任意修饰符
返回值:返回值不能进行省略。
void 返回值为空
String 返回值为字符串(其它省略)
* 任意返回值
包:可以省略,但是一般不省略。
com.test.a 固定包
com.test.a.*.service a包下的任意子包下的service包
com.test.a.. a包下的所有子包
类:可以省略,但是一般不省略。
UserServiceImpl 指定类
*Impl 以Impl结尾的类
User* 以User开头的类
* 任意类
方法名:不能省略。
addUser 固定方法
add* 以add开头的方法
*Delete 以Delete结尾的方法
* 任意方法
参数:不能省略。
() 无参数
(int) 一个int参数
(int,int) 两个int参数
(..) 参数任意
throws,可以省略。一般我们也不写。
before:前置通知(应用:各种校验)
在方法执行前执行,如果通知抛出异常,阻止方法运行
afterReturning:后置通知(应用:常规数据处理)
方法正常返回后执行,如果方法中抛出异常,通知无法执行
必须在方法执行后才执行,所以可以获得方法的返回值。
around:环绕通知(应用:十分强大,可以做任何事情)
方法执行前后分别执行,可以阻止方法的执行
必须手动执行目标方法
afterThrowing:抛出异常通知(应用:包装异常信息)
方法抛出异常后执行,如果方法没有抛出异常,无法执行
after:最终通知(应用:清理现场)
方法执行完毕后执行,无论方法中是否出现异常
导入的jar主要有四个:aop联盟(aopallicance.jar),Spring对AOP联盟的支持(Spring-aop.jar),AspectJ(aspectJ.weaver.jar),Spring对AspectJ的支持(Spring-aspectJ.jar)
目标类所实现的接口:
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
目标类:
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("addUser");
}
@Override
public void updateUser() {
System.out.println("updateUser");
}
@Override
public void deleteUser() {
System.out.println("deleteUser");
}
}
第二步:准备切面类
切面类不需要向AOP联盟那样必须实现他的规范,而是采用AspectJ提供通知名任意(方法名任意)
代码:
public class MyAspect {
//JoinPoint 用于描述连接点(目标方法),可以获得目标方法名等。
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知" + joinPoint.getSignature().getModifiers());
}
//后置通知
// 通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object ret){
// 参数1:连接点描述
// 参数2:类型Object,参数名 returning="ret" 配置的
public void myAfterReturning(JoinPoint joinPoint,Object ret) {
System.out.println("后置通知"+joinPoint.getSignature().getName()+",---->"+ret);
}
//环绕通知
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
//执行前方法
System.out.println("前");
//需要手动执行目标方法
Object obj =joinPoint.proceed();
//执行后方法
System.out.println("后");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e) {
System.out.println("抛出异常通知:"+e.getMessage());
}
//最终通知
public void myAfter() {
System.out.println("最终通知");
}
}
第三步:Spring配置文件进行配置
使用注解的方式实现AOP编程,首先需要对Spring配置文件添加context头信息
然后打开Spring的注解扫描:
接下来是让AspectJ注解生效:
整个Spring配置文件代码如下:
第二步:准备目标类(target)
目标类所实现的接口:
public interface UserService {
public void addUser();
public void updateUser();
public void deleteUser();
}
目标类:
@Service("userServiceId")
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("addUser");
}
@Override
public void updateUser() {
System.out.println("updateUser");
//int a = 1/0;
}
@Override
public void deleteUser() {
System.out.println("deleteUser");
}
}
第三步:准备切面类并直接在切面类添加注解进行AOP编程
在切面类上添加@Aspect注解是指该类为切面类。
在切面类的方法中分别添加以下注解:
(在value中对AspectJ表达式进行引用:首先需要有可引用的pointcut。如何准备pointcut呢?
在切面类中任意写一个无返回值无参数的方法,并在方法前面加上@Pointcut(里面写表达式)注解。
后面引用就直接写该方法。
)
@Before(value="aspectJ表达式或者aspectJ表达式的引用"):该方法将作为增强代码(advice)织入(weaving)到目标类中(target)。
代码如下:
@Component
@Aspect
public class MyAspect {
//注解方式生成切入点引用
@Pointcut(value="execution(* com.test.f_proxy_by_AspectJAnnotation.UserServiceImpl.*(..))")
private void myPointCut() {
}
//添加前置通知注解:value属性里面写切入点表达式或者切入点引用
//@Before("execution(* com.test.f_proxy_by_AspectJAnnotation.UserServiceImpl.*(..))")
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知" + joinPoint.getSignature().getModifiers());
}
//添加后置通知
//@AfterReturning(value="myPointCut()",returning="ret")
public void myAfterReturning(JoinPoint joinPoint,Object ret) {
System.out.println("后置通知"+joinPoint.getSignature().getName()+",---->"+ret);
}
//添加环绕通知
//@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
//执行前方法
System.out.println("前");
//需要手动执行目标方法
Object obj =joinPoint.proceed();
//执行后方法
System.out.println("后");
return obj;
}
//添加抛出异常通知
//@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e) {
System.out.println("抛出异常通知:"+e.getMessage());
}
//添加最后通知
@After("execution(* com.test.f_proxy_by_AspectJAnnotation.UserServiceImpl.*(..))")
public void myAfter() {
System.out.println("最终通知");
}
}