AOP了解
AOP:面向切面编程。把公共的模块提取出来,作为切面,在程序运行期织入到对应的业务中。AOP是通过动态代理实现的。
spring的AOP是使用哪种代理方式?
spring默认会判断被代理的类是否实现类接口,如果实现了接口,就使用JDK的代理方式,如果没有实现接口,就使用CGLib代理方式,当然我们也可以通过配置强制要求spring全部使用CGLib代理方式。
实现一个aop例子:
- 添加依赖
- 实现目标类
- 创建切面类
- 添加响应的处理方法,如前置通知方法
- 配置aop(目前通过xml配置文件进行配置)
- 测试
```java public interface UserDao { int save(String name); }<!-- spring的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
public class UserDaoImpl implements UserDao { public int save(String name) { System.out.println(“我的名字是:”+name); return 1; } }
```java
public class StAspacts {
//前置通知的方法
public void before(){ 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
https://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="userService" class="com.st.service.impl.UserServiceImpl"/>
<!-- 切面类 -->
<bean id="aspects" class="com.st.aspects.StAspacts"/>
<!-- 添加AOP的配置 -->
<!--- proxy-target-class:默认是false,如果修改为true,则表示要求spring所有的 动态代理都使用CGLib -->
<aop:config proxy-target-class="false">
<!-- 配置切面 -->
<!-- ref 执行我们上面配置的切面bean -->
<aop:aspect id="stAspacts" ref="aspects">
<!-- 配置一个前置通知:被拦截方法的之前执行的方法 -->
<!-- method:配置切面类中的对应的要执行的方法 -->
<!-- pointcut:切入点。 描述这个增强的方法,应该执行的位置。-->
<aop:before method="before" pointcut="execution(* com.st.service.impl.UserServiceImpl.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
测试:
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao bean = context.getBean(UserDao.class);
bean.save("quail");
}
AOP的五种通知
- 前置通知(before)
- 后置通知(after-returning)
- 环绕通知(round)被拦截方法的前后都会通知
- 异常通知(after-throwing)出现异常就执行
- 最终tongz(after)无论是否异常都会执行的通知
tips:所有通知都可以多次添加。
package com.quail.aspects;
import org.aspectj.lang.ProceedingJoinPoint;
public class QuailAspects {
// 1前置通知
public void before(){
System.out.println("--前置通知方法--");
}
// 6后置通知
public void afterReturning(){
System.out.println("--后置通知方法--");
}
// 234环绕通知,参数是切入点对象
public Object round(ProceedingJoinPoint joinPoint){
System.out.println("--2环绕前置通知--");
// 执行目标方法
Object proceed = null;
try {
// 通过切入点执行目标方法
proceed = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("--环绕后置通知--");
return proceed;
}
// 异常通知
public void afterThrowing(){
System.out.println("--异常通知--");
}
// 5最终通知
public void after(){
System.out.println("--最终通知--");
}
}
<!-- 添加aop配置 -->
<!-- proxy-target-class:默认是false,为true时,spring的动态代理方式为CGLib-->
<aop:config proxy-target-class="false">
<!-- 配置切面 -->
<aop:aspect id="myAspects" ref="aspects">
<!-- 配置一个前置通知:被拦截方法的之前执行的方法 -->
<!--method:配置切面类中的对应的要执行的方法-->
<!--pointcut:切入点。描述这个增强的方法,应该执行的位置。-->
<aop:before method="before"
pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
<aop:after-returning method="afterReturning"
pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
<aop:after-throwing method="afterThrowing"
pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
<aop:after method="after"
pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
<aop:around method="round"
pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
</aop:aspect>
</aop:config>
Spring通知方法中的参数
- 所有的通知方法都可以设置参数JoinPoint。这个参数叫连接点。包含了连接点的所有信息。这个参数必须写第一个。
- toString() //连接点的字符串表达形式,其中包含方法名字
- getThis() //目标对象
- getTarget() //被代理的对象
- getArgs() //获取被增强的方法的参数数组
在通知方法中使用目标方法的参数
public void before(String name){System.out.print(name);}
- 需要在配置文件中指明。
- 配置参数名,用来通知spring要传入的参数是哪个。
- 配置一个切点的要求,只有拥有这个参数的方法才会被增强。
<aop:before method="before" arg-names="name" pointcut="args(name) execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
后置通知中获取方法的返回值
- 在后置通知方法中设置一个参数,用来获取目标方法的返回值。
// 6后置通知 public void afterReturning(int returnVal){ System.out.println("--后置通知方法--"+returnVal); }
<aop:after-returning method="afterReturning" returning="returnVal" pointcut="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
- 在后置通知方法中设置一个参数,用来获取目标方法的返回值。
在异常通知中获取异常对象
- 添加表示异常的参数,如:RuntimeException e
- 配置文件中添加属性:throwing=”e”
SpringAOP切点的配置方法
定义一个切点描述:<aop:config proxy-target-class="false"> <aop:pointcut id="allMethod" expression="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/> <!-- 配置切面 --> <aop:aspect id="myAspects" ref="aspects"> <aop:after-returning method="afterReturning" pointcut-ref="allMethod"/> </aop:aspect> </aop:config>
切点描述的配置方式:
表示所有的public方法
execution(public * *(..))
- 所有的set开头的方法
execution(* set*(..))
- AccountService接口中的所有方法
execution(* com.xyz.service.AccountService.*(..))
- 在service包下的所有类的所有方法(不含子孙包)
execution(* com.xyz.service.*.*(..))
- service包下及子孙包下所有类的所有方法
execution(* com.xyz.service..*.*(..))
- service包中的任何连接点(仅SpringAOP中执行方法)
within(com.xyz.service.*)
- this中配置的都是具体类型,也就是全限定类名。this中不支持通配符
this(com.xyz.service.AccountService)
- target和this的主要不同点:target是按照目标类型进行匹配,this按照调用类型进行匹配
target(com.xyz.service.AccountService)
- 匹配指定参数的方法
args(java.io.Serializable)
- 按照指定bean的beanName匹配
bean(userService)
- 匹配所有的beanName以及service结尾的bean
bean(*Service)
Advisor方式配置AOP
- 前置通知
自定义一个类实现MethodBeforeAdvice接口。
package com.quail.aspects;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class BeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("--前置通知--");
}
}
<bean id="beforeAdvice" class="com.quail.aspects.BeforeAdvice"/>
<aop:config proxy-target-class="false">
<aop:pointcut id="allMethod"
expression="execution(* com.quail.dao.impl.UserDaoImpl.*(..))"/>
<!-- Spring会自己判断通知的类型 -->
<aop:advisor advice-ref="beforeAdvice" pointcut-ref="allMethod"/>
</aop:config>