AOP 编程
- Spring AOP
- JDK 动态代理
- CGLib 动态代理
Spring AOP 相关概念
切面(Aspect)
抽离出来的通用功能模块(包含逻辑 +
植入规则),通常是指封装的、用于横向插入系统的功能类
连接点(Joinpoint)
程序执行的某个特定点,如方法调用、异常抛出等
切入点(Pointcut)
具体插入切面的位置(筛选后的连接点)
通知(Advice)
在连接点处执行的代码,包括前置通知、后置通知、环绕通知等,即切面的具体执行逻辑(何时、如何执行)
目标对象(Target)
被切面所织入的对象,即业务逻辑所在的对象
织入(Weaving)
将切面代码插入到目标对象上,从而生成代理对象的过程
代理(Proxy)
将通知应用到目标对象后,动态生成的通知对象,就称为代理
引介 (Introduction)
是一种特殊的通知,可以在不修改目标类的情况下为目标类添加新的方法或属性
JDK 动态代理
创建 UserDao 的接口和实现类
UserDao.java1 2 3 4 5 6 7
| package com.itheima.demo01;
public interface UserDao { public void addUser();
public void deleteUser(); }
|
UserDaoImpl.java1 2 3 4 5 6 7 8 9 10 11 12
| package com.itheima.demo01;
public class UserDaoImpl implements UserDao { public void addUser() { System.out.println("添加用户"); }
public void deleteUser() { System.out.println("删除用户"); } }
|
创建切面类
MyAspect.java1 2 3 4 5 6 7 8 9 10 11 12
| package com.itheima.demo01;
public class MyAspect { public void check_Permissions() { System.out.println("模拟检查权限..."); }
public void log() { System.out.println("模拟记录日志..."); } }
|
创建代理类
MyProxy.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.itheima.demo01;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public class MyProxy implements InvocationHandler { private UserDao userDao;
public Object createProxy(UserDao userDao) { this.userDao = userDao; ClassLoader classLoader = MyProxy.class.getClassLoader(); Class[] classes = userDao.getClass().getInterfaces(); return Proxy.newProxyInstance(classLoader, classes, this); }
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MyAspect myAspect = new MyAspect(); myAspect.check_Permissions(); Object obj = method.invoke(userDao, args); myAspect.log(); return obj; } }
|
这里 MyProxy 类实现了 InvocationHandler
接口,重写了 invoke
方法,在方法中实现了前增强和后增强的逻辑。通过 createProxy
方法创建代理对象,其中 Proxy.newProxyInstance 接收 3
个参数,分别为类加载器、被代理对象实现的接口和
InvocationHandler 实例。
创建测试类
JDKTest.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.itheima.demo01;
public class JDKTest { public static void main(String[] args) { MyProxy jdkProxy = new MyProxy(); UserDao userDao = new UserDaoImpl(); UserDao userDao1 = (UserDao) jdkProxy.createProxy(userDao); userDao1.addUser(); userDao1.deleteUser(); } }
|
CGLib(Code Generation
Library)动态代理
JDK 动态代理只能对接口进行代理,而 CGLib
动态代理可以对类进行代理。CGLib
通过继承目标类并重写方法来实现代理功能。
编写类
UserDao.java1 2 3 4 5 6 7 8 9 10 11 12
| package com.itheima.demo02;
public class UserDao { public void addUser() { System.out.println("添加用户"); }
public void deleteUser() { System.out.println("删除用户"); } }
|
编写 CGLib 代理类
CglibProxy.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.itheima.demo02;
import com.itheima.demo01.MyAspect; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor { public Object createProxy(Object target) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); return enhancer.create(); }
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { MyAspect myAspect = new MyAspect(); myAspect.check_Permissions(); Object obj = methodProxy.invokeSuper(proxy, args); myAspect.log(); return obj; } }
|
这里用 methodProxy.invokeSuper(proxy, args)
来调用父类的方法,而不是使用反射的 method.invoke,因为
CGLib
生成的代理类是目标类的子类,如果使用反射调用方法,会导致递归调用。
创建测试类
CglibTest.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.itheima.demo02;
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(); } }
|
基于 XML 的 Spring AOP 配置
没错,上面两节和用 Spring 没什么关系。。。
需要引入 aspectjrt 包和 aspectjweaver
包的依赖:
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.1</version> </dependency>
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
|
Spring AOP 中的代理对象是基于 IoC 容器自动生成,无需开发者关注
创建接口和实现类
UserDao.java1 2 3 4 5 6 7 8 9 10 11
| package com.itheima.demo03;
public interface UserDao { public void insert();
public void delete();
public void update();
public void select(); }
|
UserDaoImpl.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.itheima.demo03;
public class UserDaoImpl implements UserDao { public void insert() { System.out.println("添加用户信息"); }
public void delete() { System.out.println("删除用户信息"); }
public void update() { System.out.println("更新用户信息"); }
public void select() { System.out.println("查询用户信息"); } }
|
创建切面类
XmlAdvice.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package com.itheima.demo03;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
public class XmlAdvice { public void before(JoinPoint joinPoint) { System.out.print("这是前置通知!"); System.out.print("目标类是:" + joinPoint.getTarget()); System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName()); }
public void afterReturning(JoinPoint joinPoint) { System.out.print("这是返回通知(方法不出现异常时调用)!"); System.out.println("被织入增强处理的目标方法为:" + joinPoint.getSignature().getName()); }
public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("这是环绕通知之前的部分!"); Object object = point.proceed(); System.out.println("这是环绕通知之后的部分!"); return object; }
public void afterException() { System.out.println("异常通知!"); }
public void after() { System.out.println("这是后置通知!"); } }
|
advice 本质是这个 aspect
类当中具体的一个方法,当然要用 Spring 管理,所以这些都是 Java Bean
编写配置文件
applicatioContext.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans" 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 name="userDao" class="com.itheima.demo03.UserDaoImpl"/> <bean name="xmlAdvice" class="com.itheima.demo03.XmlAdvice"/> <aop:config> <aop:pointcut id="pointcut" expression="execution(* com.itheima.demo03.UserDaoImpl.*(..))"/> <aop:aspect ref="xmlAdvice"> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/> <aop:around method="around" pointcut-ref="pointcut"/> <aop:after-throwing method="afterException" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> </beans>
|
这里通过 <aop:config> 标签配置 Spring
AOP,定义了一个切点 pointcut,表达式
execution(* com.itheima.demo03.UserDaoImpl.*(..)) 表示匹配
UserDaoImpl 类中所有方法的执行。然后通过
<aop:aspect> 标签指定切面,ref
属性指定切面所引用的 Bean,并通过
<aop:before>、<aop:after-returning>、<aop:around>、<aop:after-throwing>
和 <aop:after>
标签分别指定前置通知、返回通知、环绕通知、异常通知和后置通知,method
属性指定通知方法的名称,pointcut-ref
属性指定通知所应用的切点。
其中,切点表达式的一般格式为
execution([modifiers-pattern]? [return-type-pattern] [declaring-type-pattern]?[method-name-pattern]([parameters-pattern]) [throws-pattern]?),其中
? 表示可选项
在这里的表达式
execution(* com.itheima.demo03.UserDaoImpl.*(..))
中,*
表示匹配任意返回类型,com.itheima.demo03.UserDaoImpl
表示匹配 UserDaoImpl 类,.*(..)
表示匹配该类中所有方法的执行,允许任意数量/类型的参数
<aop:before>
表示前置通知,在目标方法执行之前执行
<aop:after-returning>
表示返回通知,在目标方法正常返回后执行
<aop:around>
表示环绕通知,可以在目标方法执行前后执行,并且可以控制目标方法是否执行,实际上是对目标方法的一个包装,控制目标方法的执行。
<aop:after-throwing>
表示异常通知,在目标方法抛出异常后执行
<aop:after>
表示后置通知,在目标方法执行后(无论是否抛出异常)执行
创建测试类
TestXml.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| package com.itheima.demo03;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestXml { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao = context.getBean("userDao", UserDao.class); userDao.delete(); System.out.println(); userDao.insert(); System.out.println(); userDao.select(); System.out.println(); userDao.update(); } }
|
基于注解的 Spring AOP 配置
编写配置文件
applicationContext-Anno.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns="http://www.springframework.org/schema/beans" 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 name="userDao" class="com.itheima.demo03.UserDaoImpl"/> <bean name="AnnoAdvice" class="com.itheima.demo04.AnnoAdvice"/> <aop:aspectj-autoproxy/> </beans>
|
这里的 <aop:aspectj-autoproxy/>
标签用于开启基于注解的 Spring AOP 配置,Spring 会自动扫描带有
@Aspect 注解的类,并将其作为切面进行处理。
创建切面类
AnnoAdvice.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package com.itheima.demo04;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*;
@Aspect public class AnnoAdvice { @Pointcut("execution( * com.itheima.demo03.UserDaoImpl.*(..))") public void poincut() { }
@Before("poincut()") public void before(JoinPoint joinPoint) { System.out.print("这是前置通知!"); System.out.print("目标类是:" + joinPoint.getTarget()); System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName()); }
@AfterReturning("poincut()") public void afterReturning(JoinPoint joinPoint) { System.out.print("这是返回通知!"); System.out.println("被织入增强处理的目标方法为:" + joinPoint.getSignature().getName()); }
@Around("poincut()") public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("这是环绕通知之前的部分!"); Object object = point.proceed(); System.out.println("这是环绕通知之后的部分!"); return object; }
@AfterThrowing("poincut()") public void afterException() { System.out.println("异常通知"); }
@After("poincut()") public void after() { System.out.println("这是后置通知!"); } }
|
这里的 pointcut() 是 dummy
方法,用于定义切点,仅用于提供
id,并不会被执行,@Pointcut
注解指定切点表达式,其他通知注解(如
@Before、@AfterReturning、@Around、@AfterThrowing
和
@After)通过引用切点方法来指定通知的应用范围,而无需重复编写具体的切点表达式。
编写测试类
TestAnnotation.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.itheima.demo04;
import com.itheima.demo03.UserDao; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAnnotation { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-Anno.xml"); UserDao userDao = context.getBean("userDao", UserDao.class); userDao.delete(); System.out.println(); userDao.insert(); System.out.println(); userDao.select(); System.out.println(); userDao.update(); } }
|