Spring框架是在两个核心概念的基础上发展起来的;依赖注入以及面向切面编程(Spring Aop)。
目录
面向切面编程
我们已经看到了Spring注射依赖今天我们将探讨面向切面编程的核心概念,以及如何使用Spring框架实现它。
SpringAOP概述
大多数企业应用程序都有一些常见的横切关注点,它们适用于不同类型的对象和模块。一些常见的横切关注点是日志记录、事务管理、数据验证等。
在面向对象编程中,应用程序的模块化是通过类来实现的,而在面向切面的编程中,应用程序的模块化是通过方面来实现的,它们被配置成跨越不同的类。
从普通面向对象的程序设计模型中提取出面向对象的直接程序设计方法。例如,我们可以有一个单独的类来记录日志,但是函数类必须调用这些方法来实现整个应用程序的日志记录。
面向切面编程的核心概念
在深入研究Spring Aop实现的实现之前,我们应该理解AOP的核心概念。
- 方面:方面是实现跨多个类(如事务管理)的企业应用程序关注点的类。方面可以是通过springxml配置配置的普通类,也可以使用Spring Aspectj集成将类定义为方面,使用
@Aspect
annotation. - 连接点:连接点是应用程序中的特定点,如方法执行、异常处理、更改对象变量值等。在Spring AOP中,连接点始终是方法的执行。
- 建议:建议是针对特定连接点执行的操作。就编程而言,它们是在应用程序中达到具有匹配切入点的特定连接点时执行的方法。你可以把建议看作Struts2拦截器或Servlet筛选器.
- 切入点:Pointcut是与连接点匹配的表达式,用于确定是否需要执行advice。切入点使用与连接点匹配的不同类型的表达式,Spring框架使用AspectJ切入点表达式语言。
- 目标对象:它们是应用通知的对象。Spring Aop是使用运行时代理实现的,因此这个对象始终是一个代理对象。这意味着在运行时创建一个子类,其中目标方法被重写,并根据它们的配置包含建议。
- AOP代理:Spring Aop实现使用JDK动态代理创建带有目标类和通知调用的代理类,这些类称为AOP代理类。我们还可以通过将CGLIB代理添加为Spring Aop项目中的依赖项来使用它。
- 编织:这是将方面与其他对象链接以创建建议的代理对象的过程。这可以在编译时、加载时或运行时完成。Spring Aop在运行时执行编织。
AOP建议类型
基于advice的执行策略,它们有以下几种类型。
- 建议之前:这些通知在执行连接点方法之前运行。我们可以利用
@Before
annotation to mark an advice type as Before advice. - 经过(最后)建议:在连接点方法完成执行后执行的通知,无论是正常执行还是引发异常。我们可以使用
@After
annotation. - 返回建议后:有时我们希望只有在连接点方法正常执行时才执行advice方法。我们可以利用
@AfterReturning
annotation to mark a method as after returning advice. - 在提出建议之后:只有当连接点方法抛出异常时,才执行此建议,我们可以使用它声明性地回滚事务。我们使用
@AfterThrowing
annotation for this type of advice. - 周围的建议:这是最重要和最有力的建议。这个建议围绕着连接点方法,我们也可以选择是否执行连接点方法。我们可以编写在执行连接点方法之前和之后执行的建议代码。around advice负责调用连接点方法并在方法返回值时返回值。我们使用
@Around
annotation to create around advice methods.
上面提到的几点听起来可能令人困惑,但是当我们看一下Spring Aop的实现时,事情会变得更清楚。让我们开始使用AOP实现创建一个简单的Spring项目。Spring支持使用AspectJ注解来创建方面,为了简单起见,我们将使用它。上面所有的AOP注解都在中定义org.aspectj.lang.annotation
package.
Spring工具套件提供了关于方面的有用信息,所以我建议您使用它。如果你不熟悉STS,我建议你看看SpringMVC教程我已经解释了如何使用它。
Spring AOP示例
创建一个新的简单springmaven项目,以便所有Spring核心库都包含在pom.xml文件文件,我们不需要显式地包含它们。我们的最终项目将如下图所示,我们将详细研究Spring核心组件和方面实现。
Spring Aop AspectJ依赖项
springframework默认提供AOP支持,但是由于我们使用AspectJ注解来配置方面和建议,所以我们需要在pom.xml文件文件。
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.samples</groupId>
<artifactId>SpringAOPExample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<!-- Generic properties -->
<java.version>1.6</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Spring -->
<spring-framework.version>4.0.2.RELEASE</spring-framework.version>
<!-- Logging -->
<logback.version>1.0.13</logback.version>
<slf4j.version>1.7.5</slf4j.version>
<!-- Test -->
<junit.version>4.11</junit.version>
<!-- AspectJ -->
<aspectj.version>1.7.4</aspectj.version>
</properties>
<dependencies>
<!-- Spring and Transactions -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Logging with SLF4J & LogBack -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>runtime</scope>
</dependency>
<!-- AspectJ dependencies -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</project>
注意,我添加了aspectjrt
and aspectjtools
dependencies (version 1.7.4) in the project. Also I have updated the Spring framework version to be the latest one as of date i.e 4.0.2.RELEASE.
模型类
让我们创建一个简单的javabean,我们将在我们的示例中使用一些附加方法。
雇员.java代码:
package com.journaldev.spring.model;
import com.journaldev.spring.aspect.Loggable;
public class Employee {
private String name;
public String getName() {
return name;
}
@Loggable
public void setName(String nm) {
this.name=nm;
}
public void throwException(){
throw new RuntimeException("Dummy Exception");
}
}
你注意到了吗集合名()方法的注解为Loggable
annotation. It is a 自定义java注解由我们在项目中定义。稍后我们将研究它的用法。
服务等级
让我们创建一个服务类来使用employeebean。
雇员服务.java代码:
package com.journaldev.spring.service;
import com.journaldev.spring.model.Employee;
public class EmployeeService {
private Employee employee;
public Employee getEmployee(){
return this.employee;
}
public void setEmployee(Employee e){
this.employee=e;
}
}
我本可以使用Spring注解将其配置为Spring组件,但是我们将在本项目中使用基于XML的配置。EmployeeService类非常标准,只为我们提供了一个EmployeeBean的访问点。
使用AOP的Spring Bean配置
如果您使用的是STS,您可以选择创建&8220;Spring Bean配置文件&8221;并选择AOP schema namespace,但是如果您使用的是其他IDE,则可以简单地将其添加到Spring Bean配置文件中。
我的项目bean配置文件如下所示。
spring.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="https://www.springframework.org/schema/aop"
xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-4.0.xsd
https://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- Enable AspectJ style of Spring AOP -->
<aop:aspectj-autoproxy />
<!-- Configure Employee Bean and initialize it -->
<bean name="employee">
<property name="name" value="Dummy Name"></property>
</bean>
<!-- Configure EmployeeService bean -->
<bean name="employeeService">
<property name="employee" ref="employee"></property>
</bean>
<!-- Configure Aspect Beans, without this Aspects advices wont execute -->
<bean name="employeeAspect" />
<bean name="employeeAspectPointcut" />
<bean name="employeeAspectJoinPoint" />
<bean name="employeeAfterAspect" />
<bean name="employeeAroundAspect" />
<bean name="employeeAnnotationAspect" />
</beans>
要在Spring Bean中使用Spring AOP,我们需要执行以下操作:
- 声明AOP命名空间xmlns:aop=&&8221;https://www.springframework.org/schema/aop&8221;
- 添加aop:aspectj autoproxy元素在运行时使用自动代理启用Spring Aspectj支持
- 将方面类配置为其他Spring Bean
您可以看到我在Spring Bean配置文件中定义了很多方面,现在是时候逐一研究它们了。
方面示例之前的Spring Aop
雇员方面.java代码:
package com.journaldev.spring.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class EmployeeAspect {
@Before("execution(public String getName())")
public void getNameAdvice(){
System.out.println("Executing Advice on getName()");
}
@Before("execution(* com.journaldev.spring.service.*.get*())")
public void getAllAdvice(){
System.out.println("Service method getter called");
}
}
上述方面的重点是:
- 方面课程必须有
@Aspect
annotation. - @Before注解用于创建Before advice
- 传入的字符串参数
@Before
annotation is the Pointcut expression - getNameAdvice()任何带有签名的Spring Bean方法都将执行advice
public String getName()
. This is a very important point to remember, if we will create Employee bean using new operator the advices will not be applied. Only when we will use ApplicationContext to get the bean, advices will be applied. - 我们可以在切入点表达式中使用星号(*)作为通配符,getAllAdvice()将应用于中的所有类
com.journaldev.spring.service
package whose name starts withget
and doesn’t take any arguments.
在我们研究了所有不同类型的建议之后,我们将在一个测试类中查看实际的建议。
Spring Aop切入点方法及其重用
有时我们必须在多个地方使用相同的切入点表达式,我们可以使用@Pointcut
annotation and then use it as an expression in the advices.
相对长度单位ployeeAspectPointcut.java代码:
package com.journaldev.spring.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class EmployeeAspectPointcut {
@Before("getNamePointcut()")
public void loggingAdvice(){
System.out.println("Executing loggingAdvice on getName()");
}
@Before("getNamePointcut()")
public void secondAdvice(){
System.out.println("Executing secondAdvice on getName()");
}
@Pointcut("execution(public String getName())")
public void getNamePointcut(){}
@Before("allMethodsPointcut()")
public void allServiceMethodsAdvice(){
System.out.println("Before executing service method");
}
//Pointcut to execute on all the methods of classes in a package
@Pointcut("within(com.journaldev.spring.service.*)")
public void allMethodsPointcut(){}
}
上面的例子非常清楚,我们在advice注解参数中使用的不是表达式,而是方法名。
Spring Aop连接点和建议参数
我们可以在advice方法中使用JoinPoint作为参数,并使用它来获得方法签名或目标对象。
我们可以利用args()
expression in the pointcut to be applied to any method that matches the argument pattern. If we use this, then we need to use the same name in the advice method from where the argument type is determined. We can use 通用对象也在建议的争论中。
电磁脉冲loyeaspectjoinpoint.java代码:
package com.journaldev.spring.aspect;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class EmployeeAspectJoinPoint {
@Before("execution(public void com.journaldev.spring.model..set*(*))")
public void loggingAdvice(JoinPoint joinPoint){
System.out.println("Before running loggingAdvice on method="+joinPoint.toString());
System.out.println("Agruments Passed=" + Arrays.toString(joinPoint.getArgs()));
}
//Advice arguments, will be applied to bean methods with single String argument
@Before("args(name)")
public void logStringArguments(String name){
System.out.println("String argument passed="+name);
}
}
Spring Aop After Advice示例
让我们看看一个简单的aspect类,它有After、After-throw和After-return-advice。
EmployeeAfterAspect.java代码:
package com.journaldev.spring.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class EmployeeAfterAspect {
@After("args(name)")
public void logStringArguments(String name){
System.out.println("Running After Advice. String argument passed="+name);
}
@AfterThrowing("within(com.journaldev.spring.model.Employee)")
public void logExceptions(JoinPoint joinPoint){
System.out.println("Exception thrown in Employee Method="+joinPoint.toString());
}
@AfterReturning(pointcut="execution(* getName())", returning="returnString")
public void getNameReturningAdvice(String returnString){
System.out.println("getNameReturningAdvice executed. Returned String="+returnString);
}
}
我们可以利用within
in pointcut expression to apply the advice to all the methods in the class. We can use @AfterReturning advice to get the object returned by the advised method.
We have throwException()通过异常()方法在Employee bean中展示使用后抛出建议。
围绕方面的Spring Aop示例
如前所述,我们可以使用Around aspect来在前后削减方法执行。我们可以用它来控制建议的方法是否执行。我们也可以检查返回值并更改它。这是最有力的建议,需要正确应用。
EmployeeAroundAspect.java代码:
package com.journaldev.spring.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class EmployeeAroundAspect {
@Around("execution(* com.journaldev.spring.model.Employee.getName())")
public Object employeeAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("Before invoking getName() method");
Object value = null;
try {
value = proceedingJoinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("After invoking getName() method. Return value="+value);
return value;
}
}
Around advice总是需要ProceedingJoinPoint作为参数,我们应该使用它的ProceedingJoinPoint()方法来调用目标对象adviced方法。
如果advised方法正在返回某些内容,则它的建议责任是将其返回到调用程序。对于void方法,advice方法可以返回null。
由于around advice绕过advice方法,我们可以控制方法的输入和输出以及它的执行行为。
带有自定义注解切入点的Spring通知
如果您查看以上所有的advice切入点表达式,那么它们很可能会被应用到其他一些不需要的bean中。例如,有人可以用getName()方法定义一个新的SpringBean,并且建议将开始应用于它,即使它不是有意的。这就是为什么我们应该尽可能缩小切入点表达式的范围。
另一种方法是创建自定义注解,并在我们希望应用建议的地方注解方法。这就是让员工集合名()方法用@Loggable注解注解。
springframework@Transactional annotation就是这种方法的一个很好的例子Spring事务管理.
可登录.java代码:
package com.journaldev.spring.aspect;
public @interface Loggable {
}
雇员OyeAnnotationAspect.java代码:
package com.journaldev.spring.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class EmployeeAnnotationAspect {
@Before("@annotation(com.journaldev.spring.aspect.Loggable)")
public void myAdvice(){
System.out.println("Executing myAdvice!!");
}
}
myAdvice()方法只通知setName()方法。这是一种非常安全的方法,每当我们想对任何方法应用建议时,我们只需要用Loggable注解对其进行注解。
Spring Aop XML配置
我总是喜欢注解,但是我们也可以选择在spring配置文件中配置方面。例如,假设我们有一个类,如下所示。
电磁脉冲loyeeXMLConfigAspect.java代码:
package com.journaldev.spring.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
public class EmployeeXMLConfigAspect {
public Object employeeAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("EmployeeXMLConfigAspect:: Before invoking getName() method");
Object value = null;
try {
value = proceedingJoinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("EmployeeXMLConfigAspect:: After invoking getName() method. Return value="+value);
return value;
}
}
我们可以通过在Spring Bean配置文件中包含以下配置来配置它。
<bean name="employeeXMLConfigAspect" />
<!-- Spring AOP XML Configuration -->
<aop:config>
<aop:aspect ref="employeeXMLConfigAspect" id="employeeXMLConfigAspectID" order="1">
<aop:pointcut expression="execution(* com.journaldev.spring.model.Employee.getName())" id="getNamePointcut"/>
<aop:around method="employeeAroundAdvice" pointcut-ref="getNamePointcut" arg-names="proceedingJoinPoint"/>
</aop:aspect>
</aop:config>
AOP-xml-config元素的用途从它们的名称中就很清楚了,所以我不想详细介绍它。
Spring AOP示例
让我们有一个简单的Spring程序,看看所有这些方面是如何贯穿bean方法的。
SpringMain.java代码:
package com.journaldev.spring.main;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.journaldev.spring.service.EmployeeService;
public class SpringMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
EmployeeService employeeService = ctx.getBean("employeeService", EmployeeService.class);
System.out.println(employeeService.getEmployee().getName());
employeeService.getEmployee().setName("Pankaj");
employeeService.getEmployee().throwException();
ctx.close();
}
}
现在,当我们执行上述程序时,我们得到以下输出。
Mar 20, 2014 8:50:09 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Thu Mar 20 20:50:09 PDT 2014]; root of context hierarchy
Mar 20, 2014 8:50:09 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring.xml]
Service method getter called
Before executing service method
EmployeeXMLConfigAspect:: Before invoking getName() method
Executing Advice on getName()
Executing loggingAdvice on getName()
Executing secondAdvice on getName()
Before invoking getName() method
After invoking getName() method. Return value=Dummy Name
getNameReturningAdvice executed. Returned String=Dummy Name
EmployeeXMLConfigAspect:: After invoking getName() method. Return value=Dummy Name
Dummy Name
Service method getter called
Before executing service method
String argument passed=Pankaj
Before running loggingAdvice on method=execution(void com.journaldev.spring.model.Employee.setName(String))
Agruments Passed=[Pankaj]
Executing myAdvice!!
Running After Advice. String argument passed=Pankaj
Service method getter called
Before executing service method
Exception thrown in Employee Method=execution(void com.journaldev.spring.model.Employee.throwException())
Exception in thread "main" java.lang.RuntimeException: Dummy Exception
at com.journaldev.spring.model.Employee.throwException(Employee.java:19)
at com.journaldev.spring.model.Employee$$FastClassBySpringCGLIB$$da2dc051.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:711)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
at com.journaldev.spring.model.Employee$$EnhancerBySpringCGLIB$$3f881964.throwException(<generated>)
at com.journaldev.spring.main.SpringMain.main(SpringMain.java:17)
您可以看到,根据它们的切入点配置,通知正在一个接一个地执行。您应该逐个配置它们,以避免混淆。
那是为了Spring Aop示例教程,我希望您通过Spring学习了AOP的基本知识,并能从示例中学习更多。从下面的链接下载示例项目,并与它一起玩。