本篇主要说明下使用 AOP
在 XML
和注解中是如何配置的,以及各个标签或注解的作用
XML入门案例
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
创建需要的接口及其实现类
public interface AccountService {
void transform();
void transform1(int id);
void transform2(int id,String name);
}
public class AccountServiceImpl implements AccountService {
@Override
public void transform() {
System.out.println("这是业务代码");
//int i = 1 / 0;
}
@Override
public void transform1(int id) {
System.out.println("这是业务代码1");
}
@Override
public void transform2(int id,String name) {
System.out.println("这是业务代码2");
}
}
创建通知类
public class Logger {
public void beforeLog(){
System.out.println("前置通知");
}
public void returningLog(){
System.out.println("后置通知");
}
public void throwingLog(){
System.out.println("异常通知");
}
public void afterLog(){
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">
<bean id="accountService" class="com.service.impl.AccountServiceImpl"></bean>
<bean id="logger" class="com.log.Logger"></bean>
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="beforeLog" pointcut="execution(* com.service.impl.*.*(..))"></aop:before>
<aop:after-returning method="returningLog" pointcut="execution(* com.service.impl.*.*(..))"></aop:after-returning>
<aop:after-throwing method="throwingLog" pointcut="execution(* com.service.impl.*.*(..))"></aop:after-throwing>
<aop:after method="afterLog" pointcut="execution(* com.service.impl.*.*(..))"></aop:after>
</aop:aspect>
</aop:config>
</beans>
主方法进行测试
public class ApplicationTest {
public static void main(String[] args) {
// 使用XML配置实现AOP
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
accountService.transform();
}
}
AOP
配置文件中的标签说明
-
aop:config
标签:表明开始AOP
配置 -
aop:aspect
标签:表明配置切面
-
aop:before
标签:表示前置通知 -
aop:after-returning
标签:表示后置通知 -
aop:after-throwing
标签:表示异常通知 -
aop:after
标签:表示最终通知 -
aop:around
标签:表示环绕通知,环绕通知的具体使用最后会说
前两个标签的属性都相对简单,主要说明下后面几个标签中的属性:
method
属性:用于指定某个类中的哪个方法是其标签对应的通知,例如aop:before
标签 method
属性对应的方法就是前置通知
pointcut
属性:用于指定切入点表达式,该表达式的含义指的是对业务层中的哪些方法增强
切入点表达式
切入点表达式的写法
execution(表达式)
表达式格式:
访问修饰符 返回值 包名.包名.类名(全限定类名).方法名(参数列表)
标准表达式
public void com.service.impl.AccountServiceImpl.transform()
访问修饰符可以省略
void com.service.impl.AccountServiceImpl.transform()
返回值可以使用通配符,表示任意返回值
* com.service.impl.AccountServiceImpl.transform()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个 *.
* *.*.*.AccountServiceImpl.transform()
包名可以使用 ..
表示当前包及其子包
* *..AccountServiceImpl.transform()
类名和方法名都可以使用 * 来实现通配
* *..AccountServiceImpl.*()
<!--AccountServiceImpl下的所有无参方法都实现了增强-->
* *..*.transform()
<!--任意包下的任意类中的任意方法都实现了增强-->
* *..*.*()
基本数据类型的参数列表直接写数据类型,如 int
* com.service.impl.*.*(int)
引用数据类型写全限定类名,如 java.lang.String
* com.service.impl.*.*(java.lang.String)
可以使用通配符标识任意类型,但是必须有参数
* com.service.impl.*.*(*,*)
可以使用 ..
表示有无参数均可,有参数可以是任意类型
* com.service.impl.*.*(..)
环绕通知
配置文件
<bean id="accountService" class="com.service.impl.AccountServiceImpl"></bean>
<bean id="logger" class="com.log.Logger"></bean>
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<aop:around method="afterLog" pointcut="execution(* com.service.impl.*.*(..))"></aop:around>
</aop:aspect>
</aop:config>
主方法测试
public class ApplicationTest {
public static void main(String[] args) {
// 使用XML配置实现AOP
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
accountService.transform();
}
}
通过执行结果发现,当我们配置了环绕通知之后。切入点方法没有执行,但是通知方法却执行了。
通过对比动态代理中环绕通知代码,发现动态代理的环绕通知有明确的切入点方法代用,而我们使用的该配置中没有
解决方式是使用 Spring
框架为我们提供的 ProceedingJoinPoint
接口。在该接口中有一个方法 proceed
,此方法就相当于明确调用切入点方法,该接口就可以作为环绕通知的方法参数。在程序执行时框架就会为我们提供接口的实现类供我们使用
环绕通知是 Spring
框架为我们提供一个可以在代码中手动控制增强方法何时执行的方式
<!--AOP 配置-->
<aop:config>
<aop:aspect id="logAdvice" ref="logger">
<aop:around method="aroundPrintLog" pointcut="execution(* com.service.impl.*.*(..))"></aop:around>
</aop:aspect>
</aop:config>
环绕通知方法
public class Logger {
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtvalue = null;
try{
Object[] args = pjp.getArgs(); //得到方法执行所需的参数
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了-----前置");
rtvalue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了-----后置");
return rtvalue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了-----异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了-----最终");
}
}
}
运行结果检测
public class ApplicationTest {
public static void main(String[] args) {
// 使用XML配置实现AOP
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
accountService.transform();
}
}
注解入门案例
引入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
创建需要的接口及其实现类
public interface AccountService {
void transform();
}
@Service(value = "accountService")
public class AccountServiceImpl implements AccountService {
@Override
public void transform() {
System.out.println("这是业务代码");
//int i = 1 / 0;
}
}
创建通知类
@Aspect //表示当前类是一个切面类
@Component("logger")
public class Logger {
// 切入点表达式
@Pointcut("execution(* com.service.impl.*.*(..))")
private void pt1(){}
// 前置通知
@Before("pt1()")
public void beforeLog(){
System.out.println("前置通知");
}
// 后置通知
@AfterReturning("pt1()")
public void returningLog(){
System.out.println("后置通知");
}
// 异常通知
@AfterThrowing("pt1()")
public void throwingLog(){
System.out.println("异常通知");
}
// 最终通知
@After("pt1()")
public void afterLog(){
System.out.println("最终通知");
}
// 环绕通知
@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtvalue = null;
try{
Object[] args = pjp.getArgs(); //得到方法执行所需的参数
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了-----前置");
rtvalue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了-----后置");
return rtvalue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了-----异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了-----最终");
}
}
}
主方法进行测试
@ComponentScan(basePackages = "com")
@Configuration
@EnableAspectJAutoProxy
public class ApplicationTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationTest.class);
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
accountService.transform();
}
}
常用注解