Annotation-based configuration and use of Spring AOP

AOP is the continuation of OOP and is the abbreviation of Aspect Oriented Programming, which means aspect-oriented programming. A technology that dynamically and uniformly adds functions to a program without modifying the source code can be achieved through pre-compilation and runtime dynamic agents. AOP is actually a continuation of the GoF design pattern. The design pattern tirelessly pursues the decoupling between the caller and the callee. AOP can be said to be a realization of this goal.

Some of the non-businesses we do now, such as logs, transactions, security, etc., will be written in business code (that is, these non-business classes are cross-cutting with business classes), but these codes are often repeated, copy-paste This type of code will bring inconvenience to the maintenance of the program. AOP realizes that these business requirements are separated from the system requirements. This solution is also called proxy mechanism.

 

Let's first understand the related concepts of AOP. The "Spring Reference Manual" defines the following important concepts of AOP. The analysis of the above code is as follows:

 

  • Aspect: The official abstraction is defined as "the modularization of a concern, which may cross-cut multiple objects", in this case, the "Aspect" is the specific behavior concerned by the class TestAspect, for example, AServiceImpl The invocation of .barA() is one of the behaviors that the aspect TestAspect is concerned with. "Aspect" is configured in ApplicationContext <aop:aspect>.
  • Joinpoint  : A behavior during program execution, for example, a call to UserService.get or an exception thrown by UserService.delete.
  • Advice  : The action generated by the "Aspect" for a "connection point", for example, the action of logging the methods of all classes in the com.spring.service package in TestAspect is an Advice. Among them, a "Aspect" can contain multiple "Advice", such as ServiceAspect.
  • Pointcut  : An assertion that matches a join point, and advice in AOP is associated with a pointcut expression. For example, the join point that all advices in TestAspect focus on is determined by the pointcut expression execution(* com.spring.service.*.*(..)).
  • Target Object  : The object notified by one or more aspects. For example, AServcieImpl and BServiceImpl, of course, in the actual runtime, Spring AOP uses proxy implementation, and the actual AOP operation is the proxy object of TargetObject.
  • AOP Proxy (AOP Proxy)  : There are two proxy methods in Spring AOP, JDK dynamic proxy and CGLIB proxy. By default, when TargetObject implements the interface, it uses JDK dynamic proxy, such as AServiceImpl; otherwise, it uses CGLIB proxy, such as BServiceImpl. To force the use of the CGLIB proxy requires setting the proxy-target-class attribute of <aop:config> to true.

 

Advice type:

 

  • Before advice (Before advice): Advice executed before a join point (JoinPoint), but this advice cannot prevent execution before the join point. In the ApplicationContext, use the <aop:before> element in the <aop:aspect> to declare. For example, the doBefore method in TestAspect.
  • After advice: Advice that is executed when a join point exits (whether it is a normal return or an abnormal exit). In the ApplicationContext, use the <aop:after> element in the <aop:aspect> to declare. For example, the returnAfter method in ServiceAspect, so when an exception is thrown by calling UserService.delete in Teser, the returnAfter method is still executed.
  • After return advice: Advice that executes after a join point completes normally, excluding exceptions thrown. In the ApplicationContext, use the <after-returning> element in the <aop:aspect> to declare.
  • Around advice (Around advice): advice around a join point, similar to the doFilter method of Filter in the Servlet specification in the Web. Custom behavior can be done before or after the method invocation, or you can choose not to. In the ApplicationContext, use the <aop:around> element in the <aop:aspect> to declare. For example, the around method in ServiceAspect.
  • After throwing advice: Advice that is executed when a method exits with an exception thrown. In the ApplicationContext, use the <aop:after-throwing> element in the <aop:aspect> to declare. For example, the returnThrow method in ServiceAspect.

 

Note: Multiple notifications can be applied to a target object, that is, multiple aspects can be woven into the same target object.

 

Using Spring AOP can be based on two methods, one is the more convenient and powerful annotation method, and the other is the well-established xml configuration method.

 

Let’s talk about annotations first. Using annotations to configure Spring AOP is generally divided into two steps. The first step is to declare and activate the automatic scanning component function in the xml file, and activate the automatic proxy function at the same time (at the same time, add a common service layer component of UserService to the xml file to Test the annotation function of AOP):

<?xml version="1.0" encoding="UTF-8"?>
<beansxmlns=http://www.springframework.org/schema/beans
	xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
	xmlns:context=http://www.springframework.org/schema/context
	xmlns:aop=http://www.springframework.org/schema/aop
	xsi:schemaLocation="http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- Activate the component scanning function, automatically scan the components configured by annotation under the package and its sub-packages-->
 	<context:component-scan base-package = "com.jaeson" />
 	<!-- Activate the automatic proxy Features -->
 	<aop:aspectj-autoproxy proxy-target-class = "true" /> </beans>

 

