初识AOP
(1)什么是AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。
(2)为什么用AOP
就是为了方便,一个国外很有名的大师说,编程的人都是“懒人”,因为他把自己做的事情都让程序做了,用了AOP能让你少写很多代码。 为了更清晰的逻辑,可以让你的业务逻辑去关注自己本身的业务,而不去想一些其他的事情,这些其他的事情包括:安全,事物,日志等。
(3)AOP的引入:以代理模式为例
静态代理
a. 定义业务接口
public interface IAccountService { // 转账业务接口 void transfer(); }
b、定义目标类与目标方法
public class AccountServiceImpl implements IAccountService { // 转账业务实现即目标方法 @Override public void transfer() { System.out.println("进行转账操作"); } }
c. 定义代理类AccountServiceImplProxy,实现IAccountService接口。在有参构造方法中传入目标对象,将目标对象引入代理类,以便代理类调用目标方法,进行增强
public class AccountServiceImplProxy implements IAccountService { // 声明目标接口对象 private IAccountService target; public AccountServiceImplProxy() { } // 业务接口对象作为构造器,用于接收目标对象 public AccountServiceImplProxy(IAccountService target) { this.target = target; } @Override public void transfer() { // 此处对目标方法进行增强 System.out.println("对转账人身份校验。。"); target.transfer(); System.out.println("进行日志记录。。"); } }
d. 编写测试类TransferServiceTest
public class TransferServiceTest { public static void main(String[] args) { // 创建目标对象 IAccountService target = new AccountServiceImpl(); // 创建代理对象,传入目标对象进行初始化 IAccountService proxy = new AccountServiceImplProxy(target); // 执行代理对象的方法 proxy.transfer(); } }
动态代理
动态代理,程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理对象只是由代理工具在程序运行时由JVM根据反射等机制动态生成。代理对象与目标对象的代理关系在程序运行时才确立。
(1)JDK动态代理
JDK动态代理是通过JDK提供的 java.lang.reflect.Proxy类实现动态大力,使用其静态方法newProxyInstance(),对目标对象、业务接口及业务增强逻辑,自动生成一个动态代理对象。
1. 定义业务接口与实现类
public interface IAccountService { // 转账业务接口 void transfer(); }
public class AccountServiceImpl implements IAccountService{ public void transfer() { System.out.println("进行转账操作"); } }
2. 定义JdkProxy类实现InvocationHandler接口,实现invoke()方法,并对业务逻辑进行增强
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkProxy implements InvocationHandler { private Object target; public JdkProxy() { } public JdkProxy(Object target) { this.target = target; } public Object createProxy(Object target) { this.target = target; // 1.类加载器 ClassLoader classLoader = JdkProxy.class.getClassLoader(); // 2.被代理对象实现的所有接口 Class[] classes = target.getClass().getInterfaces(); // 3.使用代理类,进行增强,返回的是代理对象 return Proxy.newProxyInstance(classLoader,classes,this); } /* * 所有动态代理类的方法调用,都会交由invoke()方法去处理, proxy 被代理的对象 * method 将要被执行的方法信息(反射), args 执行方法时需要的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("对转账人身份校验。。"); // 在目标类上调用方法,并传入参数 Object result = method.invoke(target, args); // 后增强 System.out.println("进行日志记录。。"); return result; } }
3. 测试验证
public class JDKProxyTest { public static void main(String[] args) { IAccountService accountService = new AccountServiceImpl(); JdkProxy jdkProxy = new JdkProxy(); // 从代理对象中获取增强后的目标对象 IAccountService accountService2 = (IAccountService) jdkProxy.createProxy(accountService); accountService2.transfer(); } }
(2)CGLIB动态代理
CGLIB是一个开源的第三方代码生成类库,对于无接口的类,要为其创建动态代理,就要使用CGLIB进行实现。CGLIB代理的生成原理是生成目标类的子类,子类是增强过的,就是目标类的代理类。所以,使用CGLIB生成动态代理,要求目标类必须能够被继承,即不能是final修饰的类。
1.引入CGLIB依赖
<dependency> <groupId>cglib</groupId> <artifactId>cglib-full</artifactId> <version>2.0.2</version> </dependency>
2.创建目标类AccountService
public class AccountService { // 转账业务 即目标方法 public void transfer() { System.out.println("进行转账操作"); } // 查询余额 即目标方法 public void getBalance() { System.out.println("查询余额操作"); } }
3. 创建代理类AccountServiceCglibProxy实现MethodInterceptor接口,完善intercept()方法进行增强,创建生成代理对象createProxy()方法
public class AccountServiceCglibProxy implements MethodInterceptor { // 声明目标类的成员变量,并创建以目标类为参数的构造器,用于接收目标对象 private AccountService target; public AccountServiceCglibProxy(AccountService accountService) { this.target = accountService; } /** * @param o 代理对象 * @param method 代理对象的方法,即增强后的方法 * @param objects 方法参数 * @param methodProxy 代理方法的对象 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 此处对目标方法进行增强 Object result = new Object(); if ("transfer".equals(method.getName())){ System.out.println("对转账人身份校验。。"); result = method.invoke(target, objects); System.out.println("进行日志记录。。"); }else{ // 直接执行目标对象的目标方法 result = methodProxy.invokeSuper(o, objects); } return result; } // 创建代理对象 public AccountService createProxy(){ // 创建增强器 Enhancer enhancer = new Enhancer(); // 初始化增强器:将目标类指定为父类 enhancer.setSuperclass(AccountService.class); // 初始化增强器:设置回调至本类中的intercept()方法 enhancer.setCallback(this); // 使用增强器创建代理对象 return (AccountService) enhancer.create(); } }
4. 创建测试类
public class CglibProxyTest { public static void main(String[] args) { // 目标对象 AccountService target = new AccountService(); // 创建代理对象,传入目标对象进行初始化 AccountService accountService = (AccountService) new AccountServiceCglibProxy(target).createProxy(); accountService.transfer(); accountService.getBalance(); } }
Spring中的aop
Spring中的AOP,就是通过配置的方式(有基于XML配置的, 以及基于注解配置的),来实现相关的拦截切入功能。对原有的操作进行加强,但不影响原本的操作。
因为Spring AOP中的代理对象由IoC容器自动生成,所以开发者无须过多关注代理对象生成的过程,只需选择连接点、创建切面、定义切点并在XML文件中添加配置信息即可。 Spring提供了一系列配置Spring AOP的XML元素。
配置Spring AOP的XML元素
测试案例
1、引入pom
<properties> <java.version>8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.0.0.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.0.0.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.0.0.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.0.0.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/cglib/cglib --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <!-- https://mvnrepository.com/artifact/aopalliance/aopalliance --> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> </dependencies>
2、创建目标类
public class StudentService { public void eat(){ System.out.println("我要开始吃午饭了"); } }
3、创建切面类
public class StudentAspect { // 前置通知 public void before() { System.out.println("前置通知==========="); } // 后置通知: public void after() { System.out.println("后置通知============"); } // 环绕通知: public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("环绕前通知=========="); Object obj = joinPoint.proceed(); System.out.println("环绕后通知=========="); return obj; } // 异常抛出通知: public void afterThrowing(Throwable e) { System.out.println("异常抛出通知=========" + e.getMessage()); } //返回通知 public void afterReturning(Object result) { System.out.println("返回通知===========" + result); } }
4、创建配置文件
在Spring的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,因此,在使用<aop:aspect>元素之前,要在配置文件中先定义一个普通的Spring Bean。Spring Bean定义完成后,通过<aop:aspect>元素的ref属性即可引用该Bean。
切点表达式类型execution匹配方法切入点。根据表达式描述匹配方法,是最通用的表达式类型,可以匹配方法、类、包。
表达式模式:
execution(modifier? ret-type declaring-type?name-pattern(param-pattern) throws-pattern?)
表达式解释:
(1)modifier:匹配修饰符,public, private 等,省略时匹配任意修饰符
(2)ret-type:匹配返回类型,使用 * 匹配任意类型
(3)declaring-type:匹配目标类,省略时匹配任意类型
- .. 匹配包及其子包的所有类
(4)name-pattern:匹配方法名称,使用 * 表示通配符
- * 匹配任意方法
- set* 匹配名称以 set 开头的方法
(5)param-pattern:匹配参数类型和数量
- () 匹配没有参数的方法
- (..) 匹配有任意数量参数的方法
- (*) 匹配有一个任意类型参数的方法
- (*,String) 匹配有两个参数的方法,并且第一个为任意类型,第二个为 String 类型
(6)throws-pattern:匹配抛出异常类型,省略时匹配任意类型
使用示例
<?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="studentService" class="com.gqx.StudentService"></bean> <!-- 配置切面类 --> <bean id="studentAspectXML" class="com.gqx.StudentAspect"></bean> <!-- 配置 AOP --> <aop:config> <!-- 配置切点表达式 --> <!-- 整个表达式可以分为五个部分: 1、execution()::表达式主体。 2、第一个*号:表示返回类型, *号表示所有的类型。 3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service包、子孙包下所有类的方法。 4、第二个*号:表示类名,*号表示所有的类。 5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数 --> <!-- 配置切点 --> <aop:pointcut expression="execution(* com.gqx.StudentService.eat(..))" id="pointcut1" /> <!-- 配置切面及通知 --> <aop:aspect ref="studentAspectXML"> <!-- 前置通知 --> <aop:before method="before" pointcut-ref="pointcut1" /> <!-- 后置通知 --> <aop:after method="after" pointcut-ref="pointcut1" /> <!-- 环绕通知--> <!-- <aop:around method="around" pointcut-ref="pointcut3" />--> <!-- 异常抛出通知 --> <!-- <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e" />--> <!-- 返回通知 --> <!-- <aop:after-returning method="afterReturning" pointcut-ref="pointcut4" returning="result" />--> </aop:aspect> </aop:config> </beans>
5、创建运行类
public class AppMain { public static void main(String[] args) { //1. 创建IoC容器对象,加载spring核心配置文件 //初始化Spring容器ApplicationContext,加载配置文件 ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml"); //2. 从IOC容器中获取Bean对象 StudentService studentService = (StudentService) application.getBean("studentService"); studentService.eat(); } }
练习
补充下面接口测试代码,实现基于AOP的切面编程。
(1)创建接口UserDao,并在该接口中编写添加、删除、修改和查询的方法。
public interface UserDao { public void insert(); public void delete(); public void update(); public void select(); }
创建UserDao接口的实现类UserDaoImpl,实现UserDao接口中的方法。
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("查询用户信息"); } }
(2)创建XmlAdvice类,用于定义切面通知。
public class XmlAdvice { // 前置通知 public void before(JoinPoint joinPoint){ System.out.print("这是前置通知!"); System.out.print("目标类是:"+joinPoint.getTarget()); System.out.println(",被织入增强处理的目标方法为:"+ joinPoint.getSignature().getName()); } //需要补充:返回通知、环绕通知、异常通知、后置通知 }
(3)创建applicationContext.xml文件,在该文件中引入AOP命名空间,使用<bean>元素添加Spring AOP的配置信息。
<!-- 注册bean省略,下面内容为配置Spring AOP--> <aop:config> <aop:pointcut id="pointcut" expression=""/><!-- 指定切点 --> <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>
(4)创建测试类TestXml,测试基于XML的AOP实现。
public class TestXml{ public static void main(String[] args){ ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); UserDao userDao=context.getBean("userDao",UserDao.class); userDao.delete(); userDao.insert(); userDao.select(); userDao.update(); } }
结果如图: