2.3 AOP面向切面编程
在软件行业中,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
注意
对于实现过接口的类在AOP时,使用基于JDK的动态代理来生成代理对象,从而对功能进行扩展。
对于没有实现接口的类在AOP时,使用CGLIB字节码增强从而生成代理对象(通过设置子类对象,从而对父类的功能进行扩展)
2.3.1 AOP常用概念
- 目标类(target)
需要进行功能扩展的类
- 连接点(joinpoint)
目标类中的所有方法称为连接点
- 切入点(pointCut)
连接点中需要增强(扩展)的方法
- 增强(通知)(advise)
功能扩展
- 织入(weaving)
将增强应用到切入点的过程
- 代理对象(proxy)
织入完成后生成代理对象
- 切面(aspect)
多个切入点组成切面
2.3.2 基于JDK的动态代理
需求:UserDao在调用saveUser方法时,先判断是否为admin,不能修改UserDao的源码
目标类:接口+实现 UserDaoImpl
增强: MyAdvice
切入点:saveUser()
工厂类:DaoBeanFactory 用于生成UserDao的代理对象
package com.sofwin.dao;
public interface UserDao {
void saveUser();
void deleteUser();
}
package com.sofwin.dao.impl;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.sofwin.dao.UserDao;
//如果注解中不声明名称,默认使用类名,首字母小写
public class UserDaoImpl implements UserDao {
@Value("100")
private Integer id;
public UserDaoImpl() {
System.out.println("constructor........userDaoImpl");
}
//init-method
@PostConstruct
public void init2() {
System.out.println("init........");
}
@PreDestroy
public void destroy() {
System.out.println("destroy......");
}
@Override
public void saveUser() {
System.out.println("saveUser|mysql....."+this.id);
}
@Override
public void deleteUser() {
System.out.println("deleteUser|mysql.........");
}
}
package com.sofwin.util;
public class MyAdvice {
public void checkUser() {
System.out.println("checkUser.......");
}
}
工厂类:
package com.sofwin.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.sofwin.dao.UserDao;
/**
* DAO代理对象工厂
* @author Mac
*
*/
public class DaoBeanFactory {
/**
* 用于生成UserDao的代理对象
* @param dao 目标类的实例
* @return 代理对象
*/
public static UserDao createUserDao(UserDao dao) {
//增强对象
final MyAdvice advice = new MyAdvice();
UserDao proxyDao = (UserDao)Proxy.newProxyInstance(DaoBeanFactory.class.getClassLoader(), dao.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取到方法名
String methodName = method.getName();
if("saveUser".equals(methodName)) {
//确定切入点
advice.checkUser();
}
Object obj = method.invoke(dao, args);
return obj;
}
});
return proxyDao;
}
}
测试类:
package com.sofwin.test;
import org.junit.Test;
import com.sofwin.dao.UserDao;
import com.sofwin.dao.impl.UserDaoImpl;
import com.sofwin.util.DaoBeanFactory;
public class JdkProxyTest {
@Test
public void test01() {
//获取目标对象
UserDao dao = new UserDaoImpl();
dao.saveUser();
UserDao daoProxy = DaoBeanFactory.createUserDao(dao);
daoProxy.saveUser();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xVLtwJX7-1625656511466)(img/6.png)]
2.3.3 基于CGLIB的动态代理
没有实现过接口,就不能使用jdk的动态代理。在这种情况下,spring使用了cglib基于字节码增强的动态代理。
CGLIB(Code Generation Library)是一个开源项目。
是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。Hibernate支持它来实现PO(Persistent Object 持久化对象)字节码的动态生成。
-
导入依赖
- cglib.jar ,asm.jar
在spring 核心依赖中已经包含有cglib的依赖
注意
cglib因为基于字节码增强,所以可以对没有实现接口和实现过接口的类进行增强。
目标类:UserDaoSqlImpl不实现任何接口,saveUser delete方法
增强类: MyAdvice (同上)
切入点:saveUser
工厂类:DaoBeanFactory
目标类:
package com.sofwin.dao.impl;
public class UserDaoSqlImpl {
public void saveUser() {
System.out.println("saveUser.....");
}
public void delete() {
System.out.println("delete......");
}
}
工厂类:
package com.sofwin.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.sofwin.dao.UserDao;
/**
* DAO代理对象工厂
* @author Mac
*
*/
import com.sofwin.dao.impl.UserDaoSqlImpl;
public class DaoBeanFactory {
/**
* 用于生成UserDao的代理对象
* @param dao 目标类的实例
* @return 代理对象
*/
public static UserDao createUserDao(UserDao dao) {
//增强对象
final MyAdvice advice = new MyAdvice();
UserDao proxyDao = (UserDao)Proxy.newProxyInstance(DaoBeanFactory.class.getClassLoader(), dao.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取到方法名
String methodName = method.getName();
if("saveUser".equals(methodName)) {
//确定切入点
advice.checkUser();
}
Object obj = method.invoke(dao, args);
return obj;
}
});
return proxyDao;
}
/**
* 基于字节码生成代理对象
* @param target 目标对象
* @return 代理对象
*/
public static UserDaoSqlImpl createSqlImpl(UserDaoSqlImpl target) {
final MyAdvice advice = new MyAdvice();
Callback a;
//cglib的核心类
Enhancer hancer = new Enhancer();
//设置父类
hancer.setSuperclass(target.getClass());
//方法回调处理
hancer.setCallback(new MethodInterceptor() {
//对方法调用的拦截(当方法被调用时先进入该方法)
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
String methodName = method.getName();
if("saveUser".equals(methodName)) {
advice.checkUser();
}
Object obj = method.invoke(target, args);
return obj;
}
});
//生成代理对象
UserDaoSqlImpl proxy = (UserDaoSqlImpl)hancer.create();
return proxy;
}
}
测试类:
package com.sofwin.test;
import org.junit.Test;
import com.sofwin.dao.UserDao;
import com.sofwin.dao.impl.UserDaoImpl;
import com.sofwin.dao.impl.UserDaoSqlImpl;
import com.sofwin.util.DaoBeanFactory;
public class JdkProxyTest {
@Test
public void test01() {
//获取目标对象
UserDao dao = new UserDaoImpl();
dao.saveUser();
UserDao daoProxy = DaoBeanFactory.createUserDao(dao);
daoProxy.saveUser();
}
@Test
public void test02() {
//创建目标对象
UserDaoSqlImpl dao = new UserDaoSqlImpl();
dao.saveUser();
//根据目标对象创建基于字节码增强的代理对象
UserDaoSqlImpl proxy = DaoBeanFactory.createSqlImpl(dao);
proxy.saveUser();
}
}
2.3.4 半自动AOP
2.3.4.1 AOP联盟增强类型
- 前置增强:目标方法执行前的功能增强
- 后置增强:目标方法执行后的功能增强
- 环绕增强:目标方法执行前后都进行了功能扩展
- 异常增强:抛出异常时执行的增强
- 引介增强:对目标对象添加方法和属性(了解)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4XCVmbFK-1625656511472)(img/7.png)]
try{
前置增强
目标方法
后置增强
}catch(Exception ex){
异常增强
}
2.3.4.2 依赖搭建
- 4个核心+2个日志+spring-aop+aopaliance.jar
工厂对象ProxyFactorBean用于生成代理对象,增强必须实现对应的增强接口。
2.3.4.3 半自动AOP编程
目标类:UserDaoImpl
增强类:implement MethodIntercepor接口
工厂类:使用spring提供的ProxyFactorBean
增强类:
package com.sofwin.util;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 环绕增强
* @author Mac
*
*/
public class MyAdviceInterceptor implements MethodInterceptor {
/**
* 反射方法
*
*/
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("前置增强.....");
//调用或执行目标方法
Object obj = methodInvocation.proceed();
System.out.println("后置增强.....");
return obj;
}
}
工厂类配置:
<?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"
xmlns:s="http://www.springframework.org/schema/p"
xsi:schemaLocation="
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.xsd">
<!--
目标类
-->
<bean id="userDao" class="com.sofwin.dao.impl.UserDaoImpl"></bean>
<!--
增强类
-->
<bean id="myAdvice" class="com.sofwin.util.MyAdviceInterceptor"></bean>
<!--
工厂类
-->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--
taret:注入目标对象,参数类型Object(引用类型)
-->
<property name="target" ref="userDao"></property>
<!--
interceporNames:注入增强,如果是单个直接使用value
-->
<property name="interceptorNames" value="myAdvice"></property>
<!--
如果使用jdk动态代理,需要使用Interfaces来指定目标类实现的接口
-->
<property name="interfaces" value="com.sofwin.dao.UserDao"></property>
<!--
optimize:是否强制使用cglib
如果为false,spring会根据是否实现接口来调用jdk动态代理
如果为true,即使目标类实现了接口,也会使用cglib
-->
<property name="optimize" value="true"></property>
</bean>
</beans>
测试类:
@Test
public void test03() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//取代理对象的配置名称
UserDao dao = (UserDao)context.getBean("userDaoProxy");
dao.saveUser();
}
2.3.5 传统AOP
- 导入AOP约束
- 导入weaver包
目标类:UserDaoImpl
增加类:必须实现指定接口MethodInterceptor MyAdviseInterceptor
工厂对象不需要spring来配置,直接使用AOP标签
配置文件:
<?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"
xmlns:s="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
所以以save开头的方法进行增强
-->
<!--
目标类的配置
-->
<bean id="userDao" class="com.sofwin.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoSql" class="com.sofwin.dao.impl.UserDaoSqlImpl"></bean>
<!--
增强的配置
-->
<bean id="myAdvise" class="com.sofwin.util.MyAdviceInterceptor"></bean>
<!--
传统AOP配置标签
proxy-target-class:是否强制使用cglib
-->
<aop:config proxy-target-class="true">
<!--
aop:pointcut:代表成员变量,在真个aop:config中的所有的aop:advisor都可以去通过pointcut-ref去使用它
-->
<aop:pointcut expression="execution(* com.sofwin.dao.impl.*.save*(..))" id="point1"/>
<!--
配置增强:
advice-ref:配置增强的引用得知
pointcut:切面,切入点表达式:用于寻找目标类并确定切入点,要求所有的目标类
是基于execution()函数的
pointcut-ref:是用于指定切面的ID,需要手动定义
pointcut:方法中的局部变量,他只能被引用他的advisor来使用
-->
<aop:advisor advice-ref="myAdvise" pointcut-ref="point1"/>
</aop:config>
</beans>
测试类:
package com.sofwin.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.sofwin.dao.UserDao;
import com.sofwin.dao.impl.UserDaoImpl;
import com.sofwin.dao.impl.UserDaoSqlImpl;
import com.sofwin.util.DaoBeanFactory;
public class JdkProxyTest {
@Test
public void test01() {
//获取目标对象
UserDao dao = new UserDaoImpl();
dao.saveUser();
UserDao daoProxy = DaoBeanFactory.createUserDao(dao);
daoProxy.saveUser();
}
@Test
public void test02() {
//创建目标对象
UserDaoSqlImpl dao = new UserDaoSqlImpl();
dao.saveUser();
//根据目标对象创建基于字节码增强的代理对象
UserDaoSqlImpl proxy = DaoBeanFactory.createSqlImpl(dao);
proxy.saveUser();
}
@Test
public void test03() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//取代理对象的配置名称
UserDao dao = (UserDao)context.getBean("userDaoProxy");
dao.saveUser();
}
@Test
public void test04() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//使用原来的bean的ID,但获取到的是目标对象的代理对象,因为满足切入点表达式
//织入的过程 aopweaving
UserDao dao = (UserDao)context.getBean("userDao");
dao.saveUser();
}
@Test
public void test05() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//使用原来的bean的ID,但获取到的是目标对象的代理对象,因为满足切入点表达式
//织入的过程 aopweaving
UserDaoSqlImpl dao = (UserDaoSqlImpl)context.getBean("userDaoSql");
dao.saveUser();
}
}
2.3.6 切入点表达式
切入点表达式是基于execution函数的,execution(表达式)
修饰符 返回值 包名.类名.方法名() throws
1修饰符(public private * )【一般省略】
2返回值(Integer int void *)不能省略
3.包名com.sofwin 【不能省】
4.类名(全称也可以使用通配符)
5.方法名(可以是全称也可以是通配符)
6.括号中输入形参类型,(…)代表任意个任意类型的参数
7.抛出的异常【可以省略的】
execution(* com.sofwin.dao.impl.UserDaoImpl.*(…))
exectuion(* com.sofwin.dao.impl.*.*(..)) || execution(* com.sofwin.util.*.*(..))
2.3.7 基于AspectJ的AOP
- AspectJ是一个java的AOP框架
- Spring 2.0开始支持AspectJ
- @Aspect是AspectJ的新注解,基于JDK5的注解,可以直接在java Bean上声明
商用场景:自定义代码或自定义增强推荐使用
框架搭建
spring-aspects.jar
基于aspectJ的增强类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RgvCU7MY-1625656511475)(img/8.png)]
try{
前置增强(准备资源)
目标方法
后置增强(功能扩展)
}cache(){
异常增强(异常处理)
}fianlly{
最终增强(释放资源)
}
2.3.7.1 xml方式的AspectJ
目标类:UserDaoImpl
增强类:MyAspectJ
工厂对象也有spring容器自动生成
增强类:
package com.sofwin.util;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
public class MyAspectj {
public void before(JoinPoint point) {
Signature signature = point.getSignature();
System.out.println("before........"+signature.getName());
}
/**
*
*/
public void afterReturnning(JoinPoint point,Object obj) {
System.out.println("after returnning......."+obj);
}
/**
*
* @param point
* @throws Throwable
*/
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("前置....");
//调用目标方法
Object obj = point.proceed();
System.out.println("后置......");
return obj;
}
public void afterThrowing(Throwable ex) {
if(ex instanceof ArithmeticException) {
System.out.println("throwing........除0异常");
}
}
public void after() {
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:context="http://www.springframework.org/schema/context"
xmlns:s="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
所以以save开头的方法进行增强
-->
<!--
目标类的配置
-->
<bean id="userDao" class="com.sofwin.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoSql" class="com.sofwin.dao.impl.UserDaoSqlImpl"></bean>
<!--
增强
-->
<bean id="myAspectj" class="com.sofwin.util.MyAspectj"></bean>
<!--
基于aspectj的AOP织入生成代理对象使用aop:config
-->
<aop:config proxy-target-class="true">
<!--
定义aspect
id:唯一名称
ref:用于指定普通的增强类
-->
<aop:aspect id="aspect1" ref="myAspectj">
<aop:pointcut expression="execution(* com.sofwin.dao.impl.*.save*(..))" id="p1"/>
<!--
aop:before 前置增强
method:指定增强类中前置增强的方法的方法名
pointcut:定义局部切入点,只能当前before使用
pointcut-ref:可以使用公共的切入点
arg-names:用于定义前置增强获取目标方法的相关数据,前置增强的方法中的JoinPoint类型的参数的参数名
-->
<aop:before method="before" pointcut-ref="p1" arg-names="point"/>
<!--
配置后置增强:
method:用于指定后置增强的方法名
returning:用于获取目标方法的返回值的,
第一个参数为JoinPoint 用于获取目标方法的信息
after-retunning增强方法中的第二个参数,类型Object
-->
<aop:after-returning method="afterReturnning" pointcut-ref="p1" returning="obj"/>
<!--
环绕增强:
method:方法名
-->
<aop:around method="around" pointcut-ref="p1"/>
<!-- 异常增强
throwing:用于获取捕获的异常信息
-->
<aop:after-throwing method="afterThrowing" pointcut-ref="p1" throwing="ex"/>
<!-- 指定最终增强 -->
<aop:after method="after" pointcut-ref="p1"/>
</aop:aspect>
</aop:config>
</beans>
2.3.7.2 基于注解的AspectJ
- 开启组件扫描
- 开启aspect代理驱动
<?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"
xmlns:s="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
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.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.sofwin"></context:component-scan>
<!-- 开启注解驱动
用来配置强制使用cglib
-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
<!--
所以以save开头的方法进行增强
-->
<!--
目标类的配置
-->
<bean id="userDao" class="com.sofwin.dao.impl.UserDaoImpl"></bean>
<bean id="userDaoSql" class="com.sofwin.dao.impl.UserDaoSqlImpl"></bean>
</beans>
package com.sofwin.util;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
//IOC
@Component
//用于声明该类是一个增强类<aop:aspect ref="">
@Aspect
public class MyAspectj {
//等于<aop:before
//value中放切入点表达式
@Before(value = "a()")
public void before(JoinPoint point) {
Signature signature = point.getSignature();
System.out.println("before........"+signature.getName());
}
/**
*
*/
//value:切入点表达式
//returnning <aop:after-returning returning="")
@AfterReturning(value = "a()",returning = "obj")
public void afterReturnning(JoinPoint point,Object obj) {
System.out.println("after returnning......."+obj);
}
/**
*
* @param point
* @throws Throwable
*/
@Around(value="a()")
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("前置....");
//调用目标方法
Object obj = point.proceed();
System.out.println("后置......");
return obj;
}
@AfterThrowing(value="a()",throwing = "ex")
public void afterThrowing(Throwable ex) {
if(ex instanceof ArithmeticException) {
System.out.println("throwing........除0异常");
}
}
@After(value="a()")
public void after() {
System.out.println("清理资源");
}
//<aop:pointcut expression=""
@Pointcut(value="execution(* com.sofwin.dao.impl.*.save*(..))")
public void a() {
}
}
总结
@Aspect //用于声明该类是一个增强类
@Before //用于声明前置增强
@AfterReturning //声明后置增强
@Aroud //声明环绕增强
@After //声明最终增强
@AfterThrowing //声明异常增强
@PointCut //用于声明切入点