Spring的切面 AOP

在所编写本章时,我所在的公司正在大量裁人,我所在的项目组,也正处于闲置阶段。我们说每天上都在上下班。这才是正确的工作流程。在我们上下班的时候,有许多与工作无关,但是你又必须得去做的事情,比如我从事java web开发,但是我每天上下班都得打卡,每天都得做绩效。每天都有可能被开除等等。

那么我们首先创建一个Person类。里面有我们的员工编号,名字和所属部门,还有考核分数;

public class Person implements Serializable{
		private String id; //工作编号
		private String name;  //名字
		private String department; //所属部门
		private Integer score;  //考核分数
		public String getId() {
			return id;
		}
		public void setId(String id) {
			this.id = id;
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public String getDepartment() {
			return department;
		}
		public void setDepartment(String department) {
			this.department = department;
		}
		public Integer getScore() {
			return score;
		}
		public void setScore(Integer score) {
			this.score = score;
		}
		
		
}

接着我们模拟一下每天上班的流程:

public interface Work {
		//上班
		public void goTOWork(Person person);
		//工作开发
		public void workExploit(Person person);
		//下班
		public void closedWork(Person person);
}

public class WorkJob  implements Work{
	/**
	 * @param person
	 * @see org.seckill.entity.Work#goTOWork(org.seckill.entity.Person)
	 *<pre>
	 *<li>Author: </li>
	 *<li>Date: 2016年9月26日</li>
	 *</pre>
	 */
	@Override
	public void goTOWork(Person person) {
		System.out.println(person.getName()+"开始上班喽");
		
	}


	/**
	 * @param person
	 * @see org.seckill.entity.Work#workExploit(org.seckill.entity.Person)
	 *<pre>
	 *<li>Author: </li>
	 *<li>Date: 2016年9月26日</li>
	 *</pre>
	 */
	@Override
	public void workExploit(Person person) {
		System.out.println(person.getName()+"开始工作啦");
	}


	/**
	 * @param person
	 * @see org.seckill.entity.Work#closedWork(org.seckill.entity.Person)
	 *<pre>
	 *<li>Author: </li>
	 *<li>Date: 2016年9月26日</li>
	 *</pre>
	 */
	@Override
	public void closedWork(Person person) {
		System.out.println("结束工作下班");
		
	}

}


最后写个测试方法,测试下。

public static void main(String[] args) {
		Person person=new Person();
		person.setId("00054");
		person.setName("张三");
		person.setDepartment("研发部");
		person.setScore(50);
		WorkJob workJob=new WorkJob();
		workJob.goTOWork(person);
		workJob.workExploit(person);
		workJob.closedWork(person);
	}

张三开始上班喽
张三开始工作啦
张三结束工作下班

如果说现在公司要求上下班打卡怎么办,我们是不是要写个打卡的方法。按照面向对象编程来说的话,我们不会主动参与调用,方法的发生,在软件开发中,分布于应用中多处的功能被称为横切关注点。通常,这些横切关注点与业务逻辑是分离的。将这些关注点分离真是面向切面编程(AOP)所有解决的。

委托和继承:

在AOP没有出现之前用的最多的就是委托和继承去实现了。

假设我们创建工作的辅助类。

public class WorkGrade {
		public void openCard(Person person){
			System.out.println(person.getName()+"开始打卡");
		}
}

然后让我们的工作类再去继承一下。

public class WorkJob extends WorkGradeimplements Work


最后再去测试一下。

public static void main(String[] args) {
		Person person=new Person();
		person.setId("00054");
		person.setName("张三");
		person.setDepartment("研发部");
		person.setScore(50);
		WorkJob workJob=new WorkJob();
		workJob.goTOWork(person);
		workJob.workExploit(person);
		workJob.closedWork(person);
		workJob.openCard(person);
	}

张三开始上班喽
张三开始工作啦
张三结束工作下班
张三开始打卡

委托模式这里就不写了,有兴趣的可以自行了解一下。

定义AOP术语:

1.通知(Advice):
通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。

Before-在方法被调用之前调用。

After-在方法完成之后调用,无论方法执行是否成功。

After-returning-在方法成功执行之后调用通知。

After-throwing-在方法抛出异常后调用通知。

Around-通知包裹了被通知的方法,在被通知的方法调用之前和调用方法之后执行自定义的行为。
2.连接点(Joinpoint):
程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。
3.切入点(Pointcut)
通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定
4.切面(Aspect)
通知和切入点共同组成了切面:时间、地点和要发生的“故事”
5.引入(Introduction)
引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)
6.目标(Target)
即被通知的对象,如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事(AOP让他做爱做的事)
7.代理(proxy)
应用通知的对象,详细内容参见设计模式里面的代理模式
8.织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术

