在Spring中使用AOP有2种方式
- Spring AOP:Spring封装了动态代理实现AOP
- AspectJ:专业的AOP框架,更强大
最初spring是自己实现的aop,即spring aop,后来spring集成了aspectj的切面语法,也可以使用aspectj。
Spring AOP
所需依赖:spring-aop
目标接口
public interface UserService {
void login();
void logout();
}
目标类
@Service
public class UserServiceImpl implements UserService {
@Override
public void login() {
System.out.println("正在执行login()...");
}
@Override
public void logout() {
System.out.println("正在执行logout()...");
}
}
切面
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
@Component //根据增强时机实现对应的接口
public class UserServiceAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//前增强
System.out.println("正在执行前增强...");
//调用目标方法,返回值是Object类型
Object object=methodInvocation.proceed();
//后增强
System.out.println("正在执行后增强...");
//返回目标方法的返回值
return object;
}
}
对应的接口
通知类型 | 增强时机 | 需要实现的接口 |
---|---|---|
环绕通知 | 目标方法的整个调用流程,可实现前增强、后增强、异常处理等 | MethodInterceptor |
前置通知 | 目标方法执行之前 | MethodBeforeAdvice |
后置通知 | 目标方法执行之后 | AfterAdvice(空接口) |
异常通知 | 目标方法执行过程中抛出异常后 | ThrowsAdvice(空接口) |
返回通知 | 目标方法返回值后 | AfterReturningAdvice |
一般不使用空接口,AfterReturningAdvice是返回通知,但很多时候都可以当作后置通知使用。
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 目标类、切面类要放到IOC容器中 -->
<context:component-scan base-package="com.chy.mall" />
<!-- UserService的代理。代理的是接口,name一般就使用接口名;代理的是目标类,name使用xxxProxy -->
<bean name="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 指定要代理的接口,如果实现了多个接口,用子元素<list>来写 -->
<property name="proxyInterfaces" value="service.UserService" />
<!-- 指定目标对象 -->
<property name="target" ref="userService" />
<!-- 通过要使用的切面,通过bean的name指定,只能用value,不能用ref -->
<property name="interceptorNames" value="userServiceAspect" />
<!-- 是否直接代理目标类,默认false:代理接口 -->
<!--<property name="proxyTargetClass" value="false" />-->
<!-- 返回的代理对象是否是单例,默认为true -->
<property name="singleton" value="true" />
</bean>
</beans>
说明
示例写的是代理接口,会代理该接口所有的实现类。如果只代理目标类
- 目标类不用实现接口
- 代理中不必配置 proxyInterfaces ,需要将 proxyTargetClass 设置为true
true:直接代理目标类,使用的是cglib动态代理
false:默认值,代理接口,目标类必须实现接口,使用的是jdk动态代理
spring aop需要在配置文件中给每个目标类|接口代理,很繁琐,且配置文件会很庞大。
AspectJ
所需依赖
- spring-aop
- spring-aspects
目标接口
public interface UserService {
void login();
void logout();
}
目标类
@Service
public class UserServiceImpl implements UserService {
@Override
public void login() {
System.out.println("正在执行login()...");
}
@Override
public void logout() {
System.out.println("正在执行logout()...");
}
}
xml配置方式
切面
@Component
public class UserServiceAspect {
/**
* 前置通知要调用的方法
*/
public void before(){
System.out.println("正在执行前置通知...");
}
/**
* 后置通知要调用的方法
*/
public void after(){
System.out.println("正在执行后置通知...");
}
/**
* 返回通知要调用的方法
*/
public void afterReturning(Object obj){
System.out.println("正在执行返回通知...");
System.out.println("目标方法的返回值是:"+obj);
}
/**
* 异常通知要调用的方法
*/
public void afterThrowing(JoinPoint point, Exception e){
System.out.println("异常信息:"+e.getMessage());
}
}
/**
* 环绕通知
*/
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//前增强
System.out.println("正在执行前增强...");
//调用目标方法
Object object=proceedingJoinPoint.proceed();
//后增强
System.out.println("正在执行后增强...");
return object;
}
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 https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 目标类、切面类要放到IOC容器中 -->
<context:component-scan base-package="com.chy.mall" />
<!-- aop配置 -->
<aop:config>
<!-- 全局切入点,所有切面中都可引用。配置切入点只能用id,不能用name -->
<!--<aop:pointcut id="pointCut" expression="execution(* com.chy.mall.service.*.*(..))" />-->
<!-- 一个<aop:aspect>配置一个切面。指定切入点、增强 -->
<aop:aspect ref="userServiceAspect">
<!-- 局部切入点,只能在此切面中引用 -->
<aop:pointcut id="pointCut" expression="execution(* com.chy.mall.service.UserService.*(..))" />
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pointCut" />
<!-- 切入点也可以现配 -->
<!-- <aop:before method="before" pointcut="execution(* com.chy.mall.service.UserService.*(..))"/>-->
<!-- 后置通知 -->
<aop:after method="after" pointcut-ref="pointCut" />
<!-- 返回通知 -->
<!-- 如果返回通知调用的方法中要使用目标方法的返回值,可以用returning指定将目标方法的返回值传递给哪个形参 -->
<aop:after-returning method="afterReturning" pointcut-ref="pointCut" returning="obj"/>
<!-- 异常通知 -->
<!-- 如果要使用捕获的异常对象,可以用throwing指定将异常对象传递给哪个形参 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut" throwing="e"/>
<!-- 环绕通知 -->
<!-- ProceedingJoinPoint是JoinPoint的子类,会自动传入 -->
<!-- <aop:around method="around" pointcut-ref="pointCut" /> -->
</aop:aspect>
</aop:config>
</beans>
注解配置方式
切面
@Component
@Aspect //标识为切面
public class UserServiceAspect {
/**
* 配置切入点。此切入点是局部切入点,只能在当前切面中引用
*/
@Pointcut("execution(* com.chy.mall.service.UserService.*(..))")
private void pointCut(){}
/**
* 前置通知
* 切入点均可现配 @Before("execution(* com.chy.mall.service.UserService.*(..))")
*/
@Before("pointCut()")
public void before(){
System.out.println("正在执行前置通知...");
}
/**
* 后置通知
*/
@After("pointCut()")
public void after(){
System.out.println("正在执行后置通知...");
}
/**
* 返回通知。可将目标方法的返回值传给指定形参
*/
@AfterReturning(value = "pointCut()",returning = "obj" )
public void afterReturning(Object obj){
System.out.println("正在执行返回通知...");
System.out.println("目标方法的返回值是:"+obj);
}
/**
* 异常通知。可将目标方法的抛出的异常传给指定形参
*/
@AfterThrowing(value = "pointCut()",throwing = "e")
public void afterThrowing(JoinPoint point,Exception e){
System.out.println("异常信息:"+e.getMessage());
}
}
/**
* 环绕通知
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//前增强
System.out.println("正在执行前增强...");
//调用目标方法
Object object=proceedingJoinPoint.proceed();
//后增强
System.out.println("正在执行后增强...");
return object;
}
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 https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 组件扫描,扫猫标注bean的注解-->
<context:component-scan base-package="com.chy.mall" />
<!-- 启用AspectJ的注解 -->
<aop:aspectj-autoproxy />
</beans>
总结
1、声明是声明为目标类型,注入是注入代理
@Resource(name="userService") //注入的是代理,按名称注入
private UserService userService; //如果代理的是接口,声明为接口类型;如果代理的目标类,声明为目标类
2、常见问题
<context:component-scan base-package=" " />
没有配置组件扫描,或者包未写全,出现bean未定义异常