本着重新学习(看到什么复习什么)的原则,这一篇讲的是JAVA的Spring框架。看了诸位大神的解释后详细的查了一些东西,记录下来,也感谢各位在网络上的分享!!!
感谢:https://me.youkuaiyun.com/nrsc272420199
https://blog.youkuaiyun.com/changudeng1992/article/details/80625134
https://www.2cto.com/kf/201708/667260.html
https://www.cnblogs.com/zhangxufeng/p/9160869.html
https://www.imooc.com/learn/869(视频课程很清晰的介绍了AOP)
之前学习了JAVA的反射,然后就有了疑问,反射机制固然有效且多变,但是我平时在什么地方能使用到这种技术呢?上篇中说到了,可以在框架级编程中常见反射,比如说控制反转等,果然。。。看到什么复习什么,那就先看看Spring框架。
1.什么是Spring?
Spring的出现是为了使得原有的JAVA EE开发更加容易,它优化了固有的很多不同类型接口在使用或实现时需要大量重复代码或者重复配置的问题。所以最终Spring的优势就在于以下几点:
1.Spring是一个开源框架,解决了企业应用开发的复杂性,但又不只可以应用于企业级开发中。
2.是一个基于POJO的轻量级的控制反转(IOC)和面向切面(AOP)的容器框架
3.能够减少重复代码的编写量
4.支持简单配置完成对事务的管理
5.方便集成其他框架,从而达到简单组合到更好的功能实现的链接
Spring是一个轻量级的框架是因为其本身占用的内存或者在运行时所需开销很小,并且通过控制反转(IOC:将控制权外交,以便在使用时直接得到期待获取的对象)的技术达到松耦合的目的 ,提供了面向切面编程的支持,允许通过分离应用的业务逻辑(如商品的入库与出库)与系统级服务(如日志系统)进行内聚性的开发,能够管理由Spring自己创建的对象的配置和生命周期,能够将其他组件配置进行组合,从而得到功能更加强大复杂的应用。
1.Spring Core:核心容器。主要通过工厂模式实现的BeanFactory来负责对象的创建,管理,配置等具体操作,使Spring成为一个容器。BeanFactory使用控制反转(IOC)模式将应用的配置和依赖性规范与实际的应用代码分离。
2.Spring Context:应用上下文模块。模块使Spring成为一个框架,模块内提供了很多企业级应用,包括调度,校验等等,Spring Context的本体是一个配置文件。
3.Spring AOP:AOP模块。模块使Spring实现面向切面编程的基础。通过将业务逻辑和系统服务逻辑解耦,提高效率。
4.Spring DAO:DAO模块。模块抽取了常用的JDBC和DAO中的重复代码(例如打开连接,配置连接,关闭连接等),减少代码量的同时也降低了其中可能遇到的异常数量。
5.Spring ORM:对象/关系映射集成模块。模块建立在Spring DAO之上,Spring的事务管理支持这些ORM框架中的每一个也包括JDBC,并且Spring实际上为若干ORM框架提供了集成方案,如Hibernate,iBatis SQL等等。
6.Spring WEB:Web模块。模块建立在Spring Context之上,为基于Web的应用程序提供支持。同样Spring也为其他Web框架提供了集成方案,如Struts等。
7.Spring WEB MVC:MVC框架。框架是一个全功能的构建Web应用程序的MVC实现。
Spring包括如上模块,所有的模块都是在核心容器之上构建的。
2.什么是IOC?
IOC即控制反转也叫做依赖注入(DI:依赖注入是IOC的一种实现方式),是面向对象编程中的一种设计模式,用来解耦合。相当于是获得依赖对象的过程被反转了,应用程序本身不负责依赖对象的创建和维护,而是由外部容器负责创建和维护(在使用对象时不是自己通过new操作符等方式自己进行对象的创建,而是由容器帮助查找和注入对象,获取对象的某种功能。例如:我们不会想要什么物品就直接自己创建它,而是通过中介或者互联网等方法找到该物品,我们只负责使用)。IOC容器在初始化时创建一系列的对象,同时将各种对象及对象之间的依赖关系通过注入的方式组合起来。而所谓依赖注入就是IOC容器在运行期间,动态的将某种依赖关系注入到对象中。也就是说,不再需要再了解各种对象及其之间的关系,而是将所有的对象的控制权全部交给IOC容器,只关注具体实现功能和调用方式等。依赖注入和控制反转是从不同的角度描述这件事情。各种不同的对象之间的依赖关系都存在某个地方中,我们不用关注其具体的形成原因或者最终的销毁过程,而这个地方就是IOC容器。
IOC容器的使用能够让各种功能不同的模块拆分开来,而不用考虑各种对象之间的关联关系。每一个功能的使用时都互不相干,互不影响。降低了耦合的同时也提高了工作效率,并且功能模块变得可复用,即我们在不同的场景中都可以使用相同的功能和代码实现(如我们不管去什么餐厅,实际上都要做的动作都是点餐,等待,用餐,付费,走人。同时,付费的模块实际上在所有需要付款的场景都需要这个功能实现。)
3.什么是注入?
指在启动Spring容器加载bean配置的时候,完成对变量的赋值行为,即在加载时扫描有关于bean的相关配置,而后在创建的过程中为其进行实例化和初始化。Spring有三种注入方式:
1.设值注入(set):通过在applicationContext.xml文件中书写对应的bean和bean的指向,并在要注入的类中写对应的set方法,完成注入。优点就是能够被继承,允许设置默认值,但无法再构造完成后立刻使用。
2.构造注入(constructor):其优点就是在对象构造完成后立刻可以使用。而缺点就是其可能会因为过多的参数列表,并且构造函数无法被继承且不能设置默认值而导致维护上的问题。
3.注解注入(Annotation):其优点就是不用过分关注注入过程,可以使用例如@Autowired注解进行自动注入,相似的注解还有@Resource,@Service,@Controller,@Repository等。
4.什么是Bean?
把所有配置到IOC容器中的实体或者对象都称为Bean。Id(Bean在IOC容器中的唯一标识),Class(具体要实例化的类),Scope(Bean的作用域),Autowiring mode(自动装配模式)
Bean的作用域:
singleton:一个Bean容器中只存在一个,是单例(默认)
prototype:每次请求或使用时都会创建新的实例
request:每次http请求都会创建一个作用域仅在当前request内的实例
session:每次http请求都会创建一个作用域仅在当前session内的实例
global session:针对多应用集成系统,必然不会每一个系统都进行一遍自己的登录,很多应用之间的共同session的这种单点登录的场景时有效
Bean的生命周期(在这里强烈推荐这一位博主的解析,很清晰,而且是从源码和实例两方面出发):
AbstractAutowireCapableBeanFactory类:doCreateBean,initializeBean,invokeAwareMethods
1.实例化Bean对象:通过构造方法(即默认使用的无参构造器),静态工厂方法(在IOC容易中实例化工厂后,提供一个静态方法负责Bean的实例化)或者动态工厂方法(直接调用动态方法,无需实例化工厂)将Bean进行实例化。
2.注入Bean对象属性:通过使用依赖注入,将Bean定义信息和属性配置给Bean对象。
3.Aware接口检查:Aware接口(如BeanNameAware,ApplicationContextAware)是在Bean被初始化后,获取相应资源对资源进行操作。这一步主要对调用的接口和需要返回的资源进行判定。总结下来就是实现了什么接口,就返回什么样的信息。
(1).BeanNameAware接口:提供关于BeanName定义的信息。如果实现了BeanNameAware接口,则调用Bean的setBeanName方法,传递在配置文件中定义的Bean的特有ID
(2).BeanFactoryAware接口:提供关于BeanFactory工厂的定义信息。如果实现了BeanFactoryAware接口,则调用Bean的setBeanFactory方法,传递Spring工厂本身
(3).ApplicationContextAware接口:提供关于IOC容器上下文的信息。如果实现了ApplicationContextAware接口,则调用setApplicationContext方法,传递Spring上下文信息
4.初始化Bean:
(1).前置处理:当Bean与BeanPostProcessor接口关联时,通过将Bean的实例传递给Bean的前置处理器(postProcessBeforeInitialization方法)对Bean进行自定义处理或全局处理。
(2).调用Bean的初始化方法:即通过调用InitializingBean方法中的invokeInitMethods方法(是否实现InitialzingBean接口和initMethod方法,如果实现了或者存在指定,则调用该方法)进行初始化。
(3).后置处理:同上,通过将Bean的实例传递给Bean的后置处理器(postProcessAfterInitialization)方法对Bean进行自定义处理(逐一处理)
5.使用Bean
6.销毁Bean:DisposableBean类中的对应destroyBean方法或者是配置文件中的destroy-method属性。
在大致了解了Spring的Bean的生命周期后,来看一下注解。现在很多的配置都不用在xml文件中进行编写了,毕竟xml的方式需要记住或者需要进行标准化重复性配置的内容太多。注解的出现是通过反射的方式获取注解内容。并且在注解进入编译时注解会被保留,并且可以在运行时获取注解内容。这里重点看一下Spring中常用的注解。
声明Bean的注解:
(1). @Component:用于将想要注入的任意类型的POJO实例化到Spring的容器中,可以用于以下几种任意一种场景,但是为了精确类型和功能,应尽可能在对应类型所在层使用对应注解。
(2). @Service:在业务服务层使用,用于处理业务逻辑,被标注的类都应该是服务。
(3). @Repository:在数据持久层使用,也就是常说的操作数据库的DAO层。
(4). @Controller:在SpringMVC的类中使用,其标注的类应该是Web操作控制类,即通过类中定义的内容对Web端的页面访问或者处理后台逻辑。
还有要注意区分的是@Bean和@Configuration和@Component。@Component和@Configuration都是类级别的注解,但是前者的范围最大,任意类型均可以注解;后者一般注解在配置类上。而在配置类中一般会有使用@Value设置默认值的成员变量或者是使用@Bean注解的方法,也就是说@Bean是方法级别的注解。@Component在使用上的作用类似XML配置,可以在容器中注入任何类型的实例。
注入bean的注解:
(1). @Autowired:可以注解在类内成员变量,set方法及构造函数上。@Autowired拥有四种可供选择的模式,byType(通过查找符合要求的参数类型的的set方法进行注入),byName(通过查找匹配的id名称进行注入),Constructor(通过构造器自动注入)和autodetect(通过Bean的自省机制来决定使用constructor还是byType方式)。@Autowired默认使用byType进行注入,如果没有匹配到或者结果众多便会转为使用byName进行查找。可通过设置required属性为false指定当找不到对应的Bean时不抛出异常。(mark一下自省机制。。。)
(2). @Inject:同样可以注解在类内成员变量,方法及构造函数上。但是需要导入外部包(javax.inject)。默认使用type类型进行查找。
(3). @Resource:同样可以注解在类内成员变量,方法上,但是构造函数不行。默认是通过type类型进行查找,但是也可以指定name属性来指定,会在没有对应Bean时抛出异常,且没有required属性可供操作。
配置类的注解:
除了上述的@Configuration和@Bean外还有@ComponentScan等。@Bean允许如上所述使用@Scope注解设置作用域。
(1). @ComponentScan:作用是定义扫描路径,如在配置类上添加@ComponentScan注解,则会默认扫描该类所在的包下所有的配置类。也可以通过value自定义要扫描的包。
5.什么是AOP?
再来看一下什么是AOP。AOP指的就是面向切面编程,如上述所说的服务层,控制层,DAO层等,将众多类型功能的点汇聚在一个层面上进行编程,降低了业务逻辑之间的耦合度,并且提高了代码的可重用性。也就是说,每一个功能点都如同被封装起来,可以使用在不同的应用场景的业务逻辑中。而其底层就是通过反射机制达到的动态代理来实现的。而在AOP中有以下术语,可以先通过(https://blog.youkuaiyun.com/changudeng1992/article/details/80625134)通俗易懂的了解一下,而后再去看那些官方解释会清晰很多。我这里按照解释和解释词语中调用的其他解释词语排序,看起来更加清晰一些。
1.连接点(JoinPoint):连接点是程序执行的某个特殊位置,另外Spring仅支持在方法级别上的连接点。也就是说在方法调用前,调用后或者方法抛出异常的时候都能织入增强。
2.目标对象(Target):目标对象就是我们要织入增强的目标类。也就是业务逻辑类,不需要关注其他的内容,如什么时候该写日志,什么时候该进行安全检查等。
3.织入(Weaving):织入是将增强添加(应用)到目标类的某个具体连接点上的过程。AOP有三种织入方式:
(1). 编译器织入:这要求使用特殊的Java编译器。(AspectJ)
(2). 类装载期织入:这要求使用特殊的类装载器。(AspectJ)
(3). 动态代理织入: 在运行期为目标类添加增强生成原类的子类的方式。(Spring)
4.增强(Advice):同通知。增强就是织入到目标类的的连接点上的一段代码。小结来说的话就是在程序执行的某个特殊位置(方法前后或抛出时),我们需要有特殊的功能需要实现,如需要记录日志,检查权限安全等操作,我们就需要将这些功能点织入到对应位置上。
5.切点(Pointcut):一个类可以有多个方法,也就意味着可以有多个连接点。而这些连接点就好比是数据库中的数据,切点就相当于是查询条件,从而达成在某个连接点上使用某个对应增强的作用。切点和连接点不是一对一关系。切点使用AspectJ的切点表达式来定义切点和筛选执行方法。(mark一下,https://www.2cto.com/kf/201708/667260.html,https://www.cnblogs.com/zhangxufeng/p/9160869.html)
6.引入(Introduction):引入相当于是可以对原有的类进行增强,相当于是一种特殊的增强。引入可以为类添加成员变量和方法。从而实现原本业务逻辑中没有实现的接口,达到增强的目的。
7.切面(Aspect):切面是由切点和增强(引入)组成的,由在某个对应切点做某个对应增强(引入)来定义一个完整的切面。
8.代理(Proxy):一个类被AOP织入增强后,便会形成一个结果类。结果类是融合了原类和增强逻辑
的代理类。我们可以使用调用原类相同的方式调用代理类。
学习了Spring的两大思想,IOC和AOP,我们可以知道IOC使用的是反射机制,将IOC容器中要生产的对象在配置文件中给出定义,然后通过反射去生成对应的对象。那么AOP的动态代理机制又是什么。。。
代理可以理解为经纪人,JAVA中一般分为静态代理和动态代理,动态代理又分为JDK代理和CGLIB代理。动态代理类在程序运行时通过反射机制动态生成。在代理过程中包含三个对象,一个是客户方,一个是目标对象,还有一个就是代理对象,客户方通过代理对象间接与目标对象进行交互。客户方通过接口来引用目标对象或者代理对象,也就是说代理对象要包含目标对象的所有信息。但是如同经纪人能帮助艺人做所有除艺人特有技能以外的事情一样,依然是目标对象中的方法在执行,代理对象只是做一些额外的要织入的逻辑。如下模拟一个场景,歌坛的歌手们都会唱歌,歌手A有个经纪人,经纪人需要在歌手唱歌前进行各种沟通和协调,在唱完歌拿钱走人,歌手不用管细节,只需要唱歌就行了。现在有一个客户需要一个歌手唱歌,那么他就得跟经纪人联系,但是唱歌的动作只能是歌手本人进行,前后事都由经纪人负责。这就是静态代理:
package com.day_5.excercise_2;
public interface Music {
void singing();
void talking();
}
package com.day_5.excercise_2;
public class Singer implements Music{
@Override
public void singing() {
System.out.println("singing......");
}
@Override
public void talking() {
System.out.println("talking......");
}
}
package com.day_5.excercise_2;
public class Agent implements Music {
private Singer singer;
public Agent(Singer singer) {
this.singer = singer;
}
@Override
public void singing() {
System.out.println("contecting......");
try {
singer.singing();
}catch (Exception e) {
System.out.println("exception:"+e.getMessage());
throw e;
}finally {
System.out.println("money......");
}
}
@Override
public void talking() {
System.out.println("contecting......");
try {
singer.talking();
}catch (Exception e) {
System.out.println("exception:"+e.getMessage());
throw e;
}finally {
System.out.println("money......");
}
}
}
package com.day_5.excercise_2;
public class Client {
public static void main(String[] args) {
Music music = new Agent(new Singer());
music.singing();
}
}
静态代理的缺点就是目标类中有多少方法,代理类就需要对所有的方法进行管理,但是前后的方法有可能都一样。继续用上边的例子,歌手要去的节目很多,假设他参加很多不同的活动,那对于每一个活动来说来说都需要谈合同,做动作(语言类或者歌唱类等等等等),要钱。所以就会有很多的重复动作,也就会产生很多重复代码。所以就需要了基于接口的JDK代理和基于继承的CGLIB代理。
JDK动态代理:存在几个必要条件,需要通过Proxy类(java.lang.reflect.Proxy)动态生成代理类,需要实现织入的逻辑需要实现InvocationHandler接口。 并且要注意是基于接口的。
package com.day_5.excercise_2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKProxyArea implements InvocationHandler{
private Singer singer;
public JDKProxyArea(Singer singer) {
this.singer = singer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object singerObject = null;
System.out.println("contecting......");
try {
singerObject = method.invoke(singer, args);
}catch (Exception e) {
System.out.println("exception:"+e.getMessage());
throw e;
}finally {
System.out.println("money......");
}
return singerObject;
}
}
package com.day_5.excercise_2;
import java.lang.reflect.Proxy;
public class ClientB {
public static void main(String[] args) {
//有人说是第二句或者第三句,我这个jdk1.8的版本是用第一句的,为了防止遗忘,把另外两句也写在这里
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", true);
// System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
Music music = (Music) Proxy.newProxyInstance(ClientB.class.getClassLoader(), new Class[] {Music.class}, new JDKProxyArea(new Singer()));
music.talking();
music.singing();
}
}
如上所示,实现一个统一化的流程,只要是活动,来了就得谈合同,完事就得要钱。在任何一个场景无论是唱歌也好还是谈话也好,都要走流程。但是可以看到,不需要像第一个例子中那样,有一个方法就代理一个方法,而是在代理中规定了一个流程。代理类实现了InvocationHandler接口,然后通过invoke方法将要实现的流程写入,并在其中将想要调用的方法通过反射的机制获取到,完成流程化的控制。而在main方法中只需要通过Proxy的newProxyInstance来构造动态代理对象就可以了。在该方法的注释中可以看到该方法的目的就是“返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。”,而其中最关键的就是生成代理类的方法getProxyClass0,在方法中会先从缓存中取得,如果没有会通过ProxyClassFactory生成一个代理类。在ProxyClassFactory中存在bytes类型的proxyClassFile存放生成的代理类的字节码文件,而生成方法的参数就是要生成的代理类 的名称,要实现的接口等。生成的文件可以通过(System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");)打开获取信息的保存开关。生成的文件路径会是你当前项目(我的项目名称是JavaReview)所在路径下的(sun.proxy)路径下会生成对应的$Proxy0.class文件。
总结下来就是(反着写了,这样记得路径):
(1). newProxyInstance构造代理类对象
(2).getProxyClass0方法生成代理类,检查是否在缓存中存在,否则生成一个代理类
(3).ProxyClassFactory代理类工厂类
(4).proxyClassFile接收依靠ProxyGenerator.generateProxyClass生成的代理类的字节码文件
/**
* Returns an instance of a proxy class for the specified interfaces
* that dispatches method invocations to the specified invocation
* handler.
......
*/
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException {
......
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
......
}
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
......
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
......
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
......
}
package com.sun.proxy;
import com.day_5.excercise_2.Music;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Music
{
private static Method m1;
private static Method m4;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject)
throws
{
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void talking()
throws
{
try
{
this.h.invoke(this, m4, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final void singing()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final String toString()
throws
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
public final int hashCode()
throws
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m4 = Class.forName("com.day_5.excercise_2.Music").getMethod("talking", new Class[0]);
m3 = Class.forName("com.day_5.excercise_2.Music").getMethod("singing", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
可以看到该代理类继承了Proxy类,并且实现了我定义的Music类,当然也实现了对应的方法。也可以看到对应方法中的逻辑就是我在InvocationHandler的实现类中书写的invoke方法中的逻辑。这也说明了JDK动态生成只能实现接口的原因是因为它动态生成的代理类默认要继承Proxy类,由于JAVA是单继承,所以只能通过实现接口的方式。
CGLIB动态代理:是通过集成的方式实现代理类,另外它是通过callback的方式织入代码的。(需要额外的包:net.sf.cglib等,下载链接:https://download.youkuaiyun.com/download/jarremdon/9624537)
package com.day_5.excercise_2;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGLIBMethodInterceptor implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
Object singerObject = null;
System.out.println("contecting......");
try {
singerObject = proxy.invokeSuper(obj, args);
}catch (Exception e) {
System.out.println("exception:"+e.getMessage());
throw e;
}finally {
System.out.println("money......");
}
return singerObject;
}
}
package com.day_5.excercise_2;
import net.sf.cglib.proxy.Enhancer;
public class ClientC {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Singer.class);
enhancer.setCallback(new CGLIBMethodInterceptor());
Music music = (Music) enhancer.create();
music.singing();
music.talking();
}
}
JDK动态代理和CGLIB动态代理的区别是:
JDK只能针对有接口的类的接口方法进行动态代理,不能对private方法进行代理
CGLIB是基于继承实现代理,所以无法对static,final类进行代理,也无法对private,static方法进行代理
SpringAOP动态代理总结:
如果目标对象实现了接口,则默认采用JDK动态代理
如果目标对象没有实现接口,则采用CGLIB动态代理
如果目标对象实现了接口,但是强制使用CGLIB代理,则使用CGLIB代理(@EnableAspectJAutoProxy(proxyTargetClass = true))
Spring的一部分内容就算是学习完了,看了很多教程,纠结了很多知识点,也看到了很好的视频课程,非常感谢大家在网络上的贡献,我也会在引用的同时尽可能的加上自己的理解,相信一遍又一遍的加上新的理解或比喻一定能让抽象的概念变得更形象,也更通俗易懂。这一块知识得常复习,之前mark的也不能忘。。。