初识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();
}
}
结果如图:


浙公网安备 33010602011771号