The second step is to annotate the Aspect aspect class:

package com.jaeson.springstudy.aop;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class AopExample {
 
 private static final Logger logger = LoggerFactory.getLogger(AopExample.class);
 @Pointcut("execution(* com.jaeson..dao..*.*(..))")
    public void dataAccessOperation() {}
 
    @Pointcut("execution(* com.jaeson..service..*.*(..))")
    public void businessService() {}
    
    @Pointcut("execution(* com.jaeson.springstudy.aop.*Service.*(..)))")
    public void aspect() {}
    /*
  * Configure the pre-notification, using the pointcut registered on the method businessService()
  * Also accepts the JoinPoint pointcut object, you can do without this parameter
  */
 @Before("businessService()")
 public void before(JoinPoint joinPoint) {
  
  logger.info("before {}", joinPoint);
 }
 //Configure the post notification, using the pointcut registered on the method businessService()
 @After("businessService()")
 public void after(JoinPoint joinPoint) {
   logger.info("after {}", joinPoint);
 }
 //Configure the surround notification, use the pointcut registered on the method dataAccessOperation()
  //The return type of @Around must be Object, otherwise an exception will be thrown when weaving a pointcut with a non-void return type:
 //Null return value from advice does not match primitive return type for:
 @Around("dataAccessOperation()")
 public Object around(JoinPoint joinPoint) throws Throwable {
  
  Object result = null;
  long start = System.currentTimeMillis();
  
  logger.info("begin around {} !", joinPoint);
  result = ((ProceedingJoinPoint) joinPoint).proceed();
  long end = System.currentTimeMillis();
  logger.info("end around {} Use time : {} ms!", joinPoint, (end - start));
  
  return result;
 }
 //Configure the post-return notification, using the entry point registered on the method businessService()
 @AfterReturning("businessService()")
 public void afterReturn(JoinPoint joinPoint) {
  logger.info("afterReturn {}", joinPoint);
 }
 //Configure the notification after an exception is thrown, using the pointcut registered on the method businessService()
 @AfterThrowing(pointcut="businessService()", throwing="ex")
 public void afterThrow(JoinPoint joinPoint, RuntimeException ex) {
  
  logger.info("afterThrow {} with exception : {}", joinPoint, ex.getMessage());
 }
 
 //Configure the pre-notification, intercept the method whose return value type is com.jaeson.hibernatestudy.bean.User
 @Before("execution(com.jaeson.hibernatestudy.bean.User com.jaeson.springstudy.aop.*Service.*(..))")
 public void beforeReturnUser(JoinPoint joinPoint) {
  logger.info("beforeReturnUser {}", joinPoint);
 }
 //Configure the pre-notification, intercept the method whose parameter type is com.jaeson.hibernatestudy.bean.User
 @Before("execution(* com.jaeson.springstudy.aop.*Service.*(com.jaeson.hibernatestudy.bean.User))")
 public void beforeArgUser(JoinPoint joinPoint) {
  logger.info("beforeArgUser {}", joinPoint);
 }
 //Configure the pre-notification, intercept the method containing the long type parameter, and inject the parameter value into the formal parameter id of the current method
 @Before("aspect() && args(id)")
 public void beforeArgId(JoinPoint joinPoint, long id) {
  logger.info("beforeArgId {} ({})", joinPoint, id);
 }
}

Test code:

package com.jaeson.springstudy.aop;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jaeson.hibernatestudy.bean.User;
import com.jaeson.springstudy.aop.AopService;
public class TestAop {
 private ClassPathXmlApplicationContext context;
 
 @Before
 public void before() {
  context = new ClassPathXmlApplicationContext(new String[] {"applicationContext.xml", "classpath:spring-hibernate.xml", "spring-mybatis.xml"});
 }
 
 @After
 public void after() {
  context.close();
 }
 
 @Test
 public void testMethodAop() {
  
  AopService service = context.getBean("aopService", AopService.class);
  service.get(100086L);
  service.save(new User());
  try {
   service.delete(95977L);
  } catch (RuntimeException ex) {
   System.out.println(ex.getMessage());
  }
 }
}