Spring提供了4种实现AOP的方式:

1.经典的基于代理的AOP
2.@AspectJ注解驱动的切面
3.纯POJO切面
4.注入式AspectJ切面(适合各个版本)
前三种方法都是spring基于代理的AOP变体,因此,Spring对AOP的支持局限于方法拦截。如果AOP需求超过了简单方法拦截的范畴(比如构造器或熟悉拦截)。那么应该考虑在Aspectj里面实现切面。利用spring的DI把springBean注入到Aspectj切面中。

一、基于代理的AOP

首先创建一个增强类:

public class WorkGrade implements MethodBeforeAdvice, AfterReturningAdvice{
		/**
		 * @param returnValue
		 * @param method
		 * @param args
		 * @param target
		 * @throws Throwable
		 * @see org.springframework.aop.AfterReturningAdvice#afterReturning(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], java.lang.Object)
		 *<pre>
		 *<li>Author: </li>
		 *<li>Date: 2016年9月26日</li>
		 *</pre>
		 */
		//方法执行成功后
		@Override
		public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
				Person person=(Person) args[0];
				System.out.println(person.getName()+"结束打卡");
		}

		/**
		 * @param method
		 * @param args
		 * @param target
		 * @throws Throwable
		 * @see org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method, java.lang.Object[], java.lang.Object)
		 *<pre>
		 *<li>Author: </li>
		 *<li>Date: 2016年9月26日</li>
		 *</pre>
		 */
		//方法执行前
		@Override
		public void before(Method method, Object[] args, Object target) throws Throwable {
			Person person=(Person) args[0];
			System.out.println(person.getName()+"开始打卡");
		}
}
再创建一下自己的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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
		<!-- 创建一个增强 advice -->
  <bean id ="workHelper" class="org.seckill.entity.WorkGrade"/> 
  
  <bean id="lina" class="org.seckill.entity.WorkJob"/> 
  <!-- 定义切点  匹配所有的上下班方法-->
  <bean id ="workPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut"> 
      <property name="pattern" value=".*Work"></property> 
  </bean> 
    
  <!-- 切面  增强+切点结合 -->
  <bean id="workHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> 
     <property name="advice" ref="workHelper"/> 
     <property name="pointcut" ref="workPointcut"/> 
  </bean> 
    
  <!-- 定义代理对象 -->
  <bean id="linaProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> 
      <property name="target" ref="lina"/> 
      <property name="interceptorNames" value="workHelperAdvisor"/> 
  </bean> 

</beans>
测试类:

@Test
	public void test(){ 
	  ApplicationContext ct = new ClassPathXmlApplicationContext("spring/spring-aop.xml"); 
	  Person person=new Person();
		person.setId("00054");
		person.setName("张三");
		person.setDepartment("研发部");
		person.setScore(50);
		Work workJob =(Work) ct.getBean("linaProxy"); 
		workJob.goTOWork(person);
		workJob.workExploit(person);
		workJob.closedWork(person);
	} 
张三开始打卡
张三开始上班喽
张三结束打卡
张三开始工作啦
张三开始打卡
张三结束工作下班
张三结束打卡
pattern属性指定了正则表达式,他匹配所有的上下班方法
使用org.springframework.aop.support.DefaultPointcutAdvisor的目的是为了使切点和增强结合起来形成一个完整的切面
最后配置完后通过org.springframework.aop.framework.ProxyFactoryBean产生一个最终的代理对象。

二、纯简单java对象切面

纯简单java对象切面在我看来就是相对于第一种配置,不需要使用代理,,而是通过spring的内部机制去自动扫描,这时候我们的配置文件就该如下修改:

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
		<!-- 创建一个增强 advice -->
  <bean id ="workHelper" class="org.seckill.entity.WorkGrade"/> 
  
  <bean id="lina" class="org.seckill.entity.WorkJob"/> 
<!-- 配置切点和通知-->
<bean id ="sleepAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> 
   <property name="advice" ref="workHelper"></property> 
   <property name="pattern" value=".*Work"/> 
</bean> 
    <!-- 自动代理配置 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> 
</beans>

测试:

	@Test
	public void test(){ 
	  ApplicationContext ct = new ClassPathXmlApplicationContext("spring/spring-aop2.xml"); 
	  Person person=new Person();
		person.setId("00054");
		person.setName("张三");
		person.setDepartment("研发部");
		person.setScore(50);
		Work workJob =(Work) ct.getBean("lina"); 
		workJob.goTOWork(person);
		workJob.workExploit(person);
		workJob.closedWork(person);
	} 

这种代理模式也被称为自动代理,因为DefaultAdvisorAutoProxyCreator这个类功能更为强大,这个类的奇妙之处是他实现了BeanProcessor接口,当ApplicationContext读如所有的Bean配置信息后,这个类将扫描上下文,寻找所有的Advistor(一个Advisor是一个切入点和一个通知的组成),将这些Advisor应用到所有符合切入点的Bean中。

三、@Aspect注解形式

@Aspect
@Component
public class WorkGrade2{
	  @Pointcut("execution(* *.Work(..))") 
	  public void sleeppoint(){} 
	    
	  @Before("workpoint()") 
	  public void beforeSleep(){ 
	    System.out.println("开始打卡"); 
	  } 
	    
	  @AfterReturning("workpoint()") 
	  public void afterSleep(){ 
	    System.out.println("结束打卡"); 
	  } 
}
	<!--扫描包 -->
	<context:component-scan base-package="org.seckill.aop"
		annotation-config="true" />
	<!-- ASPECTJ注解 -->
	<aop:aspectj-autoproxy proxy-target-class="true" />

	<!-- 目标类 -->
	<bean id="workJob" class="org.seckill.entity.WorkJob" />

四、注入形式的Aspcet切面

这个是最简单,最灵活,也是最常用的一个。

public class WorkGrade2{
	  public void beforeWork(){ 
	    System.out.println("开始打卡"); 
	  } 
	    
	  public void afterWork(){ 
	    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:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 目标类 -->
  <bean id="lina" class="org.seckill.entity.WorkJob"/> 
  <bean id ="workHelper" class="org.seckill.aop.WorkGrade2"/> 
    
  <aop:config> 
    <aop:aspect ref="workHelper"> 
       <aop:before method="beforeWork" pointcut="execution(* org.seckill..*Work(..))"/> 
       <aop:after method="afterWork" pointcut="execution(* org.seckill..*Work(..))"/> 
    </aop:aspect> 
  </aop:config> 

</beans>

切入点表达式的使用规则:

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

有“?”号的部分表示可省略的,modifers-pattern表示修饰符如publicprotected等,ret-type-pattern表示方法返回类型,declaring-type-pattern代表特定的类,name-pattern代表方法名称,param-pattern表示参数,throws-pattern表示抛出的异常。在切入点表达式中,可以使用*来代表任意字符,用..来表示任意个参数。注意*号后面有空格。

@Test
	public void test(){ 
	  ApplicationContext ct = new ClassPathXmlApplicationContext("spring/spring-aop4.xml"); 
	  Person person=new Person();
		person.setId("00054");
		person.setName("张三");
		person.setDepartment("研发部");
		person.setScore(50);
		Work workJob =(Work) ct.getBean("lina"); 
		workJob.goTOWork(person);
		workJob.workExploit(person);
		workJob.closedWork(person);
	} 

通过切面增加新功能

试想我们每天除了上下班打卡,工作是不是就没有什么乐趣了啦,我们在编程之余是不是得喝一杯咖啡提提神。所以我想在工作的时候增加一个方法,比如说我要喝咖啡,听起来是不是很不错的样子。

public interface Drink {
			void drinkCoffee();
}
public class DrinkImpl implements Drink{

	/**
	 * 
	 * @see org.seckill.aop.Drink#drinkCoffee()
	 *<pre>
	 *<li>Author: </li>
	 *<li>Date: 2016年9月27日</li>
	 *</pre>
	 */
	@Override
	public void drinkCoffee() {
		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:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
	http://www.springframework.org/schema/tx
	http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

  <bean id="lina" class="org.seckill.entity.WorkJob"/> 
  <bean id ="workHelper" class="org.seckill.aop.WorkGrade2"/> 
    
  <aop:config proxy-target-class="true">
    <aop:aspect ref="workHelper"> 
       <aop:before method="beforeWork" pointcut="execution(* org.seckill..*Work(..))"/> 
       <aop:after method="afterWork" pointcut="execution(* org.seckill..*Work(..))"/> 
       <aop:declare-parents 
            types-matching="org.seckill.entity.WorkJob"
            implement-interface="org.seckill.aop.Drink" 
            default-impl="org.seckill.aop.DrinkImpl"/>
    </aop:aspect> 
  </aop:config> 



</beans>

其中types-mathcing是之前原始的类,implement-interface是想要添加的功能的接口,default-impl是新功能的默认的实现。

在使用时,直接通过getBean获得bean转换成相应的接口就可以使用了。

@Test
	public void test(){ 
	  ApplicationContext ct = new ClassPathXmlApplicationContext("spring/spring-aop3.xml"); 
	  	Person person=new Person();
		person.setId("00054");
		person.setName("张三");
		person.setDepartment("研发部");
		person.setScore(50);
		Work workJob =(Work) ct.getBean("lina"); 
		Drink drink =(Drink) ct.getBean("lina"); 
		workJob.goTOWork(person);
		drink.drinkCoffee();
		workJob.workExploit(person);
		workJob.closedWork(person);
	} 
开始打卡
张三开始上班喽
结束打卡
来一杯卡布奇诺
张三开始工作啦
开始打卡
张三结束工作下班
结束打卡
一些编程语言,例如Ruby和Groovy,有开放类的理念,它们可以不用直接修改对象或类的定义就能搞为对象或类添加新的方法,Java虽然不是动态语言,但是可以通过AOP来通过的改变对象和类。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP(Aspect-Oriented Programming)是Spring框架提供的一种面向切面编程的现方式。它允许开发人员将横切关注点(如日志记录、事务管理等)与核心业务逻辑进行分离,从而提高代码的可重用性和可维护性。 在Spring AOP中,通过定义切面(Aspect)来描述横切关注点,然后将切面与目标对象进行关联。切面可以通过配置文件或注解的方式进行声明。在运行时,Spring AOP会根据定义的切面规则自动将切面逻辑织入到目标对象的方法中。 切面由切点(Pointcut)和通知(Advice)组成。切点定义了在哪些方法上应用切面逻辑,通知定义了切面逻辑的具体实现。通知可以在目标方法执行前、执行后、抛出异常时等不同的时机触发。 常见的通知类型包括: - 前置通知(Before Advice):在目标方法执行前执行; - 后置通知(After Advice):在目标方法执行后执行,无论是否发生异常; - 返回通知(After Returning Advice):在目标方法执行后执行,仅在目标方法成功返回时触发; - 异常通知(After Throwing Advice):在目标方法抛出异常后执行; - 环绕通知(Around Advice):在目标方法执行前后都执行,可以控制目标方法的执行流程。 Spring AOP可以应用于各个层面,如业务逻辑、数据访问、事务管理等,通过将横切关注点与核心业务逻辑解耦,可以提高代码的模块化和可维护性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值