AOP概念
AOP是面向切面,即扩展功能但是不修改源代码,AOP采取横向抽取机制,一般用于 性能监控、事务管理、安全检查、缓存;
这里先介绍一下纵向抽取机制:
我们一般添加功能是这样做的:
public class User {
public void add() {
//添加用户的功能
//添加打印日志的功能
}
public void test() {
//添加用户的功能
//添加打印日志的功能
}
// ......
}
这样的缺点是每个需要添加打印日志功能的方法都需要在方法里面添加相关的代码,很不方便,于是便诞生了纵向抽取机制的解决方法;
public class BaseLog {
//添加日志功能
public void writeLog() {
//功能代码
}
}
public class User extends BaseLog{
public void add() {
//添加用户的功能
//添加打印日志的功能
super.writeLog();
}
public void test() {
//添加用户的功能
//添加打印日志的功能
super.writeLog();
}
// ......
}
因为这样的方式是通过继承这种纵向关系实现的,所以也叫纵向抽取机制,AOP采用的是不是这个而是横向抽取机制,横向抽取机制主要用的是动态代理,也就是AOP的核心,所以结合Spring(一),我们总结一下:
- IOC的核心是反射技术;
- AOP的核心是动态代理;
要实现动态代理,Java提供了两种方式,即JDK自己提供的动态代理和第三方的CGLIB,在有接口的情况下,我们使用的是JDK提供的动态代理: 这块可参考→动态代理
我们需要一个接口、该接口的具体实现类(目标类)、代理辅助类(需要实现InvocationHandler接口):
public class Proxy implements InvocationHandler {
public void invoke() {
//添加特有的前置功能
//目标类
method.invoke();
//添加特有的后置功能
}
}
在没有接口的情况下,使用CGlib的代理方式;
AOP相关术语
- 连接点:类中可以被增强的方法,都可以被称为连接点;
- 切入点:类中实际被增强的方法;
- 通知/增强:指拦截到切入点之后可以做的事,成为增强;
- 切面:是切入点和通知的结合,把增强应用到切入点的过程;
通知分5种:
- 前置通知:执行被增强的方法之前;
- 后置通知:执行被增强的方法之后;
- 异常通知:发送异常的时候;
- 环绕通知:执行被增强的方法前后,可以看作前置通知+后置通知;
- 最终通知:最后才会执行的通知;
AOP的实现
Spring中的AOP实现还是比较简单的,AOP的实现借助于Aspectj框架(这是一个独立的框架,不是Spring的一部分!!),AOP的实现有两种方式,即配置和注解:
两种方式都需要用execution()的表达式来配置切入点:
execution(<访问修饰符><返回类型><方法名>(<参数>)<异常>)
1、execution(* com.tulun.bean.User.add(..)) //表示某个类下面的指定方法
2、execution(* com.tulun.bean.User.*(..)) //表示某个包下面某个类的所有方法
3、execution(* *.*(..)) //表示所有
AOP的实现需要在Spring基本的jar包上再添加如下的jar包:
<!--AOP相关jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
通过配置实现
步骤:
- 引入依赖;
- 创建需要被增强的类、提供增强功能的类;
- 配置XML配置文件、创建对象、配置切入点、配置切面;
- 使用;
一、引入依赖
就上面那个;
二、创建相关的类
这是一个需要被增强的类:
public class User {
//需要被增强的方法
public void add() {
System.out.println("User.add()");
}
}
这是一个提供增强功能的类:
public class Log {
public void writeLog() {
System.out.println("写日志功能");
}
}
三、创建xml文件aop1.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-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--首先需要创建对象-->
<!--创建User对象-->
<bean id="user" class="com.tulun3.User"/>
<!--创建Log对象-->
<bean id="log" class="com.tulun3.Log"/>
<!--配置AOP操作-->
<aop:config>
<!--配置切入点 实际需要被增强的方法-->
<!--使用execution()表达式-->
<aop:pointcut id="pointcut" expression="execution(* com.tulun3.User.add())"/>
<!--配置切面 将增强应用到方法上-->
<aop:aspect ref="log">
<!--配置增强的方法-->
<!--配置前置通知-->
<aop:before method="writeLog" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
四、使用
测试:
public class TestDemo1 {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("注解实现AOP/aop1.xml");
User user = (User) context.getBean("user");
user.add();
}
}
这是使用的前置通知,其他的通知也差不多,就环绕通知特殊一点,下面是一个环绕通知的实现:
需要在增强类里面添加一个环绕通知的方法:
public class Log {
//环绕通知
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("前置功能");
joinPoint.proceed();
System.out.println("后置功能");
}
}
修改一些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-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--首先需要创建对象-->
<!--创建User对象-->
<bean id="user" class="com.tulun3.User"/>
<!--创建Log对象-->
<bean id="log" class="com.tulun3.Log"/>
<!--配置AOP操作-->
<aop:config>
<!--配置切入点 实际需要被增强的方法-->
<!--使用execution()表达式-->
<aop:pointcut id="pointcut" expression="execution(* com.tulun3.User.add())"/>
<!--配置切面 将增强应用到方法上-->
<aop:aspect ref="log">
<!--配置增强的方法-->
<!--==============标记=====================-->
<!--环绕通知-->
<aop:around method="around" pointcut-ref="pointcut"/>
<!--==============标记=====================-->
</aop:aspect>
</aop:config>
</beans>
测试:
public class TestDemo2 {
@Test
public void testAround() {
ApplicationContext context = new ClassPathXmlApplicationContext("配置实现AOP/aop-Around.xml");
User user = (User) context.getBean("user");
user.add();
}
}
通过注解实现
通过注解实现那就更简单了,步骤如下:
- 引入依赖
- 创建被增强的类、提供增强的类;
- 创建XML文件,创建对象,开启AOP操作;
- 添加注解;
- 使用;
一、引入依赖
略;
二、创建相关的类
和上面一样的,
public class User {
//需要被增强的方法
public void add() {
System.out.println("User.add()");
}
}
public class Log {
public void writeLog() {
System.out.println("写日志功能");
}
}
三、创建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-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--首先需要创建对象-->
<!--创建User对象-->
<bean id="user" class="com.tulun4.User"/>
<!--创建Log对象-->
<bean id="log" class="com.tulun4.Log"/>
<!--开启AOP操作-->
<aop:aspectj-autoproxy/>
</beans>
四、添加注解
在增强类上面添加@Aspect,在增强方法上面添加相关通知:
@Aspect
public class Log {
@Before(value = "execution(* com.tulun4.User.add())")
public void writeLog() {
System.out.println("写日志功能");
}
}
前置通知:@Before
后置通知:@After
环绕通知:@Around
最终通知:@AfterReturning
异常通知:@AfterThrowing
以上注解都是作用于方法上,该方法为切入点,既要增加功能的方法,
@Before、@After、@Around的value的值是要使用execution的函数表达式;
execution表达式和配置中使用的方法和效果是一样的;
五、使用
测试:
public class TestDemo1 {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("注解实现AOP/aop1.xml");
User user = (User) context.getBean("user");
user.add();
}
}
另外,补充下,切面的执行顺序: