JavaWeb了解之Spring框架(Spring AOP篇)
2022-10-13 本文已影响0人
平安喜乐698
目录
概念
示例(一般切面)
示例(切点切面)
示例(自动代理)
示例(AspectJ xml方式)
示例(AspectJ 注解方式)
AOP:面向切面编程(在运行时动态地将代码切入到指定类的指定方法的指定位置上)。
可以在不修改源码的情况下给类添加新功能(通常是一些可复用的功能,如:日志功能、事务功能、权限检查、参数检查、统计信息,减少代码的重复性)。
概念
动态代理机制(2种)
Spring在运行期会为目标对象生成一个动态代理对象(对目标对象进行增强)。
1. JDK方式(默认)
若目标对象实现了若干接口,Spring则使用JDK的java.lang.reflect.Proxy类进行代理。
2. CGLIB方式
若目标对象没有实现任何接口,Spring则使用CGLIB库生成目标对象的子类进行代理。
注意:声明为final类型的方法无法被覆盖,所以无法完成代理。
切入点
SpringAOP属于简化版AOP组件,并没有像其他AOP框架(如 AspectJ)提供完整的AOP功能。
SpringAOP只支持方法调用(仅支持public类型且非static类型的方法)这一种切入点类型(最有用的切入点类型,可实现绝大多数AOP功能)。如果需要对private或protected类型的方法进行增强,或者需要使用其他类型的切入点(如:成员变量切入点),则需要组合其他AOP实现(如:ApectJ)来达成。
将切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。
通知类型
org.aopalliance.aop.Interface.Advice接口。
按照通知织入到目标对象方法的连接点位置,提供了6个Advice子接口:
1. 前置通知
org.springframework.aop.MethodBeforeAdvice
在目标方法执行前,执行通知(即进行增强)。
2. 后置通知
org.springframework.aop.AfterReturningAdvice
在目标方法执行后,无论是否异常,执行通知。
3. 后置返回通知
org.springframework.aop.AfterReturningAdvice
在目标方法执行后,且有返回值时,执行通知。
4. 环绕通知
org.aopalliance.intercept.MethodInterceptor
在目标方法执行前后,执行通知。
5. 异常后通知
org.springframework.aop.ThrowsAdvice
在目标方法抛出异常时,执行通知。
6. 引入通知
org.springframework.aop.IntroductionInterceptor
在目标类中添加新的方法和属性。
切面类型
对通知和切入点进行管理。
分为3类:
1. 一般切面(不带切点的切面)默认
org.springframework.aop.Advisor接口。仅有一个Advice通知类型的属性,没有定义切入点。
会对目标对象中的所有方法进行拦截并织入增强代码(由于太过宽泛,一般不使用)。
2. 切点切面
org.springframework.aop.PointcutAdvisor接口(Advisor的子接口),多了一个PointCut类型的属性。
可以通过包名、类名、方法名等信息,更加灵活地定义切面中的切入点(即描述需要拦截的方法)。
常用的实现类:
1. NameMatchMethodPointcutAdvisor类
指定Advice通知所要应用到的目标方法名。例如: hello* 代表所有以hello开头的方法。
2. RegExpMethodPointcutAdvisor类
有一个pattern属性(使用正则表达式来定义切点)。
3. 引介切面
org.springframework.aop.IntroductionAdvisor接口(Advisor的子接口)。
对引介增强的特殊切面(应用于类层面上,适用ClassFilter进行定义)。
ProxyFactoryBean
Spring能够基于org.springframework.aop.framework.ProxyFactoryBean类,根据目标对象(是否实现了接口)自动选择使用 JDK动态代理或 CGLIB动态代理机制,为目标对象(TargetBean)生成对应的代理对象(ProxyBean)。
常用的属性:
1. target
需要被代理的目标对象(即需要增强的对象)。
2. proxyInterfaces
代理对象需要实现的接口(多个接口时使用list元素进行赋值)。
3. proxyTargetClass
针对类的代理。为false(默认)表示使用JDK动态代理;为true表示使用CGlib动态代理。
4. interceptorNames
拦截器的名字(拦截器、Advice、切面 对应的Bean)。
5. singleton
代理对象是否为单例模式(默认为true)。
6. optimize
是否对代理对象进行优化(只适用于CGLIB)。
自动代理
在实际开发中,一个项目中往往包含非常多的Bean,如果每个Bean都通过在xml文件中使用ProxyFactoryBean创建代理对象,那么开发和维护成本会十分巨大,为此Spring提供了自动代理。
Spring提供的自动代理方案,都是基于后处理Bean实现的(即在Bean创建的过程中完成增强),并将目标对象替换为自动生成的代理对象。在程序中直接拿到的Bean就已经是Spring自动生成的代理对象了。
自动代理方案(3种):
1. BeanNameAutoProxyCreator
根据Bean名称 创建代理对象。
2. DefaultAdvisorAutoProxyCreator
根据Advisor信息 创建代理对象。
3. AnnotationAwareAspectJAutoProxyCreator
根据Bean中的AspectJ注解 创建自动代理对象。
集成AspectJ
AspectJ是一款独立的基于Java语言的全功能AOP框架,并不是Spring组成部分。
AspectJ支持通过Spring配置AspectJ切面,因此它是SpringAOP的完美补充,工作中通常将2者结合使用来简化AOP开发。
需要导入spring-aop-xxx.jar、spring-aspects-xxx.jar(Spring提供),aspectjweaver-xxxx.jar(AspectJ提供)。
1. 使用步骤(XML方式):
切面信息(切面、切点、通知)定义在aop:config元素中;aop:config元素可以有多个;
1. 引入aop命名空间。
2. 使用aop:aspect元素定义切面。
<aop:config>
<aop:aspect id="helloAspect" ref="aspectBean">
...
</aop:aspect>
</aop:config>
3. 使用aop:pointcut元素定义切点(对哪个方法进行增强)。
<aop:config>
<!-- 对包下的所有类中的所有方法进行增强 -->
<aop:pointcut id="helloPointCut" expression="execution(* com.sst.cx.*.*(..))"/>
</aop:config>
说明:
1. 当aop:pointcut元素定义在aop:config元素下时,表示该切点为全局切入点,可被多个切面共享;当aop:pointcut元素定义在aop:aspect元素下时,表示该切点只对当前切面有效。
2. aop:pointcut元素的execution属性用于指定切入点所关联的切入点表达式。
语法格式:execution([权限修饰符][返回值类型][类的完全限定名][方法名][参数列表])
1. 返回值类型、方法名、参数列表 为必填,其他参数可选。
2. 返回值类型:*表示任何返回值。当返回值为对象时需指定类的完整路径。
3. 方法:*表示所有方法,hello*表示以hello开头的所有方法。
4. 参数列表:(..)表示所有参数;(*)表示只有一个参数,参数类型任意;(*,String)表示有2个参数。
4. 使用aop:aspect元素定义通知(5种类型)。
<aop:aspect id="helloAspect" ref="实现下面5个方法的bean">
<aop:before method="before" pointcut-ref="前面定义的切点id"></aop:before>
<aop:after-returning method="afterReturning" pointcut-ref="前面定义的切点id" returning="returningValue"></aop:after-returning>
<aop:after-throwing method="afterThrow" pointcut-ref="前面定义的切点id" throwing="exception"></aop:after-throwing>
<aop:after method="after" pointcut-ref="前面定义的切点id"></aop:after>
<aop:around method="around" pointcut-ref="前面定义的切点id"></aop:around>
</aop:aspect>
2. 使用步骤(注解方式):
1. 开启 @AspectJ注解。
方式1. 在beans元素的开始处添加
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.sst.cx"></context:component-scan>
<!-- 开启@AspectJ注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
方式2. 在AppConfig.java类(使用@Configuration注解标注的类)中,添加@EnableAspectJAutoProxy注解和@ComponentScan(basePackages="com.sst.cx")注解。该类用于获取容器:ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);。
2. 在UserDaoAdvice上添加
@Component注解:相当于在xml中注册Bean。
@Aspect注解:定义成切面。
3. 在UserDaoAdvice的各个方法上添加通知注解
方式1. 使用切入点表达式
@Before(value="execution(* com.sst.cx.dao.UserDao.add(..))")
方式2. 使用切入点引用
@Before("UserDaoAdvice.pointCut1()")
//
定义切点
可以使用切点表达式;也可以引用其他切点:@Pointcut(value="pointCut2"),引用其他切入点时可以使用 !、&&、|| :@Pointcut(value="!pointCut2()")。
@Pointcut(value="execution(* com.sst.cx.dao.UserDao.add(..))")
// 方法要求:private类型,返回值为void,没有参数
private void pointCut1(){}
//
AspecjJ提供了如下注解:
1. @Aspect
用于定义一个切面。
2. @Pointcut
用于定义一个切入点。
3. @Before
用于定义前置通知,相当于 BeforeAdvice。
4. @AfterReturning
用于定义后置返回通知,相当于 AfterReturningAdvice。
5. @Around
用于定义环绕通知,相当于 MethodInterceptor。
6. @AfterThrowing
用于定义异常抛出通知,相当于 ThrowAdvice。
7. @After
用于定义后置通知,相当于 AfterAdvice。
8. @DeclareParents
用于定义引介通知,相当于 IntroductionInterceptor。
示例(一般切面)
如果没有对切面进行具体定义,SpringAOP会通过Advisor定义一个一般切面(不带切点的切面)。
1. 导入依赖包
4个核心jar:beans、context、core、expression
AOP特性jar:aop
三方jar:commons-logging.jar
2.
===》UserDao.java(DAO接口)
package com.sst.cx.dao;
public interface UserDao {
public void add();
public void delete();
public void update();
public void querry();
}
===》UserDaoImpl.java(DAO实现)
package com.sst.cx.dao.impl;
import com.sst.cx.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("执行add方法。。。");
}
@Override
public void delete() {
System.out.println("执行delete方法。。。");
}
@Override
public void update() {
System.out.println("执行update方法。。。");
}
@Override
public void querry() {
System.out.println("执行querry方法。。。");
}
}
===》UserDaoBeforeAdvice.java(Advice通知,拦截器)
package com.sst.cx.advice;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class UserDaoBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("前置增强操作。。。");
}
}
3. 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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 目标对象(需要增强的对象) -->
<bean id="userDaoImpl" class="com.sst.cx.dao.impl.UserDaoImpl"></bean>
<!-- 定义增强 -->
<bean id="beforeAdvice" class="com.sst.cx.advice.UserDaoBeforeAdvice"></bean>
<!-- 通过配置生成代理userDaoImpl的 代理对象 -->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 设置 被代理的目标对象(需增强的对象) -->
<property name="target" ref="userDaoImpl"/>
<!-- 设置 代理对象需实现的接口(全路径) -->
<property name="proxyInterfaces" value="com.sst.cx.dao.UserDao"/>
<!-- 设置 拦截器(增强) -->
<property name="interceptorNames" value="beforeAdvice"/>
</bean>
</beans>
4. 测试
===》MainApp.java
package com.sst.cx;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.sst.cx.dao.UserDao;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
// 获取代理对象
UserDao userDao = (UserDao)context.getBean("userDaoProxy");
userDao.add();
userDao.delete();
userDao.update();
userDao.querry();
}
}
运行结果
示例(切点切面)
1. 导入依赖包
同上
2. UserDao.java、userDaoImpl.java
同上
这次换一种通知类型:环绕通知
===》UserDaoAroundAdvice.java(Advice通知,拦截器)
package com.sst.cx.advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class UserDaoAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("前置增强操作。。。");
// 执行目标方法(被代理对象中的逻辑)
Object result = methodInvocation.proceed();
System.out.println("后置增强操作。。。");
return result;
}
}
3. 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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 目标对象(需要增强的对象) -->
<bean id="userDaoImpl" class="com.sst.cx.dao.impl.UserDaoImpl"></bean>
<!-- 定义增强 -->
<bean id="aroundAdvice" class="com.sst.cx.advice.UserDaoAroundAdvice"></bean>
<!-- 定义切面 -->
<bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--拦截哪些方法。 value设为.*则表示所有方法-->
<property name="patterns" value="com.sst.cx.dao.impl.UserDaoImpl.add.*,com.sst.cx.dao.impl.UserDaoImpl.delete.*"></property>
<property name="advice" ref="aroundAdvice"></property>
</bean>
<!-- 通过配置生成代理userDaoImpl的 代理对象 -->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 设置 被代理的目标对象(需增强的对象) -->
<property name="target" ref="userDaoImpl"/>
<!-- 设置 代理对象需实现的接口(全路径) -->
<property name="proxyInterfaces" value="com.sst.cx.dao.UserDao"/>
<!-- 设置 拦截器(增强) -->
<property name="interceptorNames" value="myPointCutAdvisor"/>
</bean>
</beans>
4. 测试
同上
运行结果
示例(自动代理)
将上面的 切点切面示例 改为自动代理。
===》方式1. BeanNameAutoProxyCreator
修改xml文件内容如下:
去除切面Bean、ProxyFactoryBean,添加自动代理Bean。
<!-- 目标对象(需要增强的对象) -->
<bean id="userDaoImpl" class="com.sst.cx.dao.impl.UserDaoImpl"></bean>
<!-- 定义增强 -->
<bean id="aroundAdvice" class="com.sst.cx.advice.UserDaoAroundAdvice"></bean>
<!-- 自动代理,对所有后缀为Impl的Bean自动创建代理对象,对所有方法进行aroundAdvice增强 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Impl"></property>
<property name="interceptorNames" value="aroundAdvice"></property>
</bean>
===》方式2. DefaultAdvisorAutoProxyCreator
修改xml文件内容如下:
去除ProxyFactoryBean,添加自动代理Bean。
<!-- 目标对象(需要增强的对象) -->
<bean id="userDaoImpl" class="com.sst.cx.dao.impl.UserDaoImpl"></bean>
<!-- 定义增强 -->
<bean id="aroundAdvice" class="com.sst.cx.advice.UserDaoAroundAdvice"></bean>
<!-- 定义切面 -->
<bean id="myPointCutAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!--拦截哪些方法。 value设为.*则表示所有方法-->
<property name="patterns" value="com.sst.cx.dao.impl.UserDaoImpl.add.*,com.sst.cx.dao.impl.UserDaoImpl.delete.*"></property>
<property name="advice" ref="aroundAdvice"></property>
</bean>
<!-- 自动代理,根据切面信息自动创建代理对象,对指定方法进行增强 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
测试文件中直接使用userDaoImpl获取代理对象:context.getBean("userDaoImpl")
示例(AspectJ)
1. 导入依赖包
4个核心jar:beans、context、core、expression
AOP特性jar:aop、aspects(Spring提供),aspectjweaver(aspectJ提供)
三方jar:commons-logging.jar
2. UserDao.java、userDaoImpl.java、UserDaoAdvice.java
===》UserDao.java(DAO接口)
package com.sst.cx.dao;
public interface UserDao {
public void add();
public void delete();
public int update();
public void querry();
public void throwException();
}
===》userDaoImpl.java(DAO实现)
package com.sst.cx.dao.impl;
import com.sst.cx.dao.UserDao;
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("执行add方法。。。");
}
@Override
public void delete() {
System.out.println("执行delete方法。。。");
}
@Override
public int update() {
System.out.println("执行update方法。。。");
return 100;
}
@Override
public void querry() {
System.out.println("执行querry方法。。。");
}
@Override
public void throwException() {
System.out.println("执行throwException方法。。。");
int x=1/0;
}
}
===》UserDaoAdvice.java(Advice通知,拦截器)
package com.sst.cx.advice;
import org.aspectj.lang.ProceedingJoinPoint;
public class UserDaoAdvice {
public void before() {
System.out.println("前置增强操作。。。");
}
public void after() {
System.out.println("后置增强操作。。。");
}
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕前置增强操作。。。");
proceedingJoinPoint.proceed();
System.out.println("环绕后置增强操作。。。");
}
public void afterReturning(Object returnValue) {
System.out.println("后置返回增强操作。。。"+returnValue);
}
public void afterThrow(Throwable exception) {
System.out.println("异常增强操作。。。"+exception.getMessage());
}
}
3. 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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- 定义Bean -->
<bean id="userDaoImpl" class="com.sst.cx.dao.impl.UserDaoImpl"></bean>
<!-- 定义通知(增强) -->
<bean id="myAdvice" class="com.sst.cx.advice.UserDaoAdvice"></bean>
<aop:config>
<aop:pointcut id="beforePointCut" expression="execution(* com.sst.cx.dao.UserDao.add(..))"/>
<aop:pointcut id="afterPointCut" expression="execution(* com.sst.cx.dao.UserDao.delete(..))"/>
<aop:pointcut id="afterReturnPointCut" expression="execution(* com.sst.cx.dao.UserDao.update(..))"/>
<aop:pointcut id="aroundPointCut" expression="execution(* com.sst.cx.dao.UserDao.querry(..))"/>
<aop:pointcut id="throwPointCut" expression="execution(* com.sst.cx.dao.UserDao.throwException(..))"/>
<aop:aspect ref="myAdvice">
<aop:before method="before" pointcut-ref="beforePointCut"></aop:before>
<aop:after-returning method="afterReturn"
pointcut-ref="afterReturnPointCut" returning="returnValue"></aop:after-returning>
<aop:after-throwing method="afterThrow"
pointcut-ref="throwPointCut" throwing="exception"></aop:after-throwing>
<aop:after method="after" pointcut-ref="afterPointCut"></aop:after>
<aop:around method="around" pointcut-ref="aroundPointCut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
4. 测试
===》MainApp.java
package com.sst.cx;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.sst.cx.dao.UserDao;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserDao userDao = context.getBean("userDaoImpl",UserDao.class);
userDao.add();
userDao.delete();
userDao.update();
userDao.querry();
userDao.throwException();
}
}
运行结果
示例(AspectJ 注解方式)
1. 导入依赖包
4个核心jar:beans、context、core、expression
AOP特性jar:aop、aspects(Spring提供),aspectjweaver(aspectJ提供)
三方jar:commons-logging.jar
2. UserDao.java、userDaoImpl.java、UserDaoAdvice.java
===》UserDao.java(DAO接口)
package com.sst.cx.dao;
public interface UserDao {
public void add();
public void delete();
public int update();
public void querry();
public void throwException();
}
===》userDaoImpl.java(DAO实现)
package com.sst.cx.dao.impl;
import org.springframework.stereotype.Component;
import com.sst.cx.dao.UserDao;
@Component("userDaoImpl")
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("执行add方法。。。");
}
@Override
public void delete() {
System.out.println("执行delete方法。。。");
}
@Override
public int update() {
System.out.println("执行update方法。。。");
return 100;
}
@Override
public void querry() {
System.out.println("执行querry方法。。。");
}
@Override
public void throwException() {
System.out.println("执行throwException方法。。。");
int x=1/0;
}
}
===》UserDaoAdvice.java(Advice通知,拦截器)
package com.sst.cx.advice;
import org.aspectj.lang.ProceedingJoinPoint;
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.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class UserDaoAdvice {
@Before(value="execution(* com.sst.cx.dao.UserDao.add(..))")
public void before() {
System.out.println("前置增强操作。。。");
}
@After(value="execution(* com.sst.cx.dao.UserDao.delete(..))")
public void after() {
System.out.println("后置增强操作。。。");
}
@Around(value="execution(* com.sst.cx.dao.UserDao.querry(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕前置增强操作。。。");
proceedingJoinPoint.proceed();
System.out.println("环绕后置增强操作。。。");
}
@AfterReturning(value="execution(* com.sst.cx.dao.UserDao.update(..))",returning="returnValue")
public void afterReturn(Object returnValue) {
System.out.println("后置返回增强操作。。。返回值:"+returnValue);
}
@AfterThrowing(value="execution(* com.sst.cx.dao.UserDao.throwException(..))",throwing="exception")
public void afterThrow(Throwable exception) {
System.out.println("异常增强操作。。。异常信息:"+exception.getMessage());
}
}
4. 测试
===》MainApp.java
package com.sst.cx;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.sst.cx.dao.UserDao;
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// 获取代理对象
UserDao userDao = context.getBean("userDaoImpl",UserDao.class);
userDao.add();
userDao.delete();
userDao.update();
userDao.querry();
userDao.throwException();
}
}
运行结果