目录
附:aop:aspectj-autoproxy(开启注解支持)说明
1、Spring对AOP的支持
Spring的底层已经使用动态代理帮助我们实现了对AOP的支持。
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP
代理可以直接使用容器中的其它bean
实例作为目标对象,这种关系可由IOC
容器的依赖注入提供。
Spring
创建动态代理的规则为:
(1)默认使用Java
动态代理来创建AOP
代理,这样就可以为任何接口实例创建动态代理了。
(2)当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB。
2、使用Spring实现AOP
需要先在父项目的pom.xml
文件中导入AOP
织入依赖包!
pom.xml
文件:
<!-- AOP织入依赖 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2.1、方式一:通过Spring API实现AOP(早期)
(1)在父项目中新建一个模块(Module
),并在其中创建一个普通的Maven子项目,项目名称为spring-09-AOP
。
(2)填写子项目名称和Maven项目GAV,点击Finish
按钮完成子项目创建,等待Maven依赖包导入完毕。
(3)在子项目的java
目录下新建一个com.atangbiji.service
包,并在该包下新建一个UserService
接口及其接口实现类UserServiceImpl
。
UserService.java
文件:
package com.atangbiji.service;
//抽象角色:增删改查业务接口
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
UserServiceImpl.java
文件:
package com.atangbiji.service;
//真实角色:去实现增删改查接口
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户!");
}
public void delete() {
System.out.println("删除了一个用户!");
}
public void update() {
System.out.println("更新了一个用户!");
}
public void select() {
System.out.println("查询了一个用户!");
}
}
(4)在子项目的java
目录下新建一个com.atangbiji.log
包,并在该包下新建两个增强类:一个前置增强(Log
类);一个后置增强(AfterLog
类)。
Log.java
文件:
package com.atangbiji.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
//日志类(即:切面)
//通知类型:前置通知
public class Log implements MethodBeforeAdvice {
// 输入参数:
//(1)method:要执行的目标对象的方法
//(2)args:被调用方法的参数
//(3)target:目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "类的" + method.getName() + "方法被执行了");
}
}
AfterLog.java
文件:
package com.atangbiji.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
//日志类(即:切面)
//通知类型:后置通知
public class AfterLog implements AfterReturningAdvice {
// 输入参数:
//(1)returnValue:返回值
//(2)method:要执行的目标对象的方法
//(3)args:被调用方法的参数
//(4)target:目标对象
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName() + "类的" + method.getName() + "方法,返回值为:" + returnValue);
}
}
(5)在子项目的resource
目录下新建一个applicationContext.xml
配置文件,用于注册bean
和配置AOP
。
applicationContext.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
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就是那些组成我们应用程序的Java对象,由Spring中的IOC容器创建、组装和管理-->
<!--(1)id:bean的唯一标识符。(相当于之前new的对象名)-->
<!--(2)class:bean所对应对象的全限定类名。(全限定名=包名+类名)-->
<!--方式一:通过Spring API实现AOP-->
<!--注册bean(切面及通知类型)-->
<bean id="userServiceImpl" class="com.atangbiji.service.UserServiceImpl"/>
<bean id="log" class="com.atangbiji.log.Log"/>
<bean id="afterLog" class="com.atangbiji.log.AfterLog"/>
<!--配置AOP-->
<aop:config>
<!--切入点(通过expression:表达式匹配要切入的位置)-->
<aop:pointcut id="pointCut" expression="execution(* com.atangbiji.service.UserServiceImpl.*(..))"/>
<!--通知(把log类和afterLog类从pointCut切入点织入)-->
<!--(1)advice-ref:通知的引用;(2)pointcut-ref:切入点的引用-->
<aop:advisor advice-ref="log" pointcut-ref="pointCut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/>
</aop:config>
</beans>
注:注意在xml
配置文件的头部导入AOP
的约束。
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
附:expression
(表达式)的语法格式为:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
其中:(以问号?
结束的部分都是可以省略)
-
modifiers-pattern
表示:方法的访问类型,如public
等; -
ret-type-pattern
表示:方法的返回值类型,如String
表示返回类型是String
,*
表示所有的返回类型; -
declaring-type-pattern
表示:方法的声明类,如com.atangbiji..*
表示com.atangbiji
包及其子包下面的所有类型; -
name-pattern
表示:方法的名称,如add*
表示所有以add
开头的方法名; -
param-pattern
表示:方法的参数类型,name-pattern(param-pattern)
一起表示方法及对应的参数类型,如add()
表示不带参数的add
方法,add(*)
表示带一个任意类型的参数的add
方法,add(*,String)
则表示带两个参数,且第二个参数是String
类型的add
方法; -
throws-pattern
表示:异常类型。
(6)在子项目的src/test/java
目录下,新建一个MyTest
测试类。
MyTest.java
文件:
import com.atangbiji.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
//解析applicationContext.xml文件,IOC容器生成并管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//通过Spring的上下文对象,从IOC容器中获取Bean对象
//注:由于动态代理代理的是接口,因此需要将UserServiceImpl类型强制转换为UserService类型
UserService userServiceImpl = (UserService)context.getBean("userServiceImpl");
//调用Bean对象的方法
userServiceImpl.add();
userServiceImpl.delete();
userServiceImpl.update();
userServiceImpl.select();
}
}
运行测试程序test
,切面织入成功。执行结果如下图所示:
注:由于动态代理代理的是接口(抽象角色),因此需要将UserServiceImpl
类型强制转换为UserService
类型。
2.2、方式二:通过自定义类实现AOP(推荐)
(1)保持spring-09-AOP
子项目com.atangbiji.service
包下的UserService
接口及其接口实现类UserServiceImpl
不变。
注:UserService.java
文件与UserServiceImpl.java
文件参见方式一。
(2)在spring-09-AOP
子项目的java
目录下新建一个com.atangbiji.diy
包,并在该包下新建一个DiyAspect
类,用于自定义切面。
DiyAspect.java
文件:
package com.atangbiji.diy;
//自定义切面
public class DiyAspect {
//通知1
public void before(){
System.out.println("---------方法执行前---------");
}
//通知2
public void after(){
System.out.println("---------方法执行后---------");
}
}
(3)在子项目resource
目录下的applicationContext.xml
配置文件中注册bean
,并配置AOP
。
applicationContext.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
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就是那些组成我们应用程序的Java对象,由Spring中的IOC容器创建、组装和管理-->
<!--(1)id:bean的唯一标识符。(相当于之前new的对象名)-->
<!--(2)class:bean所对应对象的全限定类名。(全限定名=包名+类名)-->
<!--方式二:通过自定义类实现AOP-->
<!--注册bean(切面及通知类型)-->
<bean id="userServiceImpl" class="com.atangbiji.service.UserServiceImpl"/>
<bean id="diy" class="com.atangbiji.diy.DiyAspect"/>
<!--配置AOP-->
<aop:config>
<!--自定义切面的引用-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="diyPointcut" expression="execution(* com.atangbiji.service.UserServiceImpl.*(..))"/>
<!--通知类型(把diy类从diyPointcut切入点织入)-->
<!--(1)method:通知的引用;(2)pointcut-ref:切入点的引用-->
<aop:before method="before" pointcut-ref="diyPointcut"/>
<aop:after method="after" pointcut-ref="diyPointcut"/>
</aop:aspect>
</aop:config>
</beans>
(4)保持子项目的src/test/java
目录下的MyTest
测试类不变,重新运行测试程序test
,切面同样织入成功。执行结果如下图所示:
2.3、方式三:通过注解实现AOP(主流)
(1)保持spring-09-AOP
子项目com.atangbiji.service
包下的UserService
接口及其接口实现类UserServiceImpl
不变。
注:UserService.java
文件与UserServiceImpl.java
文件参见方式一。
(2)在spring-09-AOP
子项目的java
目录下新建一个com.atangbiji.annotation
包,并在该包下新建一个AnnotationAspect
类,用于自定义切面。
AnnotationAspect.java
文件:
package com.atangbiji.annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//使用注解配置自定义切面
@Aspect
public class AnnotationAspect {
//使用注解配置(前置)通知。语法格式:@Before("切入点")
@Before("execution(* com.atangbiji.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("===========方法执行前=========");
}
//使用注解配置(后置)通知。语法格式:@After("切入点")
@After("execution(* com.atangbiji.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("===========方法执行后=========");
}
//使用注解配置(环绕)通知。语法格式:@Around("切入点")
@Around("execution(* com.atangbiji.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("------------环绕前-----------");
//执行目标方法
jp.proceed();
System.out.println("------------环绕后-----------");
}
}
注:在环绕增强(通知)中,我们可以传入一个连接点参数,代表我们要获取处理切入的点。
(3)在子项目resource
目录下的applicationContext.xml
配置文件中注册bean
,并开启注解支持。
applicationContext.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
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就是那些组成我们应用程序的Java对象,由Spring中的IOC容器创建、组装和管理-->
<!--(1)id:bean的唯一标识符。(相当于之前new的对象名)-->
<!--(2)class:bean所对应对象的全限定类名。(全限定名=包名+类名)-->
<!--方式三:通过注解实现AOP-->
<!--注册bean(切面及通知类型)-->
<bean id="userServiceImpl" class="com.atangbiji.service.UserServiceImpl"/>
<bean id="annotationAspect" class="com.atangbiji.annotation.AnnotationAspect"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
</beans>
注:我们也可以通过注解方式注册bean
,为了方便我们理解通过注解实现AOP
,这里我们仍然使用xml
配置文件的方式注册bean
。
(4)保持子项目的src/test/java
目录下的MyTest
测试类不变,重新运行测试程序test
,切面同样织入成功。执行结果如下图所示:
附:aop:aspectj-autoproxy(开启注解支持)说明
-
通过
aop
命名空间的<aop:aspectj-autoproxy />
声明自动为spring
容器中那些配置@aspectJ
切面的bean
创建代理,织入切面。当然,spring
在内部依旧采用AnnotationAwareAspectJAutoProxyCreator
进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />
隐藏起来了。 -
有一个
proxy-target-class
属性,默认为false
,表示使用jdk
动态代理织入增强,当配为时,表示使用CGLib
动态代理技术织入增强。不过即使proxy-target-class
设置为false
,如果目标类没有声明接口,则spring
将自动使用CGLib
动态代理。