Spring AOP(重点、难点)
文章目录
- Spring AOP(重点、难点)
- 1.aop引入
- 1.1 使用场景与概念引入
- 1.2 以数据校验+记录操作日志为例 写一组代码进行递推
- 初始阶段 老老实实一个一个写:
- 阶段一 **将日志和验证方法包装到一个类里,让实现类去继承**
- 阶段二 包装成静态方法去调用
- 2.AOP概念
- 3.Aspectj
- 3.1 导入包(需确认是否已经导入aop的包)
- 3.2 xml方式注册
- 3.2.1 恢复service只保留核心代码 把需要集中处理的事项写进新建的切面pojo
- 3.2.2 xml中配置bean
- 3.3 注解@AspectJ方式注册
- 3.3.1 demo
- 3.3.2 不同advice的执行顺序
- Effects of precedence
- 5. AspectJ切入点@Pointcut语法(查阅使用)
- 5.1 类型匹配语法
- 5.2 组合切入点表达式
- 5.3 切入点使用示例
- 5.4 demo
- 4.引申 静态代理与动态代理 (拓展 进阶要求)
- 静态代理
- 动态代理
- aop实现原理
1.aop引入
1.1 使用场景与概念引入
除此之外可能还会有 :日志记录,性能监测,安全控制等功能,它不属于我们最终最关注的业务部分,但是却贯穿了我们系统设计、程序设计的各个环节。
1.2 以数据校验+记录操作日志为例 写一组代码进行递推
初始阶段 老老实实一个一个写:
public interface IUserService {
//获取一个用户
public void getUserInfo();
//插入一条用户记录
public void insertUser();
//删除一个用户
public void delUserInfo();
}
public class UserServiceImpl implements IUserService {
@Override
public void getUserInfo() {
System.out.println("获取了用户信息-----");
}
@Override
public void insertUser() {
checkData();
System.out.println("插入一条数据");
insertLog();
}
@Override
public void delUserInfo() {
checkData();
System.out.println("删除一个用户");
insertLog();
}
//需要在执行前校验是否有非法字符
public void checkData(){
System.out.println("校验数据完成");
}
//需要在执行结束后记录日志
public void insertLog(){
System.out.println("记录日志");
}
}
public class springAOP01 {
@Test
public void test01(){
IUserService userService = new UserServiceImpl();
userService.insertUser();
userService.delUserInfo();
}
}
但这样写 其他类也调用同样的验证和日志方法怎么办? 每个service都要重复写一样的代码/去调用别的service内的方法? 这都不是很妥当的办法
阶段一 将日志和验证方法包装到一个类里,让实现类去继承
/**
* 将公共部分单独写一个service 让其他service去继承
* */
public class BaseService {
//需要在执行前校验是否有非法字符
public void checkData(){
System.out.println("校验数据完成");
}
//需要在执行结束后记录日志
public void insertLog(){
System.out.println("记录日志");
}
}
public class UserServiceImpl extends BaseService implements IUserService {
@Override
public void getUserInfo() {
System.out.println("获取了用户信息-----");
}
@Override
public void insertUser() {
checkData();
System.out.println("插入一条数据");
insertLog();
}
@Override
public void delUserInfo() {
checkData();
System.out.println("删除一个用户");
insertLog();
}
}
因为Java是单继承,如果service本身需要要继承其他类就不行了。
阶段二 包装成静态方法去调用
public class LogUtils {
//需要在执行结束后记录日志
public static void insertLog(){
System.out.println("记录日志");
}
}
public class CheckDataUtils {
//需要在执行前校验是否有非法字符
public static void checkData(){
System.out.println("校验数据完成");
}
}
public class UserServiceImpl implements IUserService {
@Override
public void getUserInfo() {
System.out.println("获取了用户信息-----");
}
@Override
public void insertUser() {
CheckDataUtils.checkData();
System.out.println("插入一条数据");
LogUtils.insertLog();
}
@Override
public void delUserInfo() {
CheckDataUtils.checkData();
System.out.println("删除一个用户");
LogUtils.insertLog();
}
}
特点:非业务代码与主业务深度耦合。
1.主业务代码会大量出现非业务代码的调用语句,大大影响主业务代码的可读性。
2.如果我们需要砍掉某个功能,降低代码的可维护性,也增加了开发难度。
那么我们要怎样降低这部分公共部分和主业务之间的耦合呢?
2.AOP概念
AOP的概念:
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性。
让我们可以“专心做事” 只关注自己的业务,提升代码的稳定性和可维护性
AOP原理
将复杂的需求分解出不同方面,将散布在系统中的公共功能集中解决
采用代理机制组装起来运行,在不改变原程序的基础上对代码段进行增强处理,增加新的功能
核心业务流程为纵向
非核心业务流程为横向
以下名词需要了解下:
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 … 即公共非核心业务流程
切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
通知(Advice):切面必须要完成的工作。也就是我想通过aop来做什么事情。
-
前置@Before、后置@After、环绕@Around、抛出异常@AfterThrowing、后置返回值增强@AfterReturning
-
·
@AfterReturning
, 在一个 join point 正常返回后执行的 advice -
@After
, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
目标(Target):被通知对象。核心业务流程。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):切面通知 执行的 “地点”的定义。提供一组规则来匹配joinpoint, 给满足规则的 joinpoint 添加 Advice
.
连接点(JointPoint):与切入点匹配的执行点。所有可能执行advice的点,所有的方法都可能执行advice,但不一定都会执行。
====
AOP提倡的是针对同一类问题的统一处理,并且可以在不改变原有代码、无入侵的情况下去完成新的公共非核心业务部分。
3.Aspectj
3.1 导入包(需确认是否已经导入aop的包)
<!--aspectj-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
3.2 xml方式注册
3.2.1 恢复service只保留核心代码 把需要集中处理的事项写进新建的切面pojo
public class UserServiceImpl implements IUserService {
@Override
public String getUserInfo() {
System.out.println("获取了用户信息-----");
return "这是一条用户信息";
}
@Override
public void insertUser() {
System.out.println("插入一条数据");
}
@Override
public void delUserInfo() {
System.out.println("删除一个用户");
}
}
public class MyAspect {
public void before(){
System.out.println("before 前置通知=======");
CheckDataUtils.checkData();
}
public void after(){
System.out.println("after 后置通知 ========");
LogUtils.insertLog();
}
public void afterReturning(Object result){
System.out.println("afterRunning 后置返回值通知 ========"+result.toString());
}
}
3.2.2 xml中配置bean
<bean id="myAspect" class="com.liyw.springAOP.interceptor.MyAspect"/>
<bean id="myService" class="com.liyw.springAOP.service.UserServiceImpl"></bean>
3.2.3 xml中配置aop
先配置约束
xmlns:aop=“http://www.springframework.org/schema/aop”
schemaLocation中的
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="myAspect" class="com.liyw.springAOP.interceptor.MyAspect"/>
<bean id="myService" class="com.liyw.springAOP.service.UserServiceImpl"></bean>
<aop:config>
<!--定义切入点-->
<aop:pointcut id="insertUserCut" expression="execution(* com.liyw.springAOP.service.UserServiceImpl.insert*(..))"/>
<aop:pointcut id="deleteUserCut" expression="execution(* com.liyw.springAOP.service.UserServiceImpl.de*(..))"/>
<aop:pointcut id="getUserCut" expression="execution(* com.liyw.springAOP.service.UserServiceImpl.getUserInfo())"/>
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut-ref="insertUserCut"></aop:before>
<aop:after method="after" pointcut-ref="deleteUserCut"/>
<aop:after-returning method="afterReturning" pointcut-ref="getUserCut" returning="result"></aop:after-returning>
</aop:aspect>
</aop:config>
</beans>
3.2.3 测试
@Test
public void test(){
ApplicationContext context=new ClassPathXmlApplicationContext("springAOP.xml");
IUserService userService = context.getBean("myService",IUserService.class);
userService.getUserInfo();
System.out.println("=========");
userService.insertUser();
System.out.println("=========");
userService.delUserInfo();
}
3.3 注解@AspectJ方式注册
3.3.1 demo
改写切面
import org.aspectj.lang.annotation.*;
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.liyw.springAspect.service.UserServiceImpl.insert*(..))")
public void before(){
System.out.println("before 前置通知=======");
CheckDataUtils.checkData();
}
@After("execution(* com.liyw.springAspect.service.UserServiceImpl.de*(..))")
public void after(){
System.out.println("after 后置通知 ========");
LogUtils.insertLog();
}
@AfterReturning(value="execution(* com.liyw.springAspect.service.UserServiceImpl.getUserInfo())",
returning="result")
public void afterReturning(Object result){
System.out.println("afterRunning 后置返回值通知 ========"+result.toString());
}
}
改写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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:component-scan base-package="com.liyw.lesson02" />
<!--通过aop命名空间的<aop:aspectj-autoproxy />
声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 需要着重掌握:@before @after 以及@around的写法!
@Before("execution(* com.liyw.service.UserServiceImpl.insertUser())")
public void beforeOther(JoinPoint jp){
System.out.println("注解配置AOP前置增强(切面表达式)"+jp);
System.out.println("注解配置AOP前置增强(方法签名)"+jp.getSignature());
System.out.println("注解配置AOP前置增强(目标对象)"+jp.getTarget());
Object[] args=jp.getArgs();
if(args.length!=0) {
System.out.println("注解配置AOP前置增强(目标对象)方法参数为:");
for (Object object : args) {
System.out.println(object+" ");
}
}
}
@Around(value="execution(* com.liyw.service.UserServiceImpl.insertUser())")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("注解配置AOP环绕通知前:");
Object result=pjp.proceed();
System.out.println("注解配置AOP环绕通知后:");
return result;
}
对JoinPoint和ProceedingJoinPoint做一个说明:
- JoinPoint 对象
JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以获取到封装了该方法信息的JoinPoint对象.常用api:
方法名 | 功能 |
---|---|
Signature getSignature(); | *获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息* |
Object[] getArgs(); | 获取传入目标方法的参数对象 |
Object getTarget(); | 获取被代理的对象 |
Object getThis(); | 获取代理对象 |
- ProceedingJoinPoint对象
ProceedingJoinPoint对象是JoinPoint的子接口,该对象只用在@Around的切面方法中,除上述api外,还添加了 如下两个方法.
Object proceed() throws Throwable //执行目标方法
Object proceed(Object[] var1) throws Throwable //传入的新的参数去执行目标方法
3.3.2 不同advice的执行顺序
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.liyw.service.UserServiceImpl.getUser())")
public void before1(){
System.out.println("这是一个before通知");
}
@After("execution(* com.liyw.service.UserServiceImpl.getUser())")
public void after(){
System.out.println("这是一个after通知");
}
@AfterReturning(value="execution(* com.liyw.service.UserServiceImpl.getUser())"
,returning = "result")
public void afterReturning(Object result) {
System.out.println("这是一个afterrunning的通知:"+result.toString());
}
@Around(value="execution(* com.liyw.service.UserServiceImpl.getUser())")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("注解配置AOP环绕通知前:");
Object result=pjp.proceed();
System.out.println("注解配置AOP环绕通知后:");
return result;
}
@AfterThrowing(value="execution(* com.liyw.service.UserServiceImpl.getUser())",throwing="ex")
public void afterThrowing(Throwable ex){
System.out.println("afterThrowing通知");
System.out.println("抛出异常"+ex);
}
}
-
以我们现在用的spring版本5.2.12为例:
spring aop “责任链模式”
官网对于aop执行顺序原文:https://docs.spring.io/spring-framework/docs/5.2.12.RELEASE/spring-framework-reference/core.html#aop-ataspectj-advice-ordering
Each of the distinct advice types of a particular aspect is conceptually meant to apply to the join point directly. As a consequence, an
@AfterThrowing
advice method is not supposed to receive an exception from an accompanying@After
/@AfterReturning
method.As of Spring Framework 5.2.7, advice methods defined in the same@Aspect
class that need to run at the same join point are assigned precedence based on their advice type in the following order, from highest to lowest precedence:@Around
,@Before
,@After
,@AfterReturning
,@AfterThrowing
. Note, however, that an@After
advice method will effectively be invoked after any@AfterReturning
or@AfterThrowing
advice methods in the same aspect, following AspectJ’s “after finally advice” semantics for@After
.When two pieces of the same type of advice (for example, two@After
advice methods) defined in the same@Aspect
class both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the source code declaration order through reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each@Aspect
class or refactor the pieces of advice into separate@Aspect
classes that you can order at the aspect level viaOrdered
or@Order
.
从Spring Framework 5.2.7开始,在同一@Aspect类中定义的需要在同一连接点上运行的通知方法根据其通知类型按以下顺序分配优先级:==从最高优先级到最低优先级:@Around、@Before、@After、@AfterReturning、@AfterThrowing。==但是,请注意,@After-advice方法将在同一方面的任何@afterReturning或@afterThrowing方法之后有效地调用,遵循AspectJ对@After的“After-finally-advice”语义。
当在同一@Aspect类中定义的两条相同类型的通知(例如,两个@After通知方法)都需要在同一连接点上运行时,顺序是未定义的(因为无法通过对javac编译类的反射来检索源代码声明顺序)。考虑将这样的建议方法折叠成每个@方面类中的每个连接点的一个建议方法,或者将这些建议重构成单独的@方面类,您可以通过有序或@命令在方面级别上进行排序。
-
根据官网说明,不同advice织入同一个方法的执行顺序:
-
在一个方法只被一个aspect类拦截时,aspect类内部的 advice 将按照以下的顺序进行执行:
-
-
正常情况:
进入方法-->@AROUND 前置-->@BEFORE--> 方法执行 -->@AfterReturning-->@After -->@AROUND后置
- 异常情况:
进入方法–>@AROUND 前置–>@BEFORE–> 方法执行 -->@AfterThrowing–> -->@After
-
-
- 注:如果有多个切面,那么相同的通知,aspect1 和 aspect2 的执行顺序会按照spring默认规则根据首字母的排序默认进行。当我们需要指定一个切面的顺序,我们可以给切面添加@Order注解,该注解全称为:org.springframework.core.annotation.Order。格式如@Order(5),那么这个注解后面值越小的 aspect 越先执行。如果没有跟指定的值,默认是最低优先级。
补充:
1.关于aop执行顺序,spring的旧版本,即5.2.7以前的版本使用的是aspectj的优先级,也就是网络上搜索关于aop执行顺序更常见的那个执行流程图。
官网链接:https://www.eclipse.org/aspectj/doc/next/progguide/semantics-advice.html
Effects of precedence
At a particular join point, advice is ordered by precedence.
A piece of
around
advice controls whether advice of lower precedence will run by callingproceed
. The call toproceed
will run the advice with next precedence, or the computation under the join point if there is no further advice.A piece of
before
advice can prevent advice of lower precedence from running by throwing an exception. If it returns normally, however, then the advice of the next precedence, or the computation under the join pint if there is no further advice, will run.Running
after returning
advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation returned normally, the body of the advice will run.Running
after throwing
advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then, if that computation threw an exception of an appropriate type, the body of the advice will run.Running
after
advice will run the advice of next precedence, or the computation under the join point if there is no further advice. Then the body of the advice will run.
翻译一下它的优先级排序从高到低 @around–》@before–》@after returning–》@after throwing–》@after
2.根据实际情况,我们使用注解方式配置时,执行顺序是相对统一的,同上;但是使用xml方式配置时,xml中关于各个切点配置的先后顺序(例如:aop:before和aop:around的配置顺序)会影响执行顺序。
5. AspectJ切入点@Pointcut语法(查阅使用)
- 作用
用来标注在方法上来定义切入点。
- 定义
格式:@ 注解(value=“表达标签 (表达式格式)”)
如:
@Pointcut("execution(* com.javacode2018.aop.demo9.test1.Service1.*(..))")
-
表达式标签(10种)
-
execution:用于匹配方法执行的连接点
-
within:用于匹配指定类型内的方法执行
-
this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
-
target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配
-
args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
-
@within:用于匹配所以持有指定注解类型内的方法
-
@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
-
@args:用于匹配当前执行的方法传入的参数持有指定注解的执行
-
@annotation:用于匹配当前执行方法持有指定注解的方法
-
bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法
-
我们主要讲解的是execution表达式:
命名切入点可以被其他切入点引用,而匿名切入点是不可以的。
只有@AspectJ支持命名切入点,而Schema风格不支持命名切入点。如下所示,@AspectJ使用如下方式引用命名切入点:
5.1 类型匹配语法
首先让我们来了解下AspectJ类型匹配的通配符:
*:匹配任何数量字符;
…:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
**+:**匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
java代码:
java.lang.String 匹配String类型;
java.*.String 匹配java包下的任何“一级子包”下的String类型;如匹配java.lang.String,但不匹配java.lang.ss.String
java..* 匹配java包及任何子包下的任何类型;如匹配java.lang.String、java.lang.annotation.Annotation
java.lang.*ing 匹配任何java.lang包下的以ing结尾的类型;
java.lang.Number+ 匹配java.lang包下的任何Number的自类型;如匹配java.lang.Integer,也匹配java.math.BigInteger
5.2 组合切入点表达式
AspectJ注解使用 且(&&)、或(||)、非(!)来组合切入点表达式。
5.3 切入点使用示例
execution:**使用“execution(方法表达式)”匹配方法执行;
模式 | 描述 |
---|---|
public * *(…) | 任何公共方法的执行 |
* cn.javass…IPointcutService.*() | cn.javass包及所有子包下IPointcutService接口中的任何无参方法 |
* cn.javass….(…) | cn.javass包及所有子包下任何类的任何方法 |
* cn.javass…IPointcutService.() | cn.javass包及所有子包下IPointcutService接口的任何只有一个参数方法 |
* (!cn.javass…IPointcutService+).*(…) | 非“cn.javass包及所有子包下IPointcutService接口及子类型”的任何方法 |
* cn.javass…IPointcutService+.*() | cn.javass包及所有子包下IPointcutService接口及子类型的的任何无参方法 |
* cn.javass…IPointcut*.test*(java.util.Date) | cn.javass包及所有子包下IPointcut前缀类型的的以test开头的只有一个参数类型为java.util.Date的方法,注意该匹配是根据方法签名的参数类型进行匹配的,而不是根据执行时传入的参数类型决定的如定义方法:public void test(Object obj);即使执行时传入java.util.Date,也不会匹配的; |
* cn.javass…IPointcut*.test*(…) throws IllegalArgumentException, ArrayIndexOutOfBoundsException | cn.javass包及所有子包下IPointcut前缀类型的的任何方法,且抛出IllegalArgumentException和ArrayIndexOutOfBoundsException异常 |
* (cn.javass…IPointcutService+&& java.io.Serializable+).*(…) | 任何实现了cn.javass包及所有子包下IPointcutService接口和java.io.Serializable接口的类型的任何方法 |
@java.lang.Deprecated * *(…) | 任何持有@java.lang.Deprecated注解的方法 |
@java.lang.Deprecated @cn.javass…Secure * *(…) | 任何持有@java.lang.Deprecated和@cn.javass…Secure注解的方法 |
@(java.lang.Deprecated || cn.javass…Secure) * *(…) | 任何持有@java.lang.Deprecated或@ cn.javass…Secure注解的方法 |
(@cn.javass…Secure *) *(…) | 任何返回值类型持有@cn.javass…Secure的方法 |
* (@cn.javass…Secure ).(…) | 任何定义方法的类型持有@cn.javass…Secure的方法 |
* (@cn.javass…Secure () , @cn.javass…Secure (*)) | 任何签名带有两个参数的方法,且这个两个参数都被@ Secure标记了,如public void test(@Secure String str1, @Secure String str1); |
* *((@ cn.javass…Secure ))或 *(@ cn.javass…Secure *) | 任何带有一个参数的方法,且该参数类型持有@ cn.javass…Secure;如public void test(Model model);且Model类上持有@Secure注解 |
* *(@cn.javass…Secure (@cn.javass…Secure *) ,@ cn.javass…Secure (@cn.javass…Secure *)) | 任何带有两个参数的方法,且这两个参数都被@ cn.javass…Secure标记了;且这两个参数的类型上都持有@ cn.javass…Secure; |
* *(java.util.Map<cn.javass…Model, cn.javass…Model>, …) | 任何带有一个java.util.Map参数的方法,且该参数类型是以< cn.javass…Model, cn.javass…Model >为泛型参数;注意只匹配第一个参数为java.util.Map,不包括子类型;如public void test(HashMap<Model, Model> map, String str);将不匹配,必须使用“* *(java.util.HashMap<cn.javass…Model,cn.javass…Model>, …)”进行匹配;而public void test(Map map, int i);也将不匹配,因为泛型参数不匹配 |
* *(java.util.Collection<@cn.javass…Secure *>) | 任何带有一个参数(类型为java.util.Collection)的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass…Secure注解;如public void test(Collection collection);Model类型上持有@cn.javass…Secure |
* *(java.util.Set<? extends HashMap>) | 任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型继承与HashMap;Spring AOP目前测试不能正常工作 |
* *(java.util.List<? super HashMap>) | 任何带有一个参数的方法,且传入的参数类型是有一个泛型参数,该泛型参数类型是HashMap的基类型;如public voi test(Map map);Spring AOP目前测试不能正常工作 |
* (<@cn.javass…Secure *>) | 任何带有一个参数的方法,且该参数类型是有一个泛型参数,该泛型参数类型上持有@cn.javass…Secure注解;Spring AOP目前测试不能正常工作 |
5.4 demo
在切面内:
@Aspect
@Component
public class MyAspect3 {
//匹配MerchantServiceImpl的所有方法
@Pointcut("execution(* com.liyw.service.MerchantServiceImpl.*(..))")
private void logSender(){}
//匹配UserServiceImpl的所有方法
@Pointcut("execution(* com.liyw.service.UserServiceImpl.*(..))")
private void logReceiver(){}
//匹配MerchantServiceImpl的所有方法+UserServiceImpl的所有方法
@Pointcut("logSender() || logReceiver()")
private void logMessage(){
}
@Before("logMessage()")
private void pointCut01(){
System.out.println("pointCut语法--before");
}
}
- 还可以将一些公用的@Pointcut放到一个类中,以供整个应用程序使用(公用切点属性应当是public)
@Aspect
@Component
public class MyAspect4 {
@Around("com.liyw.aspect.MyAspect3.logSender()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("注解配置AOP环绕通知前:");
Object result=pjp.proceed();
System.out.println("注解配置AOP环绕通知后:");
return result;
}
}
-
如果切点带有参数 注释配置:
//定义切点表达式(注解) @Pointcut("execution(* com.liyw.pojo.Person.sayHello())") private void anyMethod() {}//声明一个切入点 // //定义事前通知before(注解) // @Before(value="anyMethod() && args(a1,b2)", argNames="a1,b2") // public void doAccessCheck(String a1,String b2) { // System.out.println("前置通知:"+a1+b2); // } //定义事前通知before(注解) @Before(value="anyMethod() && args(name)") public void doAccessCheck(String name) { System.out.println("前置通知:"+name); } //定义事后返回通知AfterReturning(注解) @AfterReturning(value="anyMethod() && args(result)") public void doAfterReturning(String result) { System.out.println("后置通知:"+ result); } //定义事后通知After(注解) @After("anyMethod()") public void doAfter() { System.out.println("最终通知"); } //定义事后异常通知AfterThrowing(注解) @AfterThrowing(value="anyMethod() && args(e)") public void doAfterThrowing(Exception e) { System.out.println("例外通知:"+ e); } //定义环绕通知Around(注解) @Around("anyMethod()") public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { //if(){//判断用户是否在权限 System.out.println("进入方法"); Object result = pjp.proceed(); System.out.println("退出方法"); //} return result; } @Pointcut("execution(* com.dfrz.lesson03.aop2.service.IUserService.say*(int)) && args(num)") public void sayArgsMethod(int num){} @Before("sayArgsMethod(num)") public void before2(int num){ System.out.println("前置通知AOP获取方法参数:"+num); }
-
如果带有参数 xml配置
<aop:config> <aop:pointcut id="personCut" expression="execution(* com.liyw.pojo.Person.sayHello(..))"/> <!--如果一个advice需要配置多个不同方法--> <aop:pointcut id="unionUserCut" expression="execution(* com.liyw.lesson02.service.UserServiceImpl.insert*(..)) || execution(* com.liyw.lesson02.service.UserServiceImpl.de*(..))"></aop:pointcut> <aop:aspect ref="myInterceptor"> <aop:before method="doAccessCheck" pointcut="execution(* com.liyw.pojo.Person.sayHello(..)) and args(name)"></aop:before> <aop:after-returning method="doAfterReturning" pointcut-ref="personCut" returning="result"></aop:after-returning> <aop:after method="doAfter" pointcut-ref="personCut"></aop:after> <aop:after-throwing method="doAfterThrowing" pointcut-ref="personCut" throwing="e"></aop:after-throwing> <aop:around method="doBasicProfiling" pointcut-ref="personCut"></aop:around> </aop:aspect> </aop:config>
4.引申 静态代理与动态代理 (拓展 进阶要求)
综述:
- 静态代理中,代理(Proxy)是自己定义好的,在程序运行之前就已经编译完成。
- 动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。
- 相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
这是常见代理模式常见的 UML 示意图。
-
用户只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
-
接口真正实现者是上图的 RealSubject,但是它不与用户直接接触,而是通过代理。
-
代理就是上图中的 Proxy,由于它实现了 Subject 接口,所以它能够直接与用户接触。
-
用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。
静态代理
静态代理角色分析
- 抽象角色 : 一般使用接口或者抽象类来实现
- 真实角色 : 被代理的角色
- 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
- 客户 : 使用代理角色来进行一些操作 .
现实场景
房东(真实角色,被代理人)想要把自己的房子租出去,他找到中介公司,和中介公司签署委托合同–》那么对应负责的房屋中介(代理角色)和房东本人都有了共同关心的事情,就是租房(抽象角色,被代理的接口)。
**有个人(客户)**想要租房子,他直接去房屋中介中心,中介带他看房等等,最 终和他签订租房合同,代替房东把房子租给他。全程房客没有见到房东,而是通过中介代理人完成了租房的全部操作。
代码实现
Rent . java 即抽象角色
public interface Rent {
//共同关心的事情- subject主体 抽象角色:租房
public void rent();
}
Host . java 即真实角色
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
Proxy . java 即代理角色
//代理角色:中介
public class Proxy implements Rent {
private Host host;
public Proxy() { }
public Proxy(Host host) {
this.host = host;
}
//租房
public void rent(){
seeHouse();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client . java 即客户
//客户类,一般客户都会去找代理!
public class Client {
public static void main(String[] args) {
//房东要租房
Host host = new Host();
//中介帮助房东
Proxy proxy = new Proxy(host);
//你去找中介!
proxy.rent();
}
}
静态代理的好处:
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。
缺点 :
值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。
比如说房屋中介除了承担租房之外,还承担卖房的工作。如果想要代码实现,我们还需要配合房东另外写一个卖房的静态代理。
interface Rent{
void rent();
//新增一个卖房的方法
void sell();
}
class Host implements Rent{
@Override
public void rent() {
System.out.println("房东租房");
}
@Override
public void sell() {
System.out.println("房东卖房");
}
}
//代理角色 也需要实现租房的方法
class Proxy implements Rent {
// 持有一个房东对象 传递一个真实的房东对象
private Host host;
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
// 带租客看房
seeHouse();
// 中介是不可以直接租房的 他只能通过房东的授权才能完成对应的事情
host.rent();
// 签合同收收钱
fare();
}
@Override
public void sell() {
// 带租客看房
seeHouse();
// 中介是不可以直接租房的 他只能通过房东的授权才能完成对应的事情
host.sell();
// 签合同收收钱
fare();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
public class Main {
public static void main(String[] args) {
租房 没有静态代理
Rent host1 = new Host();
host1.rent();
System.out.println("================");
// 租房 代理模式
Host host = new Host();
// 左侧是接口 右侧是实现类
Rent rent = new Proxy(host);
rent.rent();
rent.sell();
}
}
即静态代理是一一对应的,如果需要真实角色多了 ,代理类也会变多 . 开发效率降低。
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
动态代理
动态代理可以分为基于接口(JDK动态代理)和基于类(cglib),以及基于字节码的动态代理(JAVAassist–jboss)(极少使用)。
动态代理的角色和静态代理的一样 .动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的。
-
基于接口(JDK动态代理)
这是jdk自带的,java在持续优化这个部分,通过拦截器+反射的方式实现,只能代理继承接口的类。
-
基于类
cglib是第三方工具体统的,基于ASM实现,性能更好,通过实现子类的方法来调用。
JDK动态代理 和 cglib代理
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
aop(JDK的动态代理)-- 基于接口
我们会用到的是Proxy 代理
使用
也就是:
classloader:类加载器
interfaces:增强方法所在的类,类需要实现的接口(支持多个接口)
InvocationHandler:实现这个接口invocationHandler,创建代理对象,写增强的方法。
InvocationHandler调用处理程序api:
实现InvocationHandler都需要override如下invoke方法,其中有这三个参数:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 代理对象
method: 代理方法
args: 该方法的参数
那么我们使用动态代理,可以将上述的静态代理例子改为:
Rent . java 即抽象角色(不变)
//抽象角色:租房
public interface Rent {
void rent();
void sell();
}
Host . java 即真实角色(不变)
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房屋出租");
}
@Override
public void sell() {
System.out.println("房东卖房");
}
}
! 重点:使用 Proxy.newProxyInstance 创建 DynProxy. java 即代理角色
import java.lang.reflect.Proxy;
//动态代理的对象
public class DynProxy {
public Object getProxy(Object object){
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),
new Handler(object));
}
}
}
重点 : Handler 实现 InvocationHandler:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。
public class Handler implements InvocationHandler {
Object object;
public Handler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//添加一些我们自己的操作
System.out.println("前置通知");
//通过反射调用委托对象的方法
Object result = method.invoke(object,args);
//添加一些我们自己的操作
System.out.println("后置通知");
return result;
}
}
Client . java
//租客
public class Client {
public static void main(String[] args) {
//创建一个实例对象,这个对象是被代理的对象 -- 真实角色
Host host = new Host();
//获取一个动态代理对象,动态代理的对象执行的每个方法事实上走的都是invoke
DynProxy dynProxy = new DynProxy();
Rent rentProxy = (Rent)dynProxy.getProxy(host);
//代理执行方法
rentProxy.rent();
rentProxy.sell();
}
}
我们审阅代码会发现,使用动态代理的代码部分没有像静态代理那样显式地提到固定类型的对象或是接口调用(静态代理的代码有显式地使用到Host这个被代理对象),被代理对象是传入的,调用方法靠的是反射的机制。另一方面,如果真实对象有的多个函数,代理类也可以直接通过对invoke方法的统一处理代替一个一个修改对应的代码。
动态代理明显比静态代理更易于拓展。
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
注意: jdk动态代理传入的代理对象,必须要实现接口!
aop实现原理
aop的底层是动态代理。
- 在昨天的练习中,我们有的同学出现过类似这样的错误:
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named ‘userServiceImpl’ is expected to be of type ‘com.liyw.service.UserServiceImpl’ but was actually of type ‘com.sun.proxy.$Proxy12’
原因:
Spring AOP的原理是 JDK 动态代理和CGLIB字节码增强技术,前者需要被代理类实现相应接口,也只有接口中的方法可以被JDK动态代理技术所处理;后者实际上是生成一个子类,来覆盖被代理类,那么父类的final方法就不能代理,因为父类的final方法不能被子类所覆盖。一般而言Spring默认优先使用JDK动态代理技术,只有在被代理类没有实现接口时,才会选择使用CGLIB技术来实现AOP。
但是也提供了配置参数来强制选择使用 CGLIB 技术,如下:
<aop:config proxy-target-class=“true” />
proxy-target-class=“true” 表示强制使用 CGLIB 技术来实现AOP,因为CGLIB是生成子类也就是代理类来实现的,所以proxy-target-class,表示是否代理目标类。<aop:config /> 就会由spring来选择,spring优先使用JDK动态代理来实现AOP。
spring已经完成了对这部分动态代理完成了封装,我们用aop是无感的,但是我们需要对这个部分有一个基本的了解。
aop(JDK的动态代理)
我们会用到的仍然是Proxy 代理 ,即使用:
也就是:
4.2.1 dao 随便什么简单的方法都可以
public interface UserDao {
public int decrease(int a,int b);
}
public class UserDaoImpl implements UserDao{
@Override
public int decrease(int a, int b) {
return a-b;
}
}
4.2.2 通过 Proxy.newProxyInstance 创建新的加强对象
import java.lang.reflect.Proxy;
//动态代理的对象
public class DynProxy {
public Object getProxy(Object object){
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),
new Handler(object));
}
}
}
Handler 实现 InvocationHandler:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class Handler implements InvocationHandler {
//传入的委托对象 实际上需要执行方法的对象
Object object;
public Handler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//添加一些我们自己的操作
System.out.println("前置方法");
Object result = method.invoke(object,args);
System.out.println("方法执行 "+result);
//添加一些我们自己的操作
System.out.println("后置方法");
return result;
}
}
调用:
public static void main(String[] args) {
IUserDao userDao = new UserDaoImpl();
//直接调用dao对象
int result = userDao.decrease(3,1);
System.out.println("直接调用== "+result);
//创建动态代理
DynProxy dynProxy = new DynProxy();
//动态代理传入实际需要被代理对象
IUserDao userDaoProxy = (IUserDao)dynProxy.getProxy(userDao);
//动态代理对象调用方法
result = userDaoProxy.decrease(3,1);
System.out.println("动态代理调用== "+result);
//代理对象和被代理对象
System.out.println(userDao.getClass().getName());
System.out.println(userDaoProxy.getClass().getName());
}
结果:
通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号
is.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//添加一些我们自己的操作
System.out.println("前置方法");
Object result = method.invoke(object,args);
System.out.println("方法执行 "+result);
//添加一些我们自己的操作
System.out.println("后置方法");
return result;
}
}
调用:
public static void main(String[] args) {
IUserDao userDao = new UserDaoImpl();
//直接调用dao对象
int result = userDao.decrease(3,1);
System.out.println("直接调用== "+result);
//创建动态代理
DynProxy dynProxy = new DynProxy();
//动态代理传入实际需要被代理对象
IUserDao userDaoProxy = (IUserDao)dynProxy.getProxy(userDao);
//动态代理对象调用方法
result = userDaoProxy.decrease(3,1);
System.out.println("动态代理调用== "+result);
//代理对象和被代理对象
System.out.println(userDao.getClass().getName());
System.out.println(userDaoProxy.getClass().getName());
}
结果:
通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号