The console output is as follows:

 

    [INFO][2016-04-26 15:56:06] com.jaeson.springstudy.aop.AopExample.beforeArgId(AopExample.java:95) beforeArgId execution(User com.jaeson.springstudy.aop.AopService.get(long)) (100086)
    [INFO][2016-04-26 15:56:06] com.jaeson.springstudy.aop.AopExample.beforeReturnUser(AopExample.java:81) beforeReturnUser execution(User com.jaeson.springstudy.aop.AopService.get(long))
    [INFO][2016-04-26 15:56:06] com.jaeson.springstudy.aop.AopService.get(AopService.java:16) AopService.get() method . . .
    [INFO][2016-04-26 15:56:06] com.jaeson.springstudy.aop.AopExample.beforeArgUser(AopExample.java:88) beforeArgUser execution(void com.jaeson.springstudy.aop.AopService.save(User))
    [INFO][2016-04-26 15:56:06] com.jaeson.springstudy.aop.AopService.save(AopService.java:22) AopService.save() method . . .
    [INFO][2016-04-26 15:56:06] com.jaeson.springstudy.aop.AopExample.beforeArgId(AopExample.java:95) beforeArgId execution(void com.jaeson.springstudy.aop.AopService.delete(long)) (95977)
    [INFO][2016-04-26 15:56:06] com.jaeson.springstudy.aop.AopService.delete(AopService.java:27) AopService.delete() method . . .
    AopService.delete() throw UnsupportedOperationException

 

It can be seen that, as we expected, although we did not make any changes to the UserSerivce class including its invocation method, Spring still intercepted the invocation of its methods, which may be the magic of AOP.

Let's briefly talk about the xml configuration method, in fact, it is just as simple:

 

<?xml version="1.0" encoding="UTF-8"?>
<beansxmlns=http://www.springframework.org/schema/beans
	xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
	xmlns:context=http://www.springframework.org/schema/context
	xmlns:aop=http://www.springframework.org/schema/aop
	xsi:schemaLocation="http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
		http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<!-- 系统服务组件的切面Bean -->
	<bean id="serviceAspect" class="com.jaeson.springstudy.aop.AopExample" />
	<!-- AOP配置 -->
	<aop:config>
		<!-- 声明一个切面,并注入切面Bean,相当于@Aspect -->
		<aop:aspect id="simpleAspect" ref="serviceAspect">
			<!-- 配置一个切入点,相当于@Pointcut -->
			<aop:pointcut expression="execution(* com.jaeson.springstudy.aop.service..*(..))" id="simplePointcut" />
			<!-- 配置通知,相当于@Before、@After、@AfterReturn、@Around、@AfterThrowing -->
			<aop:before pointcut-ref="simplePointcut" method="before" />
			<aop:after pointcut-ref="simplePointcut" method="after" />
			<aop:after-returning pointcut-ref="simplePointcut" method="afterReturn" />
			<aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex" />
		</aop:aspect>
	</aop:config>
</beans>

 

 

 AopService 的代码(其实很简单):

 

package com.jaeson.springstudy.aop;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.jaeson.hibernatestudy.bean.User;
@Service
public class AopService {
 
 private static final Logger logger = LoggerFactory.getLogger(AopService.class);
 
 public User get(long id) {
  logger.info("AopService.get() method . . .");
  return new User();
 }
 public void save(User user) {
  
  logger.info("AopService.save() method . . .");
 }
 public void delete(long id) throws RuntimeException {
  logger.info("AopService.delete() method . . .");
  throw new UnsupportedOperationException("AopService.delete() throw UnsupportedOperationException");
 }
}

 

应该说学习Spring AOP有两个难点,第一点在于理解AOP的理念和相关概念,第二点在于灵活掌握和使用切入点表达式。概念的理解通常不在一朝一夕,慢慢浸泡的时间长了,自然就明白了,下面我们简单地介绍一下切入点表达式的配置规则吧。

通常情况下,表达式中使用”execution“就可以满足大部分的要求。表达式格式如下:

 

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

 

  • modifiers-pattern:方法的操作权限
  • ret-type-pattern:返回值
  • declaring-type-pattern:方法所在的包
  • name-pattern:方法名
  • parm-pattern:参数名
  • throws-pattern:异常

其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution(* com.spring.service.*.*(..))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。

最后说一下通知参数

可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了。例如,<aop:aspect>配置如下:

 

<aop:config>
	<aop:aspect id="TestAspect" ref="aspectBean">
		<aop:pointcut id="businessService" expression="execution(* com.jaeson.springstudy.service.*.*(String,..)) and args(msg,..)" />
		<aop:after pointcut-ref="businessService" method="doAfter" />
	</aop:aspect>
</aop:config>

 

上面的代码args(msg,..)是指将切入点方法上的第一个String类型参数添加到参数名为msg的通知的入参上,这样就可以直接使用该参数啦。

访问当前的连接点

在上面的Aspect切面Bean中已经看到了,每个通知方法第一个参数都是JoinPoint。其实,在Spring中,任何通知(Advice)方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型用以接受当前连接点对象。JoinPoint接口提供了一系列有用的方法, 比如 getArgs() (返回方法参数)、getThis() (返回代理对象)、getTarget() (返回目标)、getSignature() (返回正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息)。

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326950403&siteId=291194637