Spring 是解决实际开发中的一些问题,而 AOP 解决 OOP 中遇到的一些问题.是 OOP 的延续和扩展.
使用面向对象编程 ( OOP )有一些弊端,当需要为多个不具有继承关系的对象引人同一个公共行为时,例如日志、安全检测等,我们只有在每个对象里引用公共行为,这样程序中就产生了大量的重复代码,程序就不便于维护了,所以就有了一个对面向对象编程的补充,即面向方面编程 ( AOP ), AOP 所关注的方向是横向的,区别于 OOP 的纵向。
2.1 为什么学习 AOP
在不修改源码的情况下,对程序进行增强
AOP 可以进行权限校验,日志记录,性能监控,事务控制
2.2 Spring 的 AOP 的由来
AOP 最早由 AOP 联盟的组织提出的,制定了一套规范.Spring 将 AOP 思想引入到框架中,必须遵守 AOP 联盟 的规范.
2.3 底层实现
AOP依赖于IOC来实现,在AOP中,使用一个代理类来包装目标类,在代理类中拦截目标类的方法执行并织入辅助功能。在Spring容器启动时,创建代理类bean替代目标类注册到IOC中,从而在应用代码中注入的目标类实例其实是目标类对应的代理类的实例,即使用AOP处理目标类生成的代理类。
代理机制:
Spring 的 AOP 的底层用到两种代理机制:
JDK 的动态代理 :针对实现了接口的类产生代理.
Cglib 的动态代理 :针对没有实现接口的类产生代理. 应用的是底层的字节码增强的技术 生成当前类的子类对象.
2.4 动态代理
Java中提供的动态代理,增强一个类的方法
package com.tledu.service.impl;
import lombok.Getter;
import lombok.Setter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author cyrus
*/
public class LogInterceptor implements InvocationHandler {
/**
* 被代理的对象
*/
@Getter
@Setter
private Object target;
private void before(Method method) {
System.out.println(method.getName()+"调用之前");
}
private void after(Method method) {
System.out.println(method.getName()+"调用之后");
}
/**
* @param proxy 代理的对象
* @param method 代理的方法
* @param args 方法的参数
* @return 方法的返回值
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置操作
before(method);
// 通过被代理对象调用真正需要执行的方法
Object res = method.invoke(target, args);
// 后置操作
after(method);
return res;
}
}
测试
@Test
public void proxy() {
// 创建真实的对象
IUserDao userDao = new UserDaoImpl();
// 创建代理
LogInterceptor logInterceptor = new LogInterceptor();
// 设置需要代理的对象
logInterceptor.setTarget(userDao);
// 创建代理之后的对象
/*
第一个参数: 真实对象的类解析器
第二个参数: 实现的接口数组
第三个参数: 调用代理方法的时候,会将方法分配给该参数
*/
IUserDao userDaoProxy = (IUserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),
new Class[]{IUserDao.class}, logInterceptor);
userDaoProxy.insert(null);
}
newProxyInstance,方法有三个参数:
loader: 用哪个类加载器去加载代理对象
interfaces:动态代理类需要实现的接口
h:动态代理方法在执行时,会调用h里面的invoke方法去执行
2.5 AOP相关术语
-
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点.
-
Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知.通知分为前置通知,后置 通知,异常通知,最终通知,环绕通知(切面要完成的功能)
-
Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
-
Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类 动态地添加一些方法或 Field.
-
Target(目标对象): 代理的目标对象
-
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程,spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装在期织入
-
Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类
-
Aspect(切面): 是切入点和通知(引介)的结合
2.6 注解方式
2.6.1 引入jar包
还是之前IOC的 再次引入三个就行,因为Spring的AOP是基于AspectJ开发的
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- 增加了切面 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
2.6.2 配置文件
-
引入AOP约束,通过配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
<!--开启AOP注解-->
<aop:aspectj-autoproxy />
</beans>
-
通过注解的方式开启切面支持 @EnableAspectJAutoProxy
2.6.3 AOP类
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogInterceptor {
@Pointcut("execution(public * com.tledu.zrz.spring.service..*.add(..))")
public void myMethod() {
// 假设这个方法就是被代理的方法
}
@Before("myMethod()")
public void beforeMethod() {
System.out.println(" execute start");
}
@After("myMethod()")
public void afterMethod() {
System.out.println(" execute end");
}
// 环绕,之前和之后都有,相当于 @Before 和 @After 一起使用
@Around("myMethod()")
public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
// @Around也可以和@Before 和 @After 一起使用
System.out.println("around start");
// 调用被代理的方法
pjp.proceed();
System.out.println("around end");
}
}
规则:
在配置方法的时候,方法可以添加参数JoinPoint joinPoint,通过这个参数可以拿到当前调用的信息,方便我们进行后续的处理。()
@After("myMethod()")
public void after(JoinPoint joinPoint) {
System.out.println("目标方法名为:" + joinPoint.getSignature().getName());
System.out.println("目标方法参数:" + joinPoint.getArgs());
System.out.println("目标方法所属类的简单类名:" + joinPoint.getSignature().getDeclaringType().getSimpleName());
System.out.println("目标方法所属类的类名:" + joinPoint.getSignature().getDeclaringTypeName());
}
@Pointcut 规则 https://www.cnblogs.com/itsoku123/p/10744244.html
2.6.4 测试类
@Test
public void testAdd() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"beans.xml");
UserService userService = (UserService) applicationContext
.getBean("userService");
userService.add(null);
}
演示示例:Spring_14_AOP_Annotation
2.7 XML方式
2.7.1 引入jar包
和注解方式引入的一样
2.7.2 配置文件
引入AOP约束
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
配置AOP
<!-- 实例化 -->
<bean id="logInterceptroe" class="com.tledu.zrz.spring.aop.LogInterceptor"></bean>
<!-- 设置AOP -->
<aop:config>
<aop:aspect id="logAspect" ref="logInterceptroe">
<!-- 需要进行代理的方法 -->
<aop:pointcut expression="execution(public * com.tledu.zrz.spring.service..*.add(..))"
id="pointcut" />
<!-- 前后执行 -->
<aop:before method="before" pointcut-ref="pointcut" />
<!-- 或者这样写,这样就不需要pointcut标签了 -->
<!-- <aop:before method="before" pointcut="execution(public * com.tledu.zrz.spring.service..*.add(..))"
/> -->
<aop:after method="after" pointcut-ref="pointcut" />
<!-- 环绕,一般要么使用 around 要和使用 before和after 不会一起使用 -->
<aop:around method="aroundMethod" pointcut-ref="pointcut" />
</aop:aspect>
</aop:config>
2.6.3 AOP类
和注解一样,就是把注解去掉了
import org.aspectj.lang.ProceedingJoinPoint;
public class LogInterceptor {
public void myMethod() {
// 假设这个方法就是被代理的方法
}
public void before() {
System.out.println(" execute start");
}
public void after() {
System.out.println(" execute end");
}
// 环绕,之前和之后都有,相当于 @Before 和 @After 一起使用
public void aroundMethod(ProceedingJoinPoint pjp) throws Throwable {
// @Around也可以和@Before 和 @After 一起使用
System.out.println("around start");
// 调用被代理的方法
pjp.proceed();
System.out.println("around end");
}
}
2.6.4 测试类
@Test
public void testAdd() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"beans.xml");
UserService userService = (UserService) applicationContext
.getBean("userService");
userService.add(null);
}