**Spring Framework 是基于两个核心概念开发的 - 依赖注射和面向编程( Spring AOP)。
春天AOP
我们已经看到了 [春季依赖注射]( / 社区 / 教程 / 春季依赖注射春季依赖注射示例与注释和XML配置
)是如何工作的,今天我们将研究面向编程的核心概念,以及我们如何使用春季框架来实现它。
春天AOP概览
大多数企业应用都有某些常见的交叉问题,适用于不同类型的对象和模块。一些常见的交叉问题是记录、交易管理、数据验证等。在对象导向编程中,应用的模块性是通过类实现的,而在面向编程中,应用模块性是通过方面实现的,并且它们被配置为通过不同类切割。春季AOP从类中取出交叉任务的直接依赖性,而我们无法通过正常的面向编程模型实现。
面向编程的核心概念
在深入实施春季AOP实施之前,我们应该了解AOP的核心概念。
- 联合国 ** 方面**:一个方面是执行企业应用程序关切的类别,它跨越多个类别,例如交易管理。 方面可以是通过 Spring XML 配置配置的普通类,也可以使用 Spring ASpectJ 集成,使用 QQASpect 注释定义一个类为 Spect.
- Join Point:相接点是应用程序中的特定点,如方法执行,例外处理,改变对象可变值等. 在 Spring AOP 中,相接点总是执行方法.
- ** 意见**:建议是指为某一特定联合点采取的行动。 在编程方面,它们是当应用程序中达到匹配点剪接的某个结合点时被执行的方法. 您可以将建议视为Struts2截取器 "Struts 2 有自定义认证拦截实例的拦截教程"或 [Servlet Filters] (/community/tutors/java- servlet-filter-example-tutorial] ) 或 [服务过滤器](/社区/tourises/java- servlet-filter-example-troduction) 被禁用自定义的被禁用语言. "Java Servlet过滤器示例教程"). ( (英语). 4. Pointcut:Pointcut是指与加分相匹配的表达式,以确定是否需要执行建议. Pointcut使用与相接点相匹配的不同种类的表达式,Spring框架使用AspectJ pointcut表达式语言.
- ** 目标对象**:它们是执行建议的对象。 Spring AOP是使用运行时代理执行的,因此此对象总是被代理的对象. 意味着在运行时创建子类,其中目标方法被覆盖,并根据它们的配置包含建议. () (英语). AOP代理:Spring AOP执行使用JDK动态代理创建有目标类和建议引用的代理类,这些被称为AOP代理类. 我们还可以通过在Spring AOP项目中添加CGLIB代理作为依赖性. () (英语). 7. Weaving:它是将各方面与其他对象联系起来的过程来创建被推荐的代理对象. 这可以在编译时间,加载时间或运行时间完成. Spring AOP 运行时进行编织( (英语)
AOP 建议类型
根据咨询的执行策略,它们属于以下类型。
- 联合国 ** 咨询前**:这些咨询在执行联合点方法之前进行。 我们可以使用"在注释前"标记一个建议类型作为建议前.
- ** 在(最后)建议之后**: 连接点方法完成执行后被执行的建议, 无论是正常执行还是丢弃例外 。 我们可以使用
注释后
创建建议 。 。 ** 返回后的建议**:有时我们想要执行建议方法,只有在连接点方法正常执行时才会执行。 我们可以使用"返回后注释"标记一个方法作为返回后的建议. - ** 提出咨询意见后**: 这个建议只有在加入点方法抛出例外时才能被执行,我们可以利用它来公开回滚交易. 我们使用
扔出之后
的注释来提供这类建议。 5。 ** 全面建议**:这是最重要和最有力的建议。 这个建议环绕了接合点方法,我们也可以选择是否执行接合点方法. 我们可以写出建议代码,在连接点方法执行前后执行. 如果结合点法正在返回某物,则绕过建议来引用结合点法并返回该等值. 我们用 " 周而复始 " 的注释来制作咨询方法。 (_) (英语)
上面提到的点可能听起来很困惑,但当我们看看春季AOP的实施时,事情会变得更清楚。让我们开始创建一个简单的春季项目,使用AOP的实现。春天提供了使用AspectJ注释来创建方面面的支持,我们将用它来简化。上面的所有AOP注释都是在org.aspectj.lang.annotation
包中定义的。 春季工具套件提供了有关方面有用的信息,所以我建议您使用它。
AOP 春季示例
Create a new Simple Spring Maven project so that all the Spring Core libraries are included in the pom.xml files and we don't need to include them explicitly. Our final project will look like the below image, we will look into the Spring core components and Aspect implementations in detail.
春季AOP AspectJ依赖
Spring framework 默认提供 AOP 支持,但由于我们正在使用 AspectJ 注释来配置方面和建议,我们需要将它们包含在 pom.xml 文件中。
1<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
2 xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
3 <modelVersion>4.0.0</modelVersion>
4 <groupId>org.springframework.samples</groupId>
5 <artifactId>SpringAOPExample</artifactId>
6 <version>0.0.1-SNAPSHOT</version>
7
8 <properties>
9
10 <!-- Generic properties -->
11 <java.version>1.6</java.version>
12 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
13 <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
14
15 <!-- Spring -->
16 <spring-framework.version>4.0.2.RELEASE</spring-framework.version>
17
18 <!-- Logging -->
19 <logback.version>1.0.13</logback.version>
20 <slf4j.version>1.7.5</slf4j.version>
21
22 <!-- Test -->
23 <junit.version>4.11</junit.version>
24
25 <!-- AspectJ -->
26 <aspectj.version>1.7.4</aspectj.version>
27
28 </properties>
29
30 <dependencies>
31 <!-- Spring and Transactions -->
32 <dependency>
33 <groupId>org.springframework</groupId>
34 <artifactId>spring-context</artifactId>
35 <version>${spring-framework.version}</version>
36 </dependency>
37 <dependency>
38 <groupId>org.springframework</groupId>
39 <artifactId>spring-tx</artifactId>
40 <version>${spring-framework.version}</version>
41 </dependency>
42
43 <!-- Logging with SLF4J & LogBack -->
44 <dependency>
45 <groupId>org.slf4j</groupId>
46 <artifactId>slf4j-api</artifactId>
47 <version>${slf4j.version}</version>
48 <scope>compile</scope>
49 </dependency>
50 <dependency>
51 <groupId>ch.qos.logback</groupId>
52 <artifactId>logback-classic</artifactId>
53 <version>${logback.version}</version>
54 <scope>runtime</scope>
55 </dependency>
56
57 <!-- AspectJ dependencies -->
58 <dependency>
59 <groupId>org.aspectj</groupId>
60 <artifactId>aspectjrt</artifactId>
61 <version>${aspectj.version}</version>
62 <scope>runtime</scope>
63 </dependency>
64 <dependency>
65 <groupId>org.aspectj</groupId>
66 <artifactId>aspectjtools</artifactId>
67 <version>${aspectj.version}</version>
68 </dependency>
69 </dependencies>
70</project>
请注意,我已经在项目中添加了aspectjrt
和aspectjtools
依赖(版本 1.7.4);我也已经更新了春季框架版本,以便它是最新版本,即 4.0.2.RELEASE。
类型模型
让我们创建一个简单的 java 粒子,我们将为我们的例子使用一些额外的方法。
1package com.journaldev.spring.model;
2
3import com.journaldev.spring.aspect.Loggable;
4
5public class Employee {
6
7 private String name;
8
9 public String getName() {
10 return name;
11 }
12
13 @Loggable
14 public void setName(String nm) {
15 this.name=nm;
16 }
17
18 public void throwException(){
19 throw new RuntimeException("Dummy Exception");
20 }
21}
您是否注意到 setName() 方法是用可记录
的注释注释的? 这是我们在项目中定义的 [自定义的 java 注释](/community/tutorials/java-annotations Java 注释教程与自定义的注释示例和使用反射的解析
) 我们将在稍后研究其使用情况。
服务类
让我们创建一个服务类以使用 Employee bean. EmployeeService.java 代码:
1package com.journaldev.spring.service;
2
3import com.journaldev.spring.model.Employee;
4
5public class EmployeeService {
6
7 private Employee employee;
8
9 public Employee getEmployee(){
10 return this.employee;
11 }
12
13 public void setEmployee(Employee e){
14 this.employee=e;
15 }
16}
我可以使用春季注释来配置它作为春季组件,但我们将在这个项目中使用基于XML的配置。
使用 AOP 的 Spring Bean 配置
如果您正在使用STS,您可以选择创建Spring Bean Configuration File
并选择AOP方案名称空间,但如果您正在使用一些其他IDE,您可以简单地将其添加到春豆配置文件中。
1<?xml version="1.0" encoding="UTF-8"?>
2<beans xmlns="https://www.springframework.org/schema/beans"
3 xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
4 xmlns:aop="https://www.springframework.org/schema/aop"
5 xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-4.0.xsd
6 https://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
7
8<!-- Enable AspectJ style of Spring AOP -->
9<aop:aspectj-autoproxy />
10
11<!-- Configure Employee Bean and initialize it -->
12<bean name="employee" class="com.journaldev.spring.model.Employee">
13 <property name="name" value="Dummy Name"></property>
14</bean>
15
16<!-- Configure EmployeeService bean -->
17<bean name="employeeService" class="com.journaldev.spring.service.EmployeeService">
18 <property name="employee" ref="employee"></property>
19</bean>
20
21<!-- Configure Aspect Beans, without this Aspects advices wont execute -->
22<bean name="employeeAspect" class="com.journaldev.spring.aspect.EmployeeAspect" />
23<bean name="employeeAspectPointcut" class="com.journaldev.spring.aspect.EmployeeAspectPointcut" />
24<bean name="employeeAspectJoinPoint" class="com.journaldev.spring.aspect.EmployeeAspectJoinPoint" />
25<bean name="employeeAfterAspect" class="com.journaldev.spring.aspect.EmployeeAfterAspect" />
26<bean name="employeeAroundAspect" class="com.journaldev.spring.aspect.EmployeeAroundAspect" />
27<bean name="employeeAnnotationAspect" class="com.journaldev.spring.aspect.EmployeeAnnotationAspect" />
28
29</beans>
要在春豆中使用春AOP,我们需要做到以下几点:
- 声明 AOP 名称空间为 xmlns:aop="https://www.springframework.org/schema/aop"
- 添加 aop:aspectj-autoproxy 元素以在运行时启用 Spring AspectJ 的自动代理支持
- 配置 Aspect 类作为其他 Spring 豆
你可以看到,我在春豆配置文件中定义了很多方面,是时候逐一研究它们了。
春季AOP前视角示例
Java 代码:
1package com.journaldev.spring.aspect;
2
3import org.aspectj.lang.annotation.Aspect;
4import org.aspectj.lang.annotation.Before;
5
6@Aspect
7public class EmployeeAspect {
8
9 @Before("execution(public String getName())")
10 public void getNameAdvice(){
11 System.out.println("Executing Advice on getName()");
12 }
13
14 @Before("execution(* com.journaldev.spring.service.*.get*())")
15 public void getAllAdvice(){
16 System.out.println("Service method getter called");
17 }
18}
上面的类别中的重要点是:
- Aspect类必须有
@Aspect
注释 - @Before 注释用于创建 Before advice
- 在
@Before
注释中传递的字符串参数是 Pointcut 表达式 - getNameAdvice() 建议将执行任何 Spring Bean 方法的签名
public String getName()
. 这是一个非常重要的要记住的点,如果我们将使用新操作员创建 Employee bean 建议不会应用。
我们将在测试课上看看行动中的建议,我们已经研究了所有不同类型的建议。
春季AOP点击方法和重复使用
有时我们必须在多个地方使用相同的 Pointcut 表达式,我们可以创建一个空的方法与 `@Pointcut' 注释,然后在建议中使用它作为一个表达式。
1package com.journaldev.spring.aspect;
2
3import org.aspectj.lang.annotation.Aspect;
4import org.aspectj.lang.annotation.Before;
5import org.aspectj.lang.annotation.Pointcut;
6
7@Aspect
8public class EmployeeAspectPointcut {
9
10 @Before("getNamePointcut()")
11 public void loggingAdvice(){
12 System.out.println("Executing loggingAdvice on getName()");
13 }
14
15 @Before("getNamePointcut()")
16 public void secondAdvice(){
17 System.out.println("Executing secondAdvice on getName()");
18 }
19
20 @Pointcut("execution(public String getName())")
21 public void getNamePointcut(){}
22
23 @Before("allMethodsPointcut()")
24 public void allServiceMethodsAdvice(){
25 System.out.println("Before executing service method");
26 }
27
28 //Pointcut to execute on all the methods of classes in a package
29 @Pointcut("within(com.journaldev.spring.service.*)")
30 public void allMethodsPointcut(){}
31
32}
上面的示例非常清楚,而不是表达式,我们在提示注释参数中使用方法名称。
春季AOP JoinPoint和建议论点
我们可以使用 JoinPoint 作为建议方法的参数,并使用它来获取方法签名或目标对象。我们可以在点击中使用 args()
表达式来应用到匹配参数模式的任何方法。如果我们使用这种方法,那么我们需要在建议方法中使用相同的名称来确定参数类型。我们也可以在建议参数中使用 Generic objects。
1package com.journaldev.spring.aspect;
2
3import java.util.Arrays;
4
5import org.aspectj.lang.JoinPoint;
6import org.aspectj.lang.annotation.Aspect;
7import org.aspectj.lang.annotation.Before;
8
9@Aspect
10public class EmployeeAspectJoinPoint {
11
12 @Before("execution(public void com.journaldev.spring.model..set*(*))")
13 public void loggingAdvice(JoinPoint joinPoint){
14 System.out.println("Before running loggingAdvice on method="+joinPoint.toString());
15
16 System.out.println("Agruments Passed=" + Arrays.toString(joinPoint.getArgs()));
17
18 }
19
20 //Advice arguments, will be applied to bean methods with single String argument
21 @Before("args(name)")
22 public void logStringArguments(String name){
23 System.out.println("String argument passed="+name);
24 }
25}
春季AOP咨询示例
让我们来看看一个简单的面部类别,例如投掷后
,投掷后
和返回后
建议。
1package com.journaldev.spring.aspect;
2
3import org.aspectj.lang.JoinPoint;
4import org.aspectj.lang.annotation.After;
5import org.aspectj.lang.annotation.AfterReturning;
6import org.aspectj.lang.annotation.AfterThrowing;
7import org.aspectj.lang.annotation.Aspect;
8
9@Aspect
10public class EmployeeAfterAspect {
11
12 @After("args(name)")
13 public void logStringArguments(String name){
14 System.out.println("Running After Advice. String argument passed="+name);
15 }
16
17 @AfterThrowing("within(com.journaldev.spring.model.Employee)")
18 public void logExceptions(JoinPoint joinPoint){
19 System.out.println("Exception thrown in Employee Method="+joinPoint.toString());
20 }
21
22 @AfterReturning(pointcut="execution(* getName())", returning="returnString")
23 public void getNameReturningAdvice(String returnString){
24 System.out.println("getNameReturningAdvice executed. Returned String="+returnString);
25 }
26
27}
我们可以使用 pointcut 表达式中的内
来应用提示到类中的所有方法. 我们可以使用 @AfterReturning 提示以获得建议的方法返回对象. 我们在员工豆中有 throwException() 方法来展示使用 After Throwing 建议。
周围的AOP春季示例
如前所述,我们可以使用 Around 视角来切断方法执行前后。我们可以使用它来控制建议的方法是否会执行。我们也可以检查返回的值并更改它。
1package com.journaldev.spring.aspect;
2
3import org.aspectj.lang.ProceedingJoinPoint;
4import org.aspectj.lang.annotation.Around;
5import org.aspectj.lang.annotation.Aspect;
6
7@Aspect
8public class EmployeeAroundAspect {
9
10 @Around("execution(* com.journaldev.spring.model.Employee.getName())")
11 public Object employeeAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
12 System.out.println("Before invoking getName() method");
13 Object value = null;
14 try {
15 value = proceedingJoinPoint.proceed();
16 } catch (Throwable e) {
17 e.printStackTrace();
18 }
19 System.out.println("After invoking getName() method. Return value="+value);
20 return value;
21 }
22}
周围的建议总是需要有 ProceedingJoinPoint作为一个论点,我们应该使用它的proceed()方法来召唤目标对象的建议方法. 如果建议的方法返回某些东西,它是建议的责任将其返回呼叫程序. 对于无效的方法,建议方法可以返回零。
春季建议与自定义注释点击
例如,有人可以用 getName() 方法定义一个新的春豆,并且建议将开始应用到这个方法,即使它不是意图的,这就是为什么我们应该尽可能缩小点滴表达的范围。另一种方法是创建一个自定义注释,并注明我们想要应用这个建议的方法。这就是使用 @Loggable 注释的 Employee setName() 方法的目的。 Spring Framework @Transactional 注释是 [Spring Transaction Management](/community/tutorials/spring-transaction management-jdbc-example Spring Transaction Management with JDBC Example
)这一方法的一个很好的例子。 Logjava 代码:
1package com.journaldev.spring.aspect;
2
3public @interface Loggable {
4
5}
主持人:Java 代码:
1package com.journaldev.spring.aspect;
2
3import org.aspectj.lang.annotation.Aspect;
4import org.aspectj.lang.annotation.Before;
5
6@Aspect
7public class EmployeeAnnotationAspect {
8
9 @Before("@annotation(com.journaldev.spring.aspect.Loggable)")
10 public void myAdvice(){
11 System.out.println("Executing myAdvice!!");
12 }
13}
该 myAdvice() 方法只会建议 setName() 方法. 这是一个非常安全的方法,每当我们想将建议应用于任何方法时,我们只需要用 Loggable 注释来注释它。
春季 AOP XML 配置
我总是喜欢注释,但我们还可以选择在春季配置文件中配置方面。例如,假设我们有一个类,如下。
1package com.journaldev.spring.aspect;
2
3import org.aspectj.lang.ProceedingJoinPoint;
4
5public class EmployeeXMLConfigAspect {
6
7 public Object employeeAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
8 System.out.println("EmployeeXMLConfigAspect:: Before invoking getName() method");
9 Object value = null;
10 try {
11 value = proceedingJoinPoint.proceed();
12 } catch (Throwable e) {
13 e.printStackTrace();
14 }
15 System.out.println("EmployeeXMLConfigAspect:: After invoking getName() method. Return value="+value);
16 return value;
17 }
18}
我们可以通过在 Spring Bean config 文件中包含下面的配置来配置它。
1<bean name="employeeXMLConfigAspect" class="com.journaldev.spring.aspect.EmployeeXMLConfigAspect" />
2
3<!-- Spring AOP XML Configuration -->
4<aop:config>
5 <aop:aspect ref="employeeXMLConfigAspect" id="employeeXMLConfigAspectID" order="1">
6 <aop:pointcut expression="execution(* com.journaldev.spring.model.Employee.getName())" id="getNamePointcut"/>
7 <aop:around method="employeeAroundAdvice" pointcut-ref="getNamePointcut" arg-names="proceedingJoinPoint"/>
8 </aop:aspect>
9</aop:config>
AOP xml config 元素的目的从他们的名称中很清楚,所以我不会对它进行太多细节。
AOP 春季示例
让我们有一个简单的春季程序,看看这些方面如何通过豆类方法切割。
1package com.journaldev.spring.main;
2
3import org.springframework.context.support.ClassPathXmlApplicationContext;
4
5import com.journaldev.spring.service.EmployeeService;
6
7public class SpringMain {
8
9 public static void main(String[] args) {
10 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
11 EmployeeService employeeService = ctx.getBean("employeeService", EmployeeService.class);
12
13 System.out.println(employeeService.getEmployee().getName());
14
15 employeeService.getEmployee().setName("Pankaj");
16
17 employeeService.getEmployee().throwException();
18
19 ctx.close();
20 }
21}
现在,当我们执行上述程序时,我们会得到以下输出。
1Mar 20, 2014 8:50:09 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
2INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Thu Mar 20 20:50:09 PDT 2014]; root of context hierarchy
3Mar 20, 2014 8:50:09 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
4INFO: Loading XML bean definitions from class path resource [spring.xml]
5Service method getter called
6Before executing service method
7EmployeeXMLConfigAspect:: Before invoking getName() method
8Executing Advice on getName()
9Executing loggingAdvice on getName()
10Executing secondAdvice on getName()
11Before invoking getName() method
12After invoking getName() method. Return value=Dummy Name
13getNameReturningAdvice executed. Returned String=Dummy Name
14EmployeeXMLConfigAspect:: After invoking getName() method. Return value=Dummy Name
15Dummy Name
16Service method getter called
17Before executing service method
18String argument passed=Pankaj
19Before running loggingAdvice on method=execution(void com.journaldev.spring.model.Employee.setName(String))
20Agruments Passed=[Pankaj]
21Executing myAdvice!!
22Running After Advice. String argument passed=Pankaj
23Service method getter called
24Before executing service method
25Exception thrown in Employee Method=execution(void com.journaldev.spring.model.Employee.throwException())
26Exception in thread "main" java.lang.RuntimeException: Dummy Exception
27 at com.journaldev.spring.model.Employee.throwException(Employee.java:19)
28 at com.journaldev.spring.model.Employee$$FastClassBySpringCGLIB$$da2dc051.invoke(<generated>)
29 at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
30 at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:711)
31 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
32 at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58)
33 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
34 at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
35 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
36 at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
37 at com.journaldev.spring.model.Employee$$EnhancerBySpringCGLIB$$3f881964.throwException(<generated>)
38 at com.journaldev.spring.main.SpringMain.main(SpringMain.java:17)
你可以看到建议正在根据他们的点击配置执行一个接一个,你应该将它们配置一个接一个,以避免混淆。这就是 Spring AOP 示例教程的全部,我希望你能够从示例中学习AOP的基本知识,并从下面的链接下载样本项目,并与它一起玩。
【下载《春天》】(链接)