文章目录
一、AOP
1.面向切面编程(AOP)
AOP全称Aspect-Orinted Programing(面向切面编程),是一种编程思想,是OOP的延伸和补充,采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行的时候,再将这些提取出来的代码应用到需要执行的地方。
AOP术语
名称 | 作用 |
---|---|
Aspect(切面) | 封装的用于横向插入系统功能(如事务、权限、日志)的类 |
JoinPoint(连接点) | 程序执行的某个阶段点,如方法的调用、异常的抛出等;(在Spring的AOP中,连接点就是方法的调用) |
PointCut(切入点) | 需要处理的连接点 |
Advice(通知/增强) | 定义好的切入点需要执行的代码 |
Target Object(目标对象) | 所有被通知的对象,也称为被增强对象 |
Proxy(代理) | 通知应用到目标对象后被创建的对象 |
Weaving(织入) | 将切面插入到目标对象上,从而生成代理对象的过程 |
按照通知在目标方法中的连接点位置,通知分5种类型:
① 前置通知:在目标方法前实施增强,用于权限管理;
② 后置通知:在目标方法后实施增强,用于关闭流、上传文件、删除临时文件等;(目标方法出现异常后不会执行)
③ 环绕通知:在目标方法前后实施增强,用于日志、事务管理;
④ 异常通知:在目标方法后实施增强,用于处理异常、记录日志;(只有目标方法出现异常时才会执行)
⑤ 最终通知:在目标方法前实施增强,用于释放资源;(无论目标方法出现异常都会执行)
2.AOP底层原理
AOP的底层采用JDK动态代理,实际上动态代理具体有两种:
JDK动态代理
对于使用业务接口的类,Spring默认采用JDK动态代理实现AOP;
① JDK动态代理是通过java.lang.reflect.Proxy类实现的,可以调用Proxy类的静态方法newProxyInstance()来创建代理对象。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces ,InvocationHandler h) //返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
② JDK动态代理实现的需要实现InvocationHandler接口,实现invoke方法
Object invoke(Object proxy, Method method, Object[] args) //处理代理实例上的方法调用并返回结果。
JDK动态代理如下:
//接口TestDao
public interface UserDao {
public void addUser();
public void deleteUser();
}
//接口实现类TestDaoImpl(此时的TestDaoImpl作为目标类将被增强)
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
//切面类MyAspect
public class MyAspect {
public void check(){
System.out.println("模拟检查权限...");
}
public void log(){
System.out.println("模拟记录日志...");
}
}
//代理类JDKProxy(需要实现InvocationHandler )
public class JDKProxy implements InvocationHandler {
//声明目标类接口
private UserDao userDao;
//代理方法
public Object createProxy(UserDao userDao){
this.userDao = userDao;
//1.类加载器
ClassLoader classLoader = JDKProxy.class.getClassLoader();
//2.被代理对象实现的所有接口
Class<?>[] interfaces = userDao.getClass().getInterfaces();
//3.使用代理类进行增强
return Proxy.newProxyInstance(classLoader,interfaces,this);
}
/**
* 所有动态代理类的方法调用,都会交由invoke()方法去处理
* @param proxy 被代理的对象
* @param method 将要执行的方法信息(通过反射获取)
* @param args 执行方法需要的参数
* @return 返回创建的代理类对象
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//声明切面
MyAspect myAspect = new MyAspect();
//前增强
myAspect.check();
//在目标类上调用方法,并传入参数
Object obj = method.invoke(userDao, args);
//后增强
myAspect.log();
//返回创建的代理类对象
return obj;
}
}
//测试类
public class JDKTest {
public static void main(String[] args) {
//创建代理对象
JDKProxy proxy = new JDKProxy();
//创建目标对象
UserDao userDao = new UserDaoImpl();
//从代理对象中获取增强后的目标对象
UserDao userDao1 = (UserDao) proxy.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.deleteUser();
}
}
CGLIB动态代理
JDK动态代理有局限性——使用动态代理的业务类必须实现接口,对于没有实现接口的类就需要使用CGLIB动态代理。
① CGLIB(Code Generation Library)是一个高性能的代码生成包,采用底层的字节码技术,对指定目标类生成一个子类,并对子类进行增强。
② 代理类需要实现MethodInterceptor接口,实现接口中的interceptor()方法。
//目标类(不需要实现接口,只是定义需要增强的方法即可)
public class UserDao {
public void addUser(){
System.out.println("添加用户");
}
public void deleteUser(){
System.out.println("删除用户");
}
}
此处的切面类依旧是上面JDK动态代理中的MyAspect.java
//代理类
//注意:import org.springframework.cglib.proxy.MethodInterceptor
public class CglibProxy implements MethodInterceptor {
//代理方法
public Object createProxy(Object obj){
//创建一个动态类对象
Enhancer enhancer = new Enhancer();
//确定需要增强的类,设置其父类
enhancer.setSuperclass(obj.getClass());
//添加回调函数
enhancer.setCallback((Callback) this);
//返回创建的代理类
return enhancer.create();
}
/**
* @param proxy CGLIB根据指定父类生成的代理对象
* @param method 拦截的方法
* @param args 拦截方法的参数数组
* @param methodProxy 方法的代理对象,用于执行父类的方法
* @return 返回创建的代理类对象
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//创建切面类对象
MyAspect myAspect = new MyAspect();
//前增强
myAspect.check();
//目标方法执行
Object obj = methodProxy.invokeSuper(proxy, args);
//后增强
myAspect.log();
return obj;
}
}
//CGLIB测试类
public class CglibTest {
public static void main(String[] args) {
//创建代理对象
CglibProxy cglibProxy = new CglibProxy();
//创建目标对象
UserDao userDao = new UserDao();
//获取增强后的目标对象
UserDao userDao1 = (UserDao) cglibProxy.createProxy(userDao);
//执行方法
userDao1.addUser();
userDao1.deleteUser();
}
}
二、AOP开发
AspectJ开发
AscpectJ是一个基于java语言的独立于Spring的AOP框架,使用AspectJ实现AOP有两种方式:基于XML的声明式AspectJ、基于注解的声明式AspectJ;
1.切入点表达式
(1)作用:指明对类中的那个方法进行增强处理;
(2)语法格式:execution([权限修饰符][返回值类型][类全路径][方法名称]([参数列表]))
(类全路径=包名+类名)
例1:对com.jd.dao.BookDao类里面的add进行增强:execution(* com.atguigu.dao.BookDao.add(..))
例2:对com.jd.dao.BookDao类里面的所有的方法进行增强:execution(* com.atquiqu.dao.BookDao.*(..))
例3:对com.jd.dao包里面所有类,类里面所有方法进行增强:execution(* com.atauiau.dao.*.*(..))
注解名称 | 描述 |
---|---|
@Aspect | 用于定义一个切面 |
@Pointcut | 用于定义切入点表达式,声明一个返回值为void,且方法体为空的普通方法作为切入点名称 |
@Before | 用于定义前置通知,相当于BeforeAdvice。value属性用于指定切入点表达式 |
@AfterReturning | 用于定义后置通知,相当于AfterReturningAdvice。pointcut/value属性用于指定切入点表达式 |
@Around | 用于定义环绕通知,相当于MethodInterceptor。value属性用于指定切入点表达式 |
@AfterThrowing | 用于定义异常通知来处理程序中的未处理的异常,相当于ThrowAdvice。pointcut/value属性用于指定切入点表达式 |
@After | 用于定义最终通知,无论是否异常,该通知都会执行。value属性用于指定切入点表达式 |
@DeclareParents | 用于定义引介通知 |
1.基于注解的声明式AspectJ
(1)需要导入的jar包
(2)具体实现:
//接口(业务逻辑的接口,aop默认是JDK动态代理(业务逻辑需要实现接口))
public interface UserDao {
public void addUser();
public void deleteUser();
}
//目标类,实现UserDao接口
@Repository("userDao")
public class UserDaoImpl implements UserDao{
@Override
public void addUser() {
//int i = 10/0;//模拟异常
System.out.println("添加用户");
}
@Override
public void deleteUser() {
System.out.println("删除用户");
}
}
//切面类,在此类中编写通知
@Aspect
@Component
public class MyAspect {
//定义切入点表达式
@Pointcut("execution(* com.jd.aspectj.dao.*.*(..))")//此处的配置很关键
public void myPointCut(){}
//前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟检查权限...");
System.out.print("目标类是:"+joinPoint.getTarget());
System.out.println(",被织入增强的目标方法是:"+joinPoint.getSignature().getName());
}
//后置通知
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint){
System.out.print("后置通知:模拟记录日志...");
System.out.println(",被织入增强处理的方法是:"+joinPoint.getSignature().getName());
}
//环绕通知
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
//执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
//异常通知(只有当程序执行过程中出现异常时才会执行)
@AfterThrowing(value = "myPointCut()",throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:"+"出错了"+e.getMessage());
}
//最终通知(无论是否有异常抛出,都会执行)
@After("myPointCut()")
public void myAfter(){
System.out.println("最终通知:模拟方法执行结束后的释放资源...");
}
}
<!--Spring的配置文件applicationContext.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">
<!--开启注解扫描,使注解生效-->
<context:component-scan base-package="com.jd.aspectj"/>
<!--开启Aspectj生成代理对象-->
<aop:aspectj-autoproxy/>
</beans>
//测试方法
@Test
public void annotationAspectjTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao= (UserDao) context.getBean("userDao");
userDao.addUser();
System.out.println("---------------------------------------------------------------------");
userDao.deleteUser();
}
没有异常时的正常通知情况:
模拟UserDaoImpl中出现异常,如下:
同样,可以使用配置类替代配置文件,具体如下:
扩展:有多个切面类(增强类)对同一个方法进行增强,可以设置切面类(增强类)的优先级,在增强类上面添加注解@Order(数字类型值),数字类型值越小优先级越高
@component
@Aspect
@Order(1)
public class YourAspect{...}
2.基于XML的声明式AspectJ
(xml的方式了解即可)
UserDao和UserDaoImpl还是上面的,需要该变的是Spring配置文件、切面类、
//切面类
public class MyAspect2 {
//定义切入点表达式
public void myPointCut(){}
//前置通知
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟检查权限...");
System.out.print("目标类是:"+joinPoint.getTarget());
System.out.println(",被织入增强的目标方法是:"+joinPoint.getSignature().getName());
}
//后置通知
public void myAfterReturning(JoinPoint joinPoint){
System.out.print("后置通知:模拟记录日志...");
System.out.println(",被织入增强处理的方法是:"+joinPoint.getSignature().getName());
}
//环绕通知
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
//执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
//异常通知(只有当程序执行过程中出现异常时才会执行)
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:"+"出错了"+e.getMessage());
}
//最终通知(是否有异常抛出,都会执行)
public void myAfter(){
System.out.println("最终通知:模拟方法执行结束后的释放资源...");
}
}
<!--xml方式实现aop的Spring配置文件-->
<?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">
<!--1.目标类-->
<bean id="userDao" class="com.jd.aspectj.dao.UserDaoImpl"/>
<!--2.切面类-->
<bean id="myAspect2" class="com.jd.aspectj.xml.MyAspect2"/>
<!--3.aop编程-->
<aop:config>
<!--配置切面-->
<aop:aspect ref="myAspect2">
<!--配置切入点,通知最后需要增强哪些方法-->
<aop:pointcut id="myPointCut" expression="execution(* com.jd.aspectj.dao.*.*(..))"/>
<!--前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<aop:after method="myAfterReturning" pointcut-ref="myPointCut"/>
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
//测试方法
@Test
public void xmlAspectjTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao= (UserDao) context.getBean("userDao");
userDao.addUser();
System.out.println("---------------------------------------------------------------------");
userDao.deleteUser();
}
没有异常时的正常通知情况:
模拟UserDaoImpl中出现异常,如下: