目录
目录什么是AOPAOP应用场景Spring AOP代理机制Spring AOP的术语核心概念重要通知分类织入时期为什么使用AOPSpring AOP实现基本配置pom.xmlplugins 插件配置添加依赖web.xmlJava.classIBookBiz.javaBookBizImpl.javaresource包新建 spring.xml前置通知ProxyFactoryBean 常用属性后置通知环绕通知异常通知适配器完整代码spring.xmlDemo.java
什么是AOP
OOP三大特点:封装、继承、多态
AOP
AOP(Aspect Oriented Programming)是一种面向切面的编程思想。面向切面编程是将程序抽象成各个切面,即解剖对象的内部, 将那些影响了多个类的公共行为抽取到一个可重用模块里 ,减少系统的重复代码,降低模块间的耦合度,增强代码的可操作性和可维护性。
提取公共行为抽取到一个类中
中间是具体的核心代码(核心关注点),上边和下边为 重复代码(横切关注点)
publicvoidadd(){
Connectioncon=DBHelper.getConnection(); //获取连接对象:连接数据库
PrepareStatementps=con....
ResultSetrs=ps....
核心代码
finally{
DBHelper.close(con,ps,rs); //关闭连接
}
}
-> 交付由 AOP管理
publicvoidadd(){
<------面向切面:从横切面切入代码
核心代码
<------面向切面
}
AOP把软件系统分为两个部分:核心关注点(每一个方法中的核心功能)和横切关注点(多个方法里的公共行为)。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理、增强处理。
AOP带来的好处:让我们可以 “专心做事”
应用场景
场景一:记录日志
场景二:监控方法运行时间 (监控性能)
方法执行前和方法执行后,不知道核心代码需要多长时间执行完成。由 结束时间 - 开始时间 得到 核心代码时长,然后就可以根据时长进行方法调优
场景三:权限控制
场景四:缓存优化 (
第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
场景五:事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )
比如:开启连接,关闭连接
publicvoidadd(){
Connectioncon=DBHelper.getConnection();
Transactiontran=conn.beginTransaction();
<------面向切面:从横切面切入代码
核心代码
<------面向切面
tran.commit()/tran.rollback();
}
Spring AOP代理机制
Spring 在运行期会为目标对象生成一个动态代理对象,并在代理对象中实现对目标对象的增强。Spring AOP 的底层是通过以下 2 种动态代理机制,为目标对象(Target Bean)执行横向织入的。
JDK动态代理:Spring AOP 默认的动态代理方式,若目标对象实现了若干接口,Spring 使用 JDK 的 java.lang.reflect.Proxy 类进行代理。
CGLIB动态代理:若目标对象没有实现任何接口,Spring 则使用 CGLIB 库生成目标对象的子类,以实现对目标对象的代理。
注意:由于被标记为 final 的方法是无法进行覆盖的,因此这类方法不管是通过 JDK 动态代理机制还是 CGLIB 动态代理机制都是无法完成代理的。
Spring AOP的术语
核心概念重要
Joinpoint(连接点):
指那些被拦截到的点(程序执行过程中明确的点,如 方法的调用,或者异常的抛出),在 Spring 中,可以被动态代理拦截目标类的方法。
Pointcut(切入点):
多个连接点的集合,定义了通知应该应用到哪些连接点。
Advice(通知):
指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。
Target(目标):
指代理的目标对象。
Weaving(织入):
指把增强代码应用到目标上,生成代理对象的过程。
Proxy(代理) :
指生成的代理对象(将通知应用到目标对象后创建的对象)。
Aspect(切面):
切入点和通知的结合。
术语 | 含义 |
连接点 | 程序执行过程中明确的点,方法的调用 |
切入点 | 多个连接点的集合,只有满足条件的才能将通知应用到目标上 (if 条件,筛选) |
通知 | 公共行为,横切关注点 |
目标 | 具体执行核心业务的代码 |
代理 | 将通知应用到目标上所的创建对象 (通知+目标 = 代理) |
切面 | 通知 + 切入点 【满足条件】 |
公共部分的代码写在哪里的? 通知里面,只有完整的代理对象才具备 AOP的特性
通知分类
AOP 联盟为通知(Advice)定义了一个 org.aopalliance.aop.Interface.Advice 接口。
Spring AOP 按照通知(Advice)织入到目标类方法的连接点位置,为 Advice 接口提供了 6 个子接口。
通知类型 | 接口 | 描述 |
前置通知 | org.springframework.aop.MethodBeforeAdvice | 在目标方法执行前实施增强。 |
后置通知 | org.springframework.aop.AfterAdvice | 在目标方法执行后实施增强。 |
后置返回通知 | org.springframework.aop.AfterReturningAdvice | 在目标方法执行完成,并返回一个返回值后实施增强。 |
环绕通知 | org.aopalliance.intercept.MethodInterceptor | 在目标方法执行前后实施增强。 |
异常通知 | org.springframework.aop.ThrowsAdvice | 在方法抛出异常后实施增强。 |
引入通知 | org.springframework.aop.IntroductionInterceptor | 在目标类中添加一些新的方法和属性。 |
织入时期
织⼊(Weaving):代理的⽣成时机(通知 + 目标)。织⼊是把切⾯应⽤到⽬标对象并创建新的代理对象的过程,切⾯在指定的连接点被织⼊到⽬标对象中。
在⽬标对象的⽣命周期⾥有多个点可以织⼊:
时期 | 说明 |
编译期 | 切⾯在⽬标类编译时被织⼊。这种⽅式需要特殊的编译器。AspectJ的织⼊编译器就是以这种⽅式织⼊切⾯的。 |
类加载期 | 切⾯在⽬标类加载到JVM时被织⼊。这种⽅式需要特殊的类加载器(ClassLoader),它可以在⽬标类被引⼊应⽤之前增强该⽬标类的字节码。AspectJ5的加载时织⼊(load-time weaving. LTW)就⽀持以这种⽅式织⼊切⾯。 |
运行期 | 切⾯在应⽤运⾏的某⼀时刻被织⼊。⼀般情况下,在织⼊切⾯时,AOP容器会为⽬标对象动态创建⼀个代理对象。SpringAOP就是以这种⽅式织⼊切⾯的。 |
为什么使用AOP
举个例子
病人做手术
术前准备工作,护士和麻醉师 -> 通知 ,公共行为 (一个手术室可能有多台手术,同一时间)
病人做手术 -> 目标
做手术 -> 切入点(明确的一个点:只有做手术才会触发 准备工作、打麻药...)
护士、麻醉师和医生一起做手术 -> 代理
如果没有护士、麻醉师,医生一个人完成手术,不可能吧
如果没有医生,只有护士、麻醉师,都不知道从哪里下刀
所以,通知 + 目标 = 代理,一个完成的 代理对象才具有 AOP的特性
做完手术,清理工具 -> 通知,公共行为(做完手术都要 清理)
麻醉师打麻醉,方便做手术 -> 切面(切入点+ 通知)
在指定方法被调用之前记录日志
在指定方法被调用之前记录日志。在传统方式下通过在每个方法之前先添加日志收集代码,再执行方法本身的核心业务代码
如果遇到记录日志方法业务变动的情况,后期的代码更新和维护将是一大难题。
假设有一百个方法,每个方法都要记录日志,有一个改动都要维护100次,
如果给 AOP管理,则只用修改一次
publicvoiddoSameBusiness (longlParam,StringsParam){
// 记录日志:在方法调用之前
log.info("调用 doSameBusiness方法,参数是:"+lParam);
//核心方法: 输入合法性验证
if (lParam<=0){
throwsnewIllegalArgumentException("xx应该大于0");
}
if (sParam==null||sParam.trim().equals("")){
throwsnewIllegalArgumentException("xx不能为空");
}
// 异常处理
try{ ...
}catch(...){
}
// 事务控制
tx.commit();
}
Spring AOP实现
基本配置
基于idea创建maven项目,配置pom.xml并导入依赖。
pom.xml
plugins 插件配置
<!--插件-->
<plugins>
<!--第一步就是配置maven-compiler-plugin插件,其他的插件统统删除-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${maven.compiler.source}</source>
<!--jdk 1.8-->
<target>${maven.compiler.target}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
添加依赖
<!--设置版本号-->
<properties>
<junit.version>4.12</junit.version>
<spring.version>5.0.8.RELEASE</spring.version>
</properties>
<!--动态加载依赖-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
web.xml
<web-appxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID"version="3.0">
<display-name>Archetype Created Web Application</display-name>
</web-app>
Java.class
导入书本业务类IBookBiz、BookBizImpl
IBookBiz.java
publicinterfaceIBookBiz {
// 购书
publicbooleanbuy(StringuserName, StringbookName, Doubleprice);
// 发表书评
publicvoidcomment(StringuserName, Stringcomments);
}
BookBizImpl.java
publicclassBookBizImplimplementsIBookBiz {
publicBookBizImpl() {
super();
}
publicbooleanbuy(StringuserName, StringbookName, Doubleprice) {
// 通过控制台的输出方式模拟购书
/*if (null == price || price <= 0) {
throw new PriceException("book price exception");
}*/
System.out.println(userName+" buy "+bookName+", spend "+price);
returntrue;
}
publicvoidcomment(StringuserName, Stringcomments) {
// 通过控制台的输出方式模拟发表书评
System.out.println(userName+" say:"+comments);
}
}
resource包
新建 spring.xml
用于 AOP 管理
前置通知
前置通知(org.springframework.aop.MethodBeforeAdvice):在连接点之前执行的通知。
1)创建前置通知类
packagecom.zking.spring02_aop.advice;
importorg.springframework.aop.MethodBeforeAdvice;
importjava.lang.reflect.Method;
importjava.util.Arrays;
/**
* 前置通知
*/
publicclassBeforeAdviceimplementsMethodBeforeAdvice {
/**
* 前置通知
* @param method 目标方法
* @param params 目标所执行的参数
* @param target 目标对象
* @throws Throwable
*/
@Override
publicvoidbefore(Methodmethod, Object[] params, Objecttarget) throwsThrowable {
//1. 获取目标方法名
StringmethodName=method.getName();
//2. 获取 目标对象的 类的全路径名
StringclassPath=target.getClass().getName();
System.out.println("【前置通知】 "+classPath+"."+methodName+"执行参数:"+Arrays.toString(params));//数组类型需要 转成字符串输出
}
}
2)创建spring.xml配置ProxyFactoryBean创建代理对象
Spring 能够基于 org.springframework.aop.framework.ProxyFactoryBean 类,根据目标对象的类型(是否实现了接口)自动选择使用 JDK 动态代理或 CGLIB 动态代理机制,为目标对象(Target Bean)生成对应的代理对象(Proxy Bean)
在
<?xmlversion="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans " target="_blank">http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--target 目标
目标:被通知(被代理)对象 注:执行具体业务逻辑的方法对象-->
<beanid="bookTarget"class="com.zking.spring02_aop.biz.BookBizImpl"></bean>
<!--Advice 通知
通知:在特定连接点上执行的动作 注:完成AOP面向切面编程-->
<!--前置通知-->
<beanid="before"class="com.zking.spring02_aop.advice.BeforeAdvice"></bean>
<!--代理(Proxy) = 通知+目标-->
<beanid="proxy"class="org.springframework.aop.framework.ProxyFactoryBean">
<!--需要注入原料:通知和 目标-->
<propertyname="target">
<!--引入 目标 target:被通知的目标对象-->
<refbean="bookTarget"/>
</property>
<propertyname="interceptorNames">
<!--引入 通知,可能有多个通知-->
<list>
<!--前置通知的 id-->
<value>before</value>
</list>
</property>
<!--代理对象需要实现的接口 proxyInterfaces:代理接口对象(List)-->
<propertyname="interfaces">
<list>
<!--接口的路径-->
<value>com.zking.spring02_aop.biz.IBookBiz</value>
</list>
</property>
</bean>
</beans>
ProxyFactoryBean 常用属性
属性 | 描述 |
target | 需要代理的目标对象(Bean) |
proxyInterfaces | 代理需要实现的接口,如果需要实现多个接口,可以通过 <list> 元素进行赋值。 |
proxyTargetClass | 针对类的代理,该属性默认取值为 false(可省略), 表示使用 JDK 动态代理;取值为 true,表示使用 CGlib 动态代理 |
interceptorNames | 拦截器的名字,该属性的取值既可以是拦截器、也可以是 Advice(通知)类型的 Bean,还可以是切面(Advisor)的 Bean。 |
singleton | 返回的代理对象是否为单例模式,默认值为 true。 |
optimize | 是否对创建的代理进行优化(只适用于CGLIB)。 |
3)测试代码
//示例2:前置通知
// 报错: Bean named 'proxy' is expected to be of type 'com.zking.spring02_aop.biz.BookBizImpl'
// but was actually of type 'com.sun.proxy.$Proxy4' -> 强转报错:不同类型不能转换
/*BookBizImpl proxy = ac.getBean("proxy",BookBizImpl.class);
proxy.buy("饭饭","撒野",145d);
proxy.comment("饭饭","丞哥无处不在");*/
/*Object proxy1 = ac.getBean("proxy");
System.out.println(proxy1.getClass()); //输出 class com.sun.proxy.$Proxy4*/
//请使用 接口接收 代理对象!!! 代理对象实现的是 接口
IBookBizproxy=ac.getBean("proxy", IBookBiz.class);
proxy.buy("饭饭","撒野",145d);
proxy.comment("饭饭","丞哥无处不在");
使用错误方式运行代码时,出现:
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy4 cannot be cast to com.zking.spring02.dao.BookBizImpl
提示:无法将com.sun.proxy.$Proxy4 无法转换成com.zking.spring02.dao.BookBizImpl
请使用接口接收返回的代理对象!!!请使用接口接收返回的代理对象!!!请使用接口接收返回的代理对象!!!
后置通知
后置通知(org.springframework.aop.AfterReturningAdvice):在连接点正常完成后执行的通知。
packagecom.zking.spring02_aop.advice;
importorg.springframework.aop.AfterReturningAdvice;
importjava.lang.reflect.Method;
importjava.util.Arrays;
/**
* 后置通知
*/
publicclassAfterAdviceimplementsAfterReturningAdvice {
/**
*
* @param returnValue 目标方法返回值
* @param method 目标方法
* @param params 目标方法执行参数
* @param target 目标对象
* @throws Throwable
*/
@Override
publicvoidafterReturning(ObjectreturnValue, Methodmethod, Object[] params, Objecttarget) throwsThrowable {
System.out.println("【后置通知】 "+target.getClass().getName()+"."+method.getName()+"执行参数:"+Arrays.toString(params)+
"返回值:"+returnValue);
}
}
环绕通知
环绕通知(org.aopalliance.intercept.MethodInterceptor):包围一个连接点的通知,最大特点是可以修改返回值,由于它在方法前后都加入了自己的逻辑代码,因此功能异常强大。它通过MethodInvocation.proceed()来调用目标方法(甚至可以不调用,这样目标方法就不会执行)。
packagecom.zking.spring02_aop.advice;
importorg.aopalliance.intercept.MethodInterceptor;
importorg.aopalliance.intercept.MethodInvocation;
importjava.lang.reflect.Method;
importjava.util.Arrays;
/**
* 环绕通知
*/
publicclassAroundAdviceimplementsMethodInterceptor {
/**
*
* @param methodInvocation
* @return
* @throws Throwable
*/
@Override
publicObjectinvoke(MethodInvocationmethodInvocation) throwsThrowable {
//1. 获取 目标对象
Objecttarget=methodInvocation.getThis();
//2. 获取目标方法
Methodmethod=methodInvocation.getMethod();
//3. 获取目标方法的执行参数
Object[] params=methodInvocation.getArguments();
//执行目标方法,并返回结果(环绕通知能控制目标方法的执行)
ObjectreturnValue=methodInvocation.proceed();
System.out.println("【环绕通知】 "+target.getClass().getName()+"."+method.getName()+" 执行参数:"
+Arrays.toString(params)+"返回值:"+returnValue);
returnreturnValue;
}
}
异常通知
1)创建自定义异常类
publicclassPriceExceptionextendsRuntimeException {
publicPriceException() {
super();
}
publicPriceException(Stringmessage, Throwablecause, booleanenableSuppression, booleanwritableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
publicPriceException(Stringmessage, Throwablecause) {
super(message, cause);
}
publicPriceException(Stringmessage) {
super(message);
}
publicPriceException(Throwablecause) {
super(cause);
}
}
2)设置异常抛出点
BookBizImpl 的买书方法
publicbooleanbuy(StringuserName, StringbookName, Doubleprice) {
// 通过控制台的输出方式模拟购书
if (null==price||price<=0) {
thrownewPriceException("book price exception");
}
System.out.println(userName+" buy "+bookName+", spend "+price);
returntrue;
}
3)定义异常通知
publicclassMyThrowsAdviceimplementsThrowsAdvice {
publicvoidafterThrowing(PriceExceptione) {
System.out.println("[异常通知] 价格异常,撤销订单!");
}
}
4)配置异常通知
<!-- 异常通知 -->
<beanid="myThrowsAdvice"class="com.zking.spring02.action.MyThrowsAdvice">
</bean>
<!--
代理:目标+通知
将目标和通知对象注入到代理工厂对象中
target:被通知的目标对象
proxyInterfaces:代理接口对象(List)
interceptorNames:需要应用到目标对象上的通知Bean的名字(List)
-->
<beanid="bookBiz"class="org.springframework.aop.framework.ProxyFactoryBean">
<propertyname="target">
<refbean="bookBizTarget"/>
</property>
<propertyname="proxyInterfaces">
<list>
<value>com.zking.spring02.dao.IBookBiz</value>
</list>
</property>
<propertyname="interceptorNames">
<list>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
适配器
<!-- 适配器=通知+切入点 通过正则表达式的方式对调用方法进行切入 -->
<beanid="regexpMethodPointcutAdvisor"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<propertyname="advice"ref="myAfterReturningAdvice">
</property>
<!-- 定义规则 -->
<propertyname="patterns">
<value>.*buy</value>
</property>
</bean>
<!--
代理:目标+通知
将目标和通知对象注入到代理工厂对象中
target:被通知的目标对象
proxyInterfaces:代理接口对象(List)
interceptorNames:需要应用到目标对象上的通知Bean的名字(List)
-->
<beanid="bookBiz"class="org.springframework.aop.framework.ProxyFactoryBean">
<propertyname="target">
<refbean="bookBizTarget"/>
</property>
<propertyname="proxyInterfaces">
<list>
<value>com.zking.spring02.dao.IBookBiz</value>
</list>
</property>
<propertyname="interceptorNames">
<list>
<value>myMethodBeforeAdvice</value>
<!-- 将后置通知更换成适配器 -->
<value>regexpMethodPointcutAdvisor</value>
<value>myMethodInterceptor</value>
<value>myThrowsAdvice</value>
</list>
</property>
</bean>
完整代码
spring.xml
<?xmlversion="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans " target="_blank">http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--target 目标-->
<beanid="bookTarget"class="com.zking.spring02_aop.biz.BookBizImpl"></bean>
<!--Advice 通知-->
<!--前置-->
<beanid="before"class="com.zking.spring02_aop.advice.BeforeAdvice"></bean>
<!--后置-->
<beanid="after"class="com.zking.spring02_aop.advice.AfterAdvice"></bean>
<!--环绕-->
<beanid="around"class="com.zking.spring02_aop.advice.AroundAdvice"></bean>
<!--异常-->
<beanid="exception"class="com.zking.spring02_aop.advice.ExceptionAdvice"></bean>
<!-- 适配器: 切面= 通知+目标-->
<beanid="aspect"class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<propertyname="advice">
<refbean="after"/>
</property>
<propertyname="pattern">
<value>.*buy</value>
</property>
<!-- <property name="patterns">
<value></value>
<value></value>
</property>-->
</bean>
<!--代理(Proxy) = 通知+目标-->
<beanid="proxy"class="org.springframework.aop.framework.ProxyFactoryBean">
<!--,需要注入原料:通知和 目标-->
<propertyname="target">
<!--引入 目标-->
<refbean="bookTarget"/>
</property>
<propertyname="interceptorNames">
<!--引入 通知,可能有多个通知-->
<list>
<!--前置通知的 id-->
<!--<value>before</value>-->
<!--后置-->
<!-- <value>after</value>-->
<!--环绕-->
<!--<value>around</value>-->
<!--异常-->
<value>exception</value>
<!--适配器-->
<value>aspect</value>
</list>
</property>
<!--代理对象需要实现的接口-->
<propertyname="interfaces">
<list>
<!--接口的路径-->
<value>com.zking.spring02_aop.biz.IBookBiz</value>
</list>
</property>
</bean>
</beans>
Demo.java
packagecom.zking.spring02_aop.util;
importcom.zking.spring02_aop.biz.BookBizImpl;
importcom.zking.spring02_aop.biz.IBookBiz;
importorg.springframework.context.ApplicationContext;
importorg.springframework.context.support.ClassPathXmlApplicationContext;
publicclassDemo {
// /j enter 方法注释
publicstaticvoidmain(String[] args){
//1. 什么是 AOP
//面向切面,“专心做事”
//2. 核心概念
//1) 连接点(JoinPoint): 程序执行过程中明确的点。例如:方法的调用、异常的抛出
//2) 通知(advice): 公共行为,指拦截到 Joinpoint 之后要做的事情
//3) 目标(Target): 核心代码,指代理的目标对象
//4) 代理(Proxy): 将通知应用到目标上所创建的对象。 通知+目标=代理
//注:公共部分的代码是写在通知中的:只有完整的代理对象才具备 AOP的特性
//5) 切入点(PointCut) :连接点的集合
//6) 切面(Aspect):通知+切入点(限制条件)。 只有满足条件的目标对象,才将通知应用到目标上
//3. 通知分类:
ApplicationContextac=newClassPathXmlApplicationContext("spring.xml");//初始化 IOC容器
//示例:买书、书评
/* BookBizImpl bookBiz = ac.getBean("bookTarget",BookBizImpl.class);
bookBiz.buy("饭饭","撒野",145d);
bookBiz.comment("饭饭","丞哥无处不在");*/
//示例2:前置通知 //示例3:后置通知 //示例4:环绕通知 //示例5:异常通知
// 报错: Bean named 'proxy' is expected to be of type 'com.zking.spring02_aop.biz.BookBizImpl'
// but was actually of type 'com.sun.proxy.$Proxy4' -> 强转报错:不同类型不能转换
/*BookBizImpl proxy = ac.getBean("proxy",BookBizImpl.class);
proxy.buy("饭饭","撒野",145d);
proxy.comment("饭饭","丞哥无处不在");*/
/*Object proxy1 = ac.getBean("proxy");
System.out.println(proxy1.getClass()); //输出 class com.sun.proxy.$Proxy4*/
//请使用 接口接收 代理对象!!! 代理对象实现的是 接口
IBookBizproxy=ac.getBean("proxy", IBookBiz.class);
proxy.buy("饭饭","撒野",145d);
proxy.comment("饭饭","丞哥无处不在");
/*
方法的调用 -> 连接点
将通知应用到我的目标上 -> 代理对象
公共代码 -> 通知
*/
//示例6:适配器
//只有符合条件,才会输出
//比如:只有购买书籍,才可返利 2元,书评不可以获得
}
}