Spring学习记录(六):Spring AOP学习
文章目录
前言
Spring两大核心IOC以及AOP,接下来就是AOP的相关用法了。
一、AOP是什么?
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率(官话官话)。
二、使用AOP的好处
- 在程序运行期间,在不修改源码的情况下对方法进行功能增强。
- 逻辑更加清晰,在核心业务开发时只需要关注增强部分。
- 减少重复代码,提升个开发效率以便后期维护。
三、AOP底层原理实现
AOP的底层是通过Spring提供的动态代理技术实现的,在代码运行期间,Spring通过动态代理技术生成的代理对象执行方法进行功能增强,之后再调用目标方法完成功能的增强。
四、AOP的相关术语
1.Target(目标对象)
代理的目标对象,也可以理解为被代理类,就是我们的主要功能。
2.Proxy(代理)
生成的代理对象,当一个类被织入增强后,就会产生一个结果代理类。
3.Joinpoint(连接点)
指那些可以被拦截到的点,在Spring里面我们可以理解为被拦截的方法。通俗的理解可以为可以被拦截增强的方法,比如你写的service下的方法都是可以作为连接点的地方。
4.Pointcut(切入点)
指我们要对哪些连接点拦截的定义,通俗来说就是真正被拦截的方法,就是更细分点!
5.Advice(通知)
所谓通知就是拦截之后要做的事情,包括前置通知、后置通知、异常通知、最终通知、环绕通知,通俗来说就是增强的业务逻辑。
6.Aspect(切面)
切入点和通知的结合。
7.Weaving(织入)
指把增强应用到目标对象来创建新的代理对象,Spring采用动态代理技术,AspectJ采用编译期织入和类装载器织入。
五、注意事项
因为AOP底层是通过动态代理来实现的,在Spring中会根据目标类是否实现了接口来决定采用哪种动态代理的方式,其中当bean实现接口时会采用JDK动态代理模式,没有实现接口时采用cglib,也可以强制使用cglib(在配置文件中加入):
<aop:aspectj-autoproxy proxy-target-class="true"/>
六、基于XML实现AOP开发
先贴上小demo的结构:
1.导入坐标
不管做啥,要想母鸡下蛋总得现有母鸡:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2. 创建目标接口以及实现类
这里创建一个目标接口以及实现类(啥都行自己玩儿):
public interface AccountService {
public void transfer();
}
public class AccountServiceImpl implements AccountService {
@Override
public void transfer() {
System.out.println("转账方法执行了");
}
}
3.创建通知类以及方法
创建通知类
public class MyAdvice {
public void before(){
System.out.println("前置通知执行了。。。");
}
}
4.创建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"
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
">
<!--目标类交给IOC容器-->
<bean id="accountService" class="com.ssm.service.impl.AccountServiceImpl"/>
<!--通知类交给IOC容器-->
<bean id="myAdvice" class="com.ssm.advice.MyAdvice"/>
<!--AOP配置-->
<aop:config>
<!--抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(public void com.ssm.service.impl.AccountServiceImpl.transfer())"/>
<!--配置切面+通知-->
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
这里先设置一个前置通知来学习一下流程,首先将通知类和目标类交给Spring容器,之后使用aop标签进行设置,首先用pointcut定义切入点,其中目标方法为transfer,这里需要全路径~接着定义切面,其中切面为我们的通知类,增强方法为before。
5.测试
接着写一个测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() {
accountService.transfer();
}
}
我们看到运行结果中,前置增强确实实现了。
6.切面表达式详解
写法:execution(访问修饰符 返回值 包名.包名……类名.方法名(参数列表))
例:execution(public void com.ssm.service.impl.AccountServiceImpl.transfer())
访问修饰符可以省略,返回值可以使用通配符*匹配。
包名也可以使用匹配,数量代表包的层级,当前包可以使用…标识,例如 *…AccountServiceImpl.transfer()
类名和方法名也都可以使用匹配: ….*()
参数列表使用…可以标识有无参数均可,且参数可为任意类型。
通常情况下,切入点应当设置再业务层实现类下的所有方法:* com.ssm.service.impl..(…)。
7.通知类详解
我们在之前配置了前置通知,现在我们来做一下所有的通知类,首先他的基础语法是:
<aop:通知类型 method="通知类中的方法" pointcut-ref="切点表达式"/>
名称 | 标签 | 说明 |
---|---|---|
前置通知 | < aop:before > | 用于配置前置通知,指定增强的方法在切入点方法之前执行 |
后置通知 | < aop:after-returning > | 用于配置后置通知,指定增强的方法在切入点方法之后执行 |
异常通知 | < aop:after-throwing > | 用于配置异常通知,指定增强的方法在切入点方法出现异常执行 |
最终通知 | < aop:after > | 用于配置最终通知,增强方法无论是否有异常都会执行 |
环绕通知 | < aop:around > | 用于配置环绕通知,手动控制增强代码何时执行 |
这里通常情况下,环绕通知都是独立使用的。
改造一下通知类和xml文件:
public class MyAdvice {
public void before(){
System.out.println("前置通知执行了。。。");
}
public void afterReturning(){
System.out.println("后置通知执行了。。。");
}
public void afterThrowing(){
System.out.println("异常通知执行了。。。");
}
public void after(){
System.out.println("最终通知执行了。。。");
}
}
<?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
">
<!--目标类交给IOC容器-->
<bean id="accountService" class="com.ssm.service.impl.AccountServiceImpl"/>
<!--通知类交给IOC容器-->
<bean id="myAdvice" class="com.ssm.advice.MyAdvice"/>
<!--AOP配置-->
<aop:config>
<!--抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(public void com.ssm.service.impl.AccountServiceImpl.transfer())"/>
<!--配置切面+通知-->
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="myPointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>
<aop:after method="after" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
执行结果展示:
再来看一下环绕通知的演示,依旧改造通知类和xml文件:
public class MyAdvice {
//ProceedingJoinPoint:正在执行的连接点 : 切点
public Object around(ProceedingJoinPoint pjd){
Object procees=null;
try {
System.out.println("前置通知执行了。。。");
procees = pjd.proceed();
System.out.println("后置通知执行了。。。");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("异常通知执行了。。。");
}finally {
System.out.println("最终通知执行了。。。");
}
return procees;
}
}
<?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
">
<!--目标类交给IOC容器-->
<bean id="accountService" class="com.ssm.service.impl.AccountServiceImpl"/>
<!--通知类交给IOC容器-->
<bean id="myAdvice" class="com.ssm.advice.MyAdvice"/>
<!--AOP配置-->
<aop:config>
<!--抽取切点表达式-->
<aop:pointcut id="myPointcut" expression="execution(public void com.ssm.service.impl.AccountServiceImpl.transfer())"/>
<!--配置切面+通知-->
<aop:aspect ref="myAdvice">
<aop:around method="around" pointcut-ref="myPointcut"/>
</aop:aspect>
</aop:config>
</beans>
其中ProceedingJoinPoint参数是我们的切点,可以理解为我们要执行的目标方法,接下来展示结果:
七、基于注解实现AOP开发
当然说到注解是之后开发的趋势,关于基于注解进行代码实现也很重要,接下来我们将改造当前的demo进行改造,将其变成纯注解实现的AOP开发。
首先我们改造当前的实现类加上@Service注入IOC容器中:
@Service
public class AccountServiceImpl implements AccountService {
@Override
public void transfer() {
System.out.println("转账方法执行了....");
}
}
对于通知类我们也需要将其注入IOC容器中,并通过注解@Aspect将其升级成切面类,同时还要配置切入点以及通知方法:
@Component
@Aspect //升级为切面类,配置切入点和通知关系
public class MyAdvice {
@Pointcut("execution(* com.ssm.service.impl.AccountServiceImpl.*(..))")
public void MyPointcut(){
}
@Before("MyAdvice.MyPointcut()")
public void before() {
System.out.println("前置通知执行了。。。");
}
@AfterReturning("MyAdvice.MyPointcut()")
public void afterReturning() {
System.out.println("后置通知执行了。。。");
}
@AfterThrowing("MyAdvice.MyPointcut()")
public void afterThrowing() {
System.out.println("异常通知执行了。。。");
}
@After("MyAdvice.MyPointcut()")
public void after() {
System.out.println("最终通知执行了。。。");
}
@Around("MyAdvice.MyPointcut()")
public Object around(ProceedingJoinPoint pjd){
Object procees=null;
try {
System.out.println("Around前置通知执行了。。。");
procees = pjd.proceed();
System.out.println("Around后置通知执行了。。。");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("Around异常通知执行了。。。");
}finally {
System.out.println("Around最终通知执行了。。。");
}
return procees;
}
}
这些注解在学习过xml进行配置的时候会十分熟悉,只不过他们以注解的形式存在。
除此之外并没有完结,我们还需要配置一个Config类来开启AOP的自动代理:
@Configuration
@ComponentScan("com.ssm")
@EnableAspectJAutoProxy //开启AOP的自动代理
public class SpringConfig {
}
我们使用@Configuration将该类加载成配置类,同时我们需要通过包扫描将通知类以及实现类一并扫描进去,因此我们直接指定@ComponentScan(“com.ssm”),进行该包下的所有文件进行扫描,在通过注解@EnableAspectJAutoProxy来开启AOP的自动代理,最后我们在测试方法上加上@ContextConfiguration(classes = SpringConfig.class)来对核心配置类进行扫描。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccontServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer(){
accountService.transfer();
}
}
总结
以上就是关于SpringAOP基于注解开发的简单流程,一般在使用AOP的应用场景中,基本涵盖在记录日志、监控性能、权限控制、事务管理等方面,接下来就应该学习一下SpringAOP的源码内容了。