Spring AOP
前记:
- 参考书籍《Spring实战(第4版)》
- 很久之前看了AOP相关概念,但并没有进行实践,这次再次理解相关概念并实践
- 学习前,最好先理解一下设计模式中的代理模式:https://blog.csdn.net/qq_47234534/article/details/114106579
- 个人理解:AOP是进行水平扩展的经典实例,垂直扩展:改变原有系统的结构,不引入新资源,如垂直分表分库,垂直功能开发等;水平扩展:不改变原有的系统的结构,引入新资源(如增加服务器),如水平分表分库。要增加新功能,水平是扩展性比较强的。
一、概述
1、使用AOP优点
- 可以实现横向关注点与业务的分离
- 将横向关注点模块化之后(即切面),可以使关注点代码更集中
- 将横向关注点模块化之后(即切面),服务业务更精简
- 扩展更简单。可以为现有类增加方法和属性
2、AOP术语
- 横切关注点:通用的功能,与业务功能相对,如安全,日志等。
- 切面:横切关注点被模块化成一个个具体特殊的类,这些类就称为切面
- 通知:切面要做的工作,即切面中的方法
- 连接点:所有潜在的,能够应用通知的点,即能被切面切入的所有点
- 切点:说明通知的具体应用位置,即通过切点来选择连接点
- 织入:把切面应用到目标对象并创建新的代理对象的过程,即环绕增强
- 引入:向现有的类添加新的方法或者属性
3、切点表达式
基本格式
concert是一个包,Performance是一个类。
concert. * . *代表concert下的所
有类和所有方法
可以加入一些AspectJ指示器来细化该表达式。用时查。
切点指示器P107
限制切点范围P109
带参数P115
只有execution是具体执行的,其它都是用来限制匹配的。
4、依赖
<properties>
<spring.version>5.2.2.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.12</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
二、经典代理方法
- 只支持方法级别的代理。原因是底层使用了动态代理
- 功能相对于以下两种最强大
抽象对象及真实对象
public interface UserService {
void add();
void delete();
}
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("add a user");
}
public void delete() {
System.out.println("delete a user");
}
}
切面
//前绕通知切面
public class LogBefore implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("方法:" + method.getName() + ",进行了调用前通知");
}
}
//后绕通知切面
public class LogAfter implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("方法:" + method.getName() + ",进行了调用后通知");
}
}
配置
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userServiceImpl" class="com.hao.service.UserServiceImpl"/>
<bean id="logBefore" class="com.hao.aspect.LogBefore"/>
<bean id="logAfter" class="com.hao.aspect.LogAfter"/>
<!--aop相关配置-->
<aop:config>
<!--定义切点-->
<aop:pointcut id="userPointCut" expression="execution(* com.hao.service.UserServiceImpl.*(..))"/>
<!--环绕增强-->
<aop:advisor advice-ref="logBefore" pointcut-ref="userPointCut"/>
<aop:advisor advice-ref="logAfter" pointcut-ref="userPointCut"/>
</aop:config>
</beans>
测试
public class TestMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("application.xml");
UserService userService = (UserService)classPathXmlApplicationContext.getBean("userServiceImpl");
userService.add();
userService.delete();
}
}
/**
方法:add,进行了调用前通知
add a user
方法:add,进行了调用后通知
方法:delete,进行了调用前通知
delete a user
方法:delete,进行了调用后通知
Process finished with exit code 0
**/
三、纯POJO方法(自定义类)
//抽象对象
public interface UserService {
void add();
void delete();
}
//真实对象
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("add a user");
}
public void delete() {
System.out.println("delete a user");
}
}
//切面
public class LogAspect {
public void before() {
System.out.println("-------前绕通知-------");
}
public void after() {
System.out.println("-------后绕通知-------");
}
/**
*
* @param proceedingJoinPoint 会自动注入进来。包含一些被代理对象的一些信息
*/
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("---------执行带参数的环绕通知前------");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("被代理对象签名为:" + proceedingJoinPoint.getSignature());
System.out.println("--------执行带参数的环绕通知后-------");
}
}
//测试
public class TestMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("application.xml");
UserService userService = (UserService)classPathXmlApplicationContext.getBean("userServiceImpl");
userService.add();
userService.delete();
}
}
/**
-------前绕通知-------
---------执行带参数的环绕通知前------
add a user
被代理对象签名为:void com.hao.service.UserService.add()
--------执行带参数的环绕通知后-------
-------后绕通知-------
-------前绕通知-------
---------执行带参数的环绕通知前------
delete a user
被代理对象签名为:void com.hao.service.UserService.delete()
--------执行带参数的环绕通知后-------
-------后绕通知-------
Process finished with exit code 0
**/
配置
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册Bean-->
<bean id="userServiceImpl" class="com.hao.service.UserServiceImpl"/>
<bean id="logAspect" class="com.hao.aspect.LogAspect"/>
<aop:config>
<!--定义通用切点。可在多个切面中使用-->
<aop:pointcut id="userPointCut" expression="execution(* com.hao.service.UserServiceImpl.*(..))"/>
<!--定义切面:logAspect-->
<aop:aspect ref="logAspect">
<!--通知织入,环绕增强-->
<aop:before method="before" pointcut-ref="userPointCut"/>
<aop:after method="after" pointcut-ref="userPointCut"/>
<aop:around method="around" pointcut-ref="userPointCut"/>
</aop:aspect>
</aop:config>
</beans>
四、注解
- 本质上,它依然是Spring基于代理的切面P114
- 注解和方法三中的配置内容是一一对应的
//抽象对象
public interface UserService {
void add();
void delete();
}
//真实对象
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("add a user");
}
public void delete() {
System.out.println("delete a user");
}
}
//切面
@Aspect
//配置中可以用这个注解替代@EnableAspectJAutoProxy
public class LogAspect {
@Before("execution(* com.hao.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("-------前绕通知-------");
}
@After("execution(* com.hao.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("-------后绕通知-------");
}
/**
*
* @param proceedingJoinPoint 会自动注入进来。包含一些被代理对象的一些信息
*/
@Around("execution(* com.hao.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("---------执行带参数的环绕通知前------");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("被代理对象签名为:" + proceedingJoinPoint.getSignature());
System.out.println("--------执行带参数的环绕通知后-------");
}
}
//测试
public class TestMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("application.xml");
UserService userService = (UserService)classPathXmlApplicationContext.getBean("userServiceImpl");
userService.add();
userService.delete();
}
}
/**
-------前绕通知-------
---------执行带参数的环绕通知前------
add a user
被代理对象签名为:void com.hao.service.UserService.add()
--------执行带参数的环绕通知后-------
-------后绕通知-------
-------前绕通知-------
---------执行带参数的环绕通知前------
delete a user
被代理对象签名为:void com.hao.service.UserService.delete()
--------执行带参数的环绕通知后-------
-------后绕通知-------
Process finished with exit code 0
**/
配置
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userServiceImpl" class="com.hao.service.UserServiceImpl"/>
<bean id="logAspect" class="com.hao.aspect.LogAspect"/>
<!--开启自动代理配置-->
<aop:aspectj-autoproxy/>
</beans>
五、技巧
1注解切点语句重用
//切面
@Aspect
//配置中可以用这个注解替代@EnableAspectJAutoProxy
public class LogAspect {
@Before("execution(* com.hao.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("-------前绕通知-------");
}
@After("execution(* com.hao.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("-------后绕通知-------");
}
/**
*
* @param proceedingJoinPoint 会自动注入进来。包含一些被代理对象的一些信息
*/
@Around("execution(* com.hao.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("---------执行带参数的环绕通知前------");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("被代理对象签名为:" + proceedingJoinPoint.getSignature());
System.out.println("--------执行带参数的环绕通知后-------");
}
}
----------------------------------------------------------------------
可以被简化为:
@Aspect
//配置中可以用这个注解替代@EnableAspectJAutoProxy
public class LogAspect {
@Pointcut("execution(* com.hao.service.UserServiceImpl.*(..))")
public void action() {
}
@Before("action()")
public void before() {
System.out.println("-------前绕通知-------");
}
@After("action()")
public void after() {
System.out.println("-------后绕通知-------");
}
/**
*
* @param proceedingJoinPoint 会自动注入进来。包含一些被代理对象的一些信息
*/
@Around("action()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("---------执行带参数的环绕通知前------");
Object proceed = proceedingJoinPoint.proceed();
System.out.println("被代理对象签名为:" + proceedingJoinPoint.getSignature());
System.out.println("--------执行带参数的环绕通知后-------");
}
}
2