代码仓库地址:
跟着黑马满一航老师的spring高级49讲做的学习笔记,本笔记跟视频内容的项目名称和代码略有不同,我将49讲的代码每一讲的代码都拆成了独立的springboot项目,并且项目名称尽量做到了见名知意,都是基于我自己的考量,代码都已经过运行验证过的,仅供参考。
视频教程地址:
注:
1. 每一讲对应一个二级标题,每一个三级标题是使用子项目名称命名的,和我代码仓库的项目是一一对应的;
2. 代码里面用到了lombok插件来简化了Bean中的get()、set()方法,以及日志的记录的时候用了lombok的@Slf4j注解。
笔记中如有不正确的地方,欢迎在评论区指正,非常感谢!!!
每个子项目对应的视频链接以及一些重要内容的笔记
p26 025-第六讲-Aware与InitializingBean接口
Aware
接口用于注入一些与容器相关信息,例如:
a. BeanNameAware
注入 Bean
的名字
b. BeanFactoryAware
注入 BeanFactory
容器
c. ApplicationContextAware
注入 ApplicationContext
容器
d. EmbeddedValueResolverAware
注入 解析器,解析${}
定义一个MyBean
类,实现BeanNameAware
、ApplicationContextAware
和InitializingBean
接口并实现其方法,再定义两个方法,其中一个加@Autowired注解,注入ApplicationContext容器,另一个加@PostConstruct注解,具体代码如下:
public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {@Overridepublic void setBeanName(String name) {log.debug("当前bean:" + this + ",实现 BeanNameAware 调用的方法,名字叫:" + name);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {log.debug("当前bean:" + this + ",实现 ApplicationContextAware 调用的方法,容器叫:" + applicationContext);}@Overridepublic void afterPropertiesSet() throws Exception {log.debug("当前bean:" + this + ",实现 InitializingBean 调用的方法,初始化");}@Autowiredpublic void aaa(ApplicationContext applicationContext) {log.debug("当前bean:" + this +",使用 @Autowired 容器是:" + applicationContext);}@PostConstructpublic void init() {log.debug("当前bean:" + this + ",使用 @PostConstruct 初始化");}
}
测试代码:
public class TestAwareAndInitializingBean {@Testpublic void testAware1() throws Exception {GenericApplicationContext context = new GenericApplicationContext();isterBean("myBean", MyBean.class);fresh();context.close();}
}
运行结果:
加了@Autowired
和@PostConstruct
注解的方法并没有被执行,而Aware
和InitializingBean
接口方法都被执行了。
修改测试代码,把解析@Autowired
和@PostConstruct
注解的Bean
后处理加进来,然后再运行一下
public class TestAwareAndInitializingBean {@Testpublic void testAware1() throws Exception {GenericApplicationContext context = new GenericApplicationContext();isterBean("myBean", MyBean.class);// 解析 @Autowired 注解的Bean后处理器isterBean(AutowiredAnnotationBeanPostProcessor.class);// 解析 @PostConstruct 注解的Bean后处理器isterBean(CommonAnnotationBeanPostProcessor.class);fresh();context.close();}
}
运行结果:
可以看到这下都执行了
有人可能会问:b
、c
、d
的功能用 @Autowired
注解就能实现啊,为啥还要用 Aware
接口呢?
InititalizingBean
接口可以用 @PostConstruct
注解实现,为啥还要用InititalizingBean
呢?
简单地说:
@Autowired
和@PostConstruct
注解的解析需要用到 Bean
后处理器,属于扩展功能,而 Aware
接口属于内置功能,不加任何扩展,Spring
就能识别;
某些情况下,扩展功能会失效,而内置功能不会失效
p27 026-第六讲-@Autowired失效分析
例1:比如没有把解析@Autowired
和@PostStruct
注解的Bean
的后处理器加到Bean
工厂中,你会发现用 Aware
注入 ApplicationContext
成功, 而 @Autowired
注入 ApplicationContext
失败
例2:定义两个Java Config
类(类上加@Configuration
注解),名字分别叫MyConfig1
和MyConfig2
,都实现注入ApplicationContext
容器和初始化功能,MyConfig1
用@Autowired
和@PostConstruct
注解实现,MyConfig2
用实现Aware
和InitializingBean
接口的方式实现,另外,两个Config
类中都通过@Bean
注解的方式注入一个BeanFactoryPostProcessor
,代码如下:
MyConfig1
:
@Slf4j
public class MyConfig1 {@Autowiredpublic void setApplicationContext(ApplicationContext applicationContext) {log.debug("注入 ApplicationContext");}@PostConstructpublic void init() {log.debug("初始化");}@Beanpublic BeanFactoryPostProcessor processor1() {return beanFactory -> {log.debug("执行 processor1");};}
}
测试代码:
@Slf4j
public class TestAwareAndInitializingBean {@Testpublic void testAware_MyConfig1() {GenericApplicationContext context = new GenericApplicationContext();// MyConfig1没有加上isterBean("myConfig1", MyConfig1.class);// 解析 @Autowired 注解的Bean后处理器isterBean(AutowiredAnnotationBeanPostProcessor.class);// 解析 @PostConstruct 注解的Bean后处理器isterBean(CommonAnnotationBeanPostProcessor.class);// 解析@ComponentScan、@Bean、@Import、@ImportResource注解的后处理器// 这个后处理器不加出不来效果isterBean(ConfigurationClassPostProcessor.class);// 1. 添加beanfactory后处理器;2. 添加bean后处理器;3. 初始化单例。fresh();context.close();
}
运行结果:
MyConfig2
:
@Slf4j
public class MyConfig2 implements ApplicationContextAware, InitializingBean {@Overridepublic void setApplicationContext(ApplicationContext applicationContext) {log.debug("注入 ApplicationContext");}@Overridepublic void afterPropertiesSet() throws Exception {log.debug("初始化");}@Beanpublic BeanFactoryPostProcessor processor1() {return beanFactory -> {log.debug("执行 processor1");};}
}
测试代码:
@Slf4j
public class TestAwareAndInitializingBean {@Testpublic void testAutowiredAndInitializingBean_MyConfig2() {GenericApplicationContext context = new GenericApplicationContext();isterBean("myConfig2", MyConfig2.class);// 1. 添加beanfactory后处理器;2. 添加bean后处理器;3. 初始化单例。fresh();context.close();}
}
运行结果:
Java配置类在添加了 bean
工厂后处理器后,你会发现用传统接口方式的注入和初始化依然成功,而 @Autowired
和 @PostConstruct
的注入和初始化失败。
那是什么原因导致的呢?
配置类 @Autowired
注解失效分析
Java 配置类不包含 BeanFactoryPostProcessor
的情况
总结:
Aware
接口提供了一种【内置】 的注入手段,可以注入 BeanFactory
,ApplicationContext
;InitializingBean
接口提供了一种 【内置】 的初始化手段;spring
框架内部的类常用它们。p28 027-第七讲-初始化与销毁
定义Bean1
类,实现InitializingBean
接口和对应的接口方法afterPropertiesSet()
,再定义init1()
方法,在方法上加@PostConstruct
注解,最后定义init3()
@Slf4j
public class Bean1 implements InitializingBean {@PostConstructpublic void init1() {log.debug("初始化1,@PostConstruct");}@Overridepublic void afterPropertiesSet() throws Exception {log.debug("初始化2,InitializingBean接口");}public void init3() {log.debug("初始化3,@Bean的initMethod");}
}
定义Bean2
类,实现DisposableBean
接口和对应的接口方法destroy()
,再定义destroy1()
方法,在方法上加@PreDestroy
注解,最后定义init3()
@Slf4j
public class Bean2 implements DisposableBean {@PreDestroypublic void destroy1() {log.debug("销毁1,@PreDestory");}@Overridepublic void destroy() throws Exception {log.debug("销毁2,DisposableBean接口");}public void destroy3() {log.debug("销毁3,@Bean的destroyMethod");}
}
定义Config
类,类上加@Configuration
注解,类中通过@Bean
注解把Bean1
和Bean2
加到Bean
工厂中,分别在@Bean
注解中指定initMethod = "init3"
,destroyMethod = "destroy"
@Configuration
public class Config {@Bean(initMethod = "init3")public Bean1 bean1() {return new Bean1();}@Bean(destroyMethod = "destroy3")public Bean2 bean2() {return new Bean2();}
}
编写测试代码,观察三个初始化方法和三个销毁方法的执行顺序
@Slf4j
public class TestInitAndDestroy {@Testpublic void testInitAndDestroy() throws Exception {// ⬇️GenericApplicationContext 是一个【干净】的容器,这里只是为了看初始化步骤,就不用springboot启动类进行演示了GenericApplicationContext context = new GenericApplicationContext();isterBean("config", Config.class);// 解析@PostConstruct注解的bean后处理器isterBean(CommonAnnotationBeanPostProcessor.class);// 解析@Configuration、@Component、@Bean注解的bean工厂后处理器isterBean(ConfigurationClassPostProcessor.class);fresh();context.close();}
}
运行结果:
可以看到,spring提供了多种初始化和销毁手段
对于init
,三个初始化方法的执行顺序是
@PostConstruct
-> InitializingBean
接口 -> @Bean
的initMethod
对于destory
, 三个销毁方法的执行顺序是
@PreDestroy
-> DisposableBean
接口 -> @Bean
的destroy
p29 028-第八讲-Scope
spring
的scope
类型:
singleton
:单例prototype
:多例request
:web
请求session
:web
的会话application
:web
的ServletContext
测试scope
类型中的request
、session
、application
定义**BeanForRequest
类,加上@Component
和@Scope
注解,指定Scope
类型为request
**,在类型中定义destroy()
方法,方法上加@PreDestory
注解,代码如下:
@Slf4j
@Scope("request")
@Component
public class BeanForRequest {@PreDestroypublic void destory() {log.debug("destroy");}
}
定义**BeanForSession
类,加上@Component
和@Scope
注解,指定Scope
类型为session
**,在类型中定义destroy()
方法,方法上加@PreDestory
注解,代码如下:
@Slf4j
@Scope("request")
@Component
public class BeanForRequest {@PreDestroypublic void destory() {log.debug("destroy");}
}
定义**BeanForApplication
类,加上@Component
和@Scope
注解,指定Scope
类型为application
**,在类型中定义destroy()
方法,方法上加@PreDestory
注解,代码如下:
@Slf4j
@Scope("request")
@Component
public class BeanForRequest {@PreDestroypublic void destory() {log.debug("destroy");}
}
编写一个MyController
类,加上@RestController
注解,在该类中通过@Autowired
注解注入BeanForRequest
、BeanForSession
和BeanForApplication
的实例,需要注意,这里还需要加@Lazy
注解(至于原因后面会解释),否则会导致@Scope
域失效,再定义一个方法tes()
,加上@GetMapping
注解,用于响应一个http
请求,在test()
方法中,打印beanForRequest
、beanForSession
和beanForApplication
,代码如下:
@RestController
public class MyController {@Lazy@Autowiredprivate BeanForRequest beanForRequest;@Lazy@Autowiredprivate BeanForSession beanForSession;@Lazy@Autowiredprivate BeanForApplication beanForApplication;@GetMapping(value = "/test", produces = "text/html")public String test(HttpServletRequest request, HttpSession session) {// ServletContext sc = ServletContext();String result = "<ul>" +"<li>request scope: " + beanForRequest + "</li>" +"<li>session scope: " + beanForSession + "</li>" +"<li>application scope: " + beanForApplication + "</li>" +"</ul>";return result;}
}
springboot
启动类
@Slf4j
@SpringBootApplication
public class ScopeApplicationContext {public static void main(String[] args) throws InterruptedException {testRequest_Session_Application_Scope();}// 演示 request, session, application作用域private static void testRequest_Session_Application_Scope() throws InterruptedException {ConfigurableApplicationContext context = SpringApplication.run(ScopeApplicationContext.class);}
}
启动后,用谷歌浏览器访问 localhost:8080/test,
浏览器运行结果如下:
再刷新一下当前页,查看运行结果:
控制台运行结果如下:
可以看到两次刷新只有BeanForRequest
对象发生了改变,这是由于scope
为request
类型的对象,会在请求结束后销毁,再来一次请求就会重新创建,请求结束后又会销毁。
接下来我们换个Edge
浏览器访问 localhost:8080/test,对比两个浏览器的显示结果:
可以看到这回除了BeanForRequest
对象不同,BeanForSession
对象也不同了,这是因为开一个新的浏览器会创建一个新的会话,所以BeanForSession
对象也不同了。
继续进行测试,在application.properties
配置一个属性server.servlet.session.timeout=10s
,这个属性的默认值为30
分钟,这样10s
没有操作浏览器的话就会销毁对应session
,不过经过测试这个这个属性最少为1
分钟,低于1分钟一律按照1分钟算。具体原理看这篇博客:,里面进行了源码级别的分析。
设置好之后,重启项目, 然后去浏览器访问,1分钟后控制台会打印session
被销毁,如下图所示:
那什么时候scope
为application
的对象BeanForApplication
会销毁呢?按理说应该是在SpringBoot
程序结束,也即内置的Tomcat
服务器停止的时候调用,但是经过测试:无论是在控制台停止SpringBoot
项目,还是调用ApplicationContext
的close()
方法,都没有调用BeanForApplication
的销毁方法,有知道什么方法可以让它调用的,请评论区告知,谢谢!!!
p30 029-第八讲-Scope失效解决1,2
p31 030-第八讲-Scope失效解决3,4
定义1个单例类SingletonBean
,指定它的Scope
为singleton
,定义5
个多例类,PrototypeBean
、PrototypeBean1
、PrototypeBean2
、PrototypeBean3
、PrototypeBean4
,将这5
个多例类的对象注入到SingletonBean
的5
个属性中,其中PrototypeBean
用来演示多例对象注入到单例对象中Scope
失效的情况,其他四个类的对象用来演示四种解决Scope
失效的方法。相关类的定义如下:
SingletonBean
@Scope("singleton")
@Component
public class SingletonBean {@Autowiredprivate PrototypeBean prototypeBean;@Lazy@Autowiredprivate PrototypeBean1 prototypeBean1;@Autowiredprivate PrototypeBean2 prototypeBean2;@Autowiredprivate ObjectFactory<PrototypeBean3> factory;@Autowiredprivate ApplicationContext context;public PrototypeBean getPrototypeBean() {return prototypeBean;}public PrototypeBean1 getPrototypeBean1() {return prototypeBean1;}public PrototypeBean2 getPrototypeBean2() {return prototypeBean2;}public PrototypeBean3 getPrototypeBean3() {Object();}public PrototypeBean4 getPrototypeBean4() {Bean(PrototypeBean4.class);}
}
PrototypeBean
@Scope("prototype")
@Component
public class PrototypeBean {
}
PrototypeBean1
@Scope("prototype")
@Component
public class PrototypeBean1 {
}
PrototypeBean2
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class PrototypeBean2 {
}
PrototypeBean3
@Scope("prototype")
@Component
public class PrototypeBean3 {
}
PrototypeBean4
@Scope("prototype")
@Component
public class PrototypeBean4 {
}
测试代码:
@Slf4j
@SpringBootApplication
public class ScopeApplicationContext {public static void main(String[] args) throws Exception {testSingletonPrototypeInvalidAndSolve();}// 演示单例中注入多例失效的情况,以及解决失效问题的方法private static void testSingletonPrototypeInvalidAndSolve() {ConfigurableApplicationContext context = SpringApplication.run(ScopeApplicationContext.class);SingletonBean singletonBean = Bean(SingletonBean.class);// 单例中注入多例失效的情况log.debug("{}", PrototypeBean().getClass());log.debug("{}", PrototypeBean());log.debug("{}", PrototypeBean());log.debug("{}", PrototypeBean());System.out.println("------------------------------------");// 解决方法1:在SingletonBean的PrototypeBean1属性上加@Lazy注解log.debug("{}", PrototypeBean1().getClass());log.debug("{}", PrototypeBean1());log.debug("{}", PrototypeBean1());log.debug("{}", PrototypeBean1());System.out.println("------------------------------------");// 解决方法2:在PrototypeBean2的类上的@Scope注解多配置一个属性,如,@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)log.debug("{}", PrototypeBean2().getClass());log.debug("{}", PrototypeBean2());log.debug("{}", PrototypeBean2());log.debug("{}", PrototypeBean2());System.out.println("------------------------------------");// 解决方法3:使用ObjectFactory<PrototypeBean3>工厂类,在每次调用getProtypeBean3()方法中返回Object()log.debug("{}", PrototypeBean3().getClass());log.debug("{}", PrototypeBean3());log.debug("{}", PrototypeBean3());log.debug("{}", PrototypeBean3());System.out.println("------------------------------------");// 解决方法4:在SingletonBean中注入一个ApplicationContext,使用Bean(PrototypeBean4.class)获取对应的多例log.debug("{}", PrototypeBean4().getClass());log.debug("{}", PrototypeBean4());log.debug("{}", PrototypeBean4());log.debug("{}", PrototypeBean4());System.out.println("------------------------------------");}
}
运行结果如下:
aop
是spring
框架中非常重要的功能,其主要实现通常情况下是动态代理,但是这个说法并不全面,还有另外两种实现:
ajc
编译器agent
类加载p32 031-第九讲-aop之ajc增强
先看aop
的第一种实现ajc
编译器代码增强,这是一种编译时的代码增强。
新建一个普通的maven项目
添加依赖
使用ajc
编译器进行代码增强,首先需要在ajc
编译器插件依赖
<build><plugins><plugin><groupId&jo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.11</version><configuration><complianceLevel>1.8</complianceLevel><source>8</source><target>8</target><showWeaveInfo>true</showWeaveInfo><verbose>true</verbose><Xlint>ignore</Xlint><encoding>UTF-8</encoding></configuration><executions><execution><goals><!-- use this goal to weave all your main classes --><goal>compile</goal><!-- use this goal to weave all your test classes --><goal>test-compile</goal></goals></execution></executions></plugin></plugins>
</build>
加入aspectjweaver
的依赖
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.7</version>
</dependency>
加入日志和单元测试的依赖
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.36</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.10</version>
</dependency>
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version>
</dependency>
需要增强的类MyService
public class MyService {private static final Logger log = Logger(MyAspect.class);public void foo() {log.debug("foo()");}
}
切面类MyAspect
,编写execution
表达式,对MyService
类的foo()
方法进行增强
@Aspect // ⬅️注意此切面并未被 Spring 管理,本项目pom文件中根本没有引入spring的相关类
public class MyAspect {private static final Logger log = Logger(MyAspect.class);@Before("execution(* top.jacktgq.service.MyService.foo())")public void before() {log.debug("before()");}
}
测试代码
public class Aop_Aspectj_Test {@Testpublic void testAopAjc() {new MyService().foo();}
}
编译项目,这里需要使用maven
来编译,打开idea
中的maven
面板,点击compile
然后再运行测试代码,可以看到创建MyService
对象并调用foo()
方法会先执行切面类中的before()
方法
注:
有些小伙伴可能会遇到问题:明明按照一样的步骤来操作,可是运行以后代码并没有增强。这是由于idea
中在执行代码之前会默认编译一遍代码,这本来是正常的,可是,如果使用maven
来编译代码,会在执行代码前将maven
编译的代码覆盖,这就会导致maven
的ajc
编译器增强的代码被覆盖,所以会看不到最终的运行效果。
解决办法:在设置中将自动构建项目的选项勾上,就不会出现多次编译覆盖的问题了。
总结:
可以看到没有引入任何跟spring
框架相关的包,MyService
类是通过直接new()
的方式获得的,所以也就不存在使用了动态代理的说法了
打开编译后的MyService.class
文件,双击以后idea会反编译该字节码文件,可以看到foo()
方法体的开头加了一行代码,这就是增强的代码,这是ajc
编译器在编译MyService
类的时候为我们添加的代码,这是一种编译时的增强。
p33 032-第十讲-aop之agent增强
现在来看aop
的另外一种实现agent
增强,这是一种类加载时的代码增强。
新建一个普通的maven项目
加入aspectjweaver
的依赖
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.7</version>
</dependency>
加入日志和单元测试的依赖
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.36</version>
</dependency>
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.10</version>
</dependency>
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version>
</dependency>
需要增强的类MyService
@Slf4j
public class MyService {public void foo() {log.debug("foo()");bar();}public void bar() {log.debug("bar()");}
}
切面类MyAspect
,编写execution
表达式,对MyService
类的foo()
方法进行增强
@Aspect // ⬅️注意此切面并未被 Spring 管理,本项目pom文件中根本没有引入spring的相关类
@Slf4j
public class MyAspect {@Before("execution(* top.jacktgq.service.MyService.*())")public void before() {log.debug("before()");}
}
测试代码
public class Aop_agent_Test {@Testpublic void testAopAgent() throws Exception {MyService myService = new MyService();myService.foo();ad();}
}
运行时需要在 VM options 里加入 -javaagent:D:同步空间repositoryorgaspectjaspectjweaver1.9.7aspectjweaver-1.9.7.jar
把其中 D:同步空间repository
改为你自己 maven
仓库起始地址
注:还需要在resources/META-INF
目录下建一个aspectj
会自动扫描到这个配置文件,不加这个配置文件不会出效果。
<aspectj><aspects><aspect name="top.jacktgq.aop.MyAspect"/><weaver options="-verbose -showWeaveInfo"><include within="top.jacktgq.service.MyService"/><include within="top.jacktgq.aop.MyAspect"/></weaver></aspects>
</aspectj>
运行测试代码,可以看到创建MyService
对象并调用foo()
方法会先执行切面类中的before()
方法
注:
有些小伙伴可能会遇到问题:明明按照一样的步骤来操作,可是运行以后代码并没有增强。这是由于idea
中在执行代码之前会默认编译一遍代码,这本来是正常的,可是,如果使用maven
来编译代码,会在执行代码前将maven
编译的代码覆盖,这就会导致maven
的ajc
编译器增强的代码被覆盖,所以会看不到最终的运行效果。
解决办法:在设置中将自动构建项目的选项勾上,就不会出现多次编译覆盖的问题了。
总结:
可以看到没有引入任何跟spring
框架相关的包,MyService
类是通过直接new()
的方式获得的,所以也就不存在使用了动态代理的说法了
打开编译后的MyService.class
文件,双击以后idea会反编译该字节码文件,可以看到foo()
方法体中并没有添加多余的代码,所以就不是编译时增强了,而是类加载的时候增强的,这里可以借助阿里巴巴的Arthas工具,下载地址:.html,解压以后进入到arthas的bin目录下,启动黑窗口,输入java -jar .arthas-boot.jar
,在输出的java
进程列表里面找到我们要连接的进程,输入对应进程的序号,我这里是4
,连接上以后会打印ARTHAS
的logo
再输入jad top.jacktgq.service.MyService
反编译内存中的MyService
类
可以看到foo()
和bar()
方法体的第一行都加了一行代码,这就说明通过添加虚拟机参数-javaagent
的方式可以在类加载的时候对代码进行增强。
p34 033-第十一讲-aop之proxy增强-jdk
测试代码
public class AopJdkProxyTest {@Testpublic void testJdkProxy() {// jdk的动态代理,只能针对接口代理// 目标对象Target target = new Target();// 用来加载在运行期间动态生成的字节码ClassLoader loader = ClassLoader();Foo fooProxy = (Foo) wProxyInstance(loader, new Class[]{Foo.class}, (proxy, method, args) -> {System.out.println(");Object result = method.invoke(target, args);System.out.println(");return result; // 让代理也返回目标方法执行的结果});fooProxy.foo();}
}
interface Foo {void foo();
}
@Slf4j
final class Target implements Foo {public void foo() {log.debug("target foo");}
}
运行结果
jdk
动态代理总结:
Foo
接口,代理对象类型不能强转成目标对象类型;final
修饰。p35 034-第十一讲-aop之proxy增强-cglib
public class AopCglibProxyTest {@Testpublic void testCglibProxy1() {// 目标对象Target target = new Target();Foo fooProxy = (Foo) ate(Target.class, (MethodInterceptor) (obj, method, args, proxy) -> {System.out.println(");Object result = method.invoke(target, args); // 用方法反射调用目标System.out.println(");return result;});System.out.Class());fooProxy.foo();}@Testpublic void testCglibProxy2() {// 目标对象Target target = new Target();Foo fooProxy = (Foo) ate(Target.class, (MethodInterceptor) (obj, method, args, proxy) -> {System.out.println(");// proxy 它可以避免反射调用Object result = proxy.invoke(target, args); // 需要传目标类System.out.println(");return result;});System.out.Class());fooProxy.foo();}@Testpublic void testCglibProxy3() {// 目标对象Foo fooProxy = (Foo) ate(Target.class, (MethodInterceptor) (obj, method, args, proxy) -> {System.out.println(");// proxy 它可以避免反射调用Object result = proxy.invokeSuper(obj, args); // 不需要目标类,需要代理自己System.out.println(");return result;});System.out.Class());fooProxy.foo();}
}
interface Foo {void foo();
}
@Slf4j
class Target implements Foo {public void foo() {log.debug("target foo");}
}
运行结果
cglib
动态代理总结:
MethodInterceptor
的intercept()
方法的第2个参数是method
,可以通过反射对目标方法进行调用
Object result = method.invoke(target, args); // 用方法反射调用目标
第4个参数proxy
,可以不用反射就能对目标方法进行调用;
Object result = proxy.invoke(target, args); // 需要传目标类 (spring用的是这种)
// 或者
Object result = proxy.invokeSuper(obj, args); // 不需要目标类,需要代理自己
代理类不需要实现接口;
代理对象和目标对象是父子关系,代理类继承于目标类;
目标类定义的时候不能加final
修饰,否则代理类就无法继承目标类了,会报java.lang.IllegalArgumentException: Cannot subclass final class top.lib.Target
异常;
目标类方法定义的时候不能加final
修饰,否则代理类继承目标类以后就不能重写目标类的方法了。
p36 035-第十二讲-jdk代理原理
p37 036-第十二讲-jdk代理原理
p38 037-第十二讲-jdk代理源码
为了更好地探究jdk
动态代理原理,先用代码显式地模拟一下这个过程。
先定义一个Foo
接口,里面有一个foo()
方法,再定义一个Target
类来实现这个接口,代码如下所示:
public interface Foo {void foo();
}
@Slf4j
public final class Target implements Foo {public void foo() {log.debug("target foo");}
}
public class $Proxy0 implements Foo {@Overridepublic void foo() {// 1. 功能增强System.out.println(");// 2. 调用目标new Target().foo();}
}
public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0();proxy.foo();}
}
接下来对Target
类中的foo()
方法进行增强
首先想到的是,再定义一个类也同样地实现一下Foo
接口,然后在foo()
方法中编写增强代码,接着再new
一个Target
对象,调用它的foo()
方法,代码如下所示:
public class $Proxy0 implements Foo {@Overridepublic void foo() {// 1. 功能增强System.out.println(");// 2. 调用目标new Target().foo();}
}
// 测试运行
public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0();proxy.foo();}
}
上面的代码把功能增强的代码和调用目标的代码都固定在了代理类的内部,不太灵活。因此可以通过定义一个InvocationHandler
接口的方式来将这部分代码解耦出来,代码如下:
public interface InvocationHandler {void invoke();
}
public interface Foo {void foo();
}
@Slf4j
public final class Target implements Foo {public void foo() {System.out.println("target foo");}
}
public class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {h.invoke();}
}
public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic void invoke() {// 1. 功能增强System.out.println(");// 2. 调用目标new Target().foo();}});proxy.foo();}
}
第2个版本的代码虽然将功能增强的代码和调用目标的代码通过接口的方式独立出来了,但还是有问题,如果此时接口中新增了一个方法bar()
,Target
类和$Proxy0
类中都要实现bar()
方法,那么调用proxy
的foo()
和bar()
方法都将间接调用目标对象的foo()
方法,因为在InvocationHandler
的invoke()
方法中调用的是target.foo()
方法,代码如下:
public interface InvocationHandler {void invoke();
}
public interface Foo {void foo();void bar();
}
@Slf4j
public final class Target implements Foo {public void foo() {System.out.println("target foo");}@Overridepublic void bar() {log.debug("target bar");}
}
public class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {h.invoke();}@Overridepublic void bar() {h.invoke();}
}
public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic void invoke() {// 1. 功能增强System.out.println(");// 2. 调用目标new Target().foo();}});proxy.foo();proxy.bar();}
}
改进方法是,代理类中调用方法的时候,通过反射把接口中对应的方法Method
对象作为参数传给InvocationHandler
,代码如下:
public interface InvocationHandler {void invoke(Method method, Object[] args) throws Throwable;
}
public interface Foo {void foo();void bar();
}
public final class Target implements Foo {public void foo() {System.out.println("target foo");}@Overridepublic void bar() {System.out.println("target bar");}
}
public class $Proxy0 implements Foo {private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {try {Method foo = DeclaredMethod("foo");h.invoke(foo, new Object[0]);} catch (Throwable e) {e.printStackTrace();}}@Overridepublic void bar() {try {Method bar = DeclaredMethod("bar");h.invoke(bar, new Object[0]);} catch (Throwable e) {e.printStackTrace();}}
}
public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {// 1. 功能增强System.out.println(");// 2. 调用目标method.invoke(new Target(), args);}});proxy.foo();proxy.bar();}
}
第3个版本的代码其实已经离jdk动态代理生成的代码很相近了,为了更好地学习底层,更近一步,修改Foo
接口的中bar()
方法,使其具有int
类型的返回值,因此InvocationHandler
的invoke()
方法也得有返回值,同时将代理对象本身作为第一个参数,具体代码如下:
public interface InvocationHandler {Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
public interface Foo {void foo();int bar();
}
public final class Target implements Foo {public void foo() {System.out.println("target foo");}@Overridepublic int bar() {System.out.println("target bar");return 1;}
}
public class $Proxy0 implements Foo {static Method foo;static Method bar;static {try {foo = DeclaredMethod("foo");bar = DeclaredMethod("bar");} catch (NoSuchMethodException e) {throw new Message());}}private InvocationHandler h;public $Proxy0(InvocationHandler h) {this.h = h;}@Overridepublic void foo() {try {h.invoke(this, foo, new Object[0]);} catch (RuntimeException | Error e) {throw e;} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}@Overridepublic int bar() {try {return (int) h.invoke(this, bar, new Object[0]);} catch (RuntimeException | Error e) {throw e;} catch (Throwable e) {throw new UndeclaredThrowableException(e);}}
}
public class Main {public static void main(String[] args) {Foo proxy = new $Proxy0(new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {// 1. 功能增强System.out.println(");// 2. 调用目标return method.invoke(new Target(), args);}});proxy.foo();System.out.println("bar()方法返回值:" + proxy.bar());}
}
到这里跟jdk的动态代理只有些微差距了,jdk
的动态代码会让代理类再继承一个Proxy
类,里面定义了一个InvocationHandler
接口的对象,代理类中会通过super(h)
调用父类Proxy的构造,这里建议结合视频教程理解。
p39 038-第十二讲-jdk代理字节码生成
p40 039-第十二讲-jdk反射优化
p41 040-第十三讲-cglib代理原理
p42 041-第十三讲-cglib代理原理-MethodProxy
p43 042-第十四讲-MethodProxy原理
p44 043-第十四讲-MethodProxy原理
p45 044-第十五讲-Spring选择代理
p46 045-第十五讲-Spring选择代理
p47 046-第十五讲-Spring选择代理
p48 047-第十六讲-切点匹配
p49 048-第十六讲-切点匹配
p50 049-第十七讲-Advisor与@Aspect
p51 050-第十七讲-findEligibleAdvisors
p52 051-第十七讲-wrapIfNecessary
p53 052-第十七讲-代理创建时机
p54 053-第十七讲-吐槽@Order
p55 054-第十七讲-高级切面转低级切面
p56 055-第十八讲-统一转换为环绕通知
p57 056-第十八讲-统一转换为环绕通知
p58 057-第十八讲-适配器模式
p59 058-第十八讲-调用链执行
p60 059-第十八讲-模拟实现调用链
p61 060-第十八讲-模拟实现调用链-责任链模式
p62 061-第十九讲-动态通知调用
p63 062-第十九讲-动态通知调用
p64 063-第廿讲-DispatcherServlet初始化时机
p65 064-第廿讲-DispatcherServlet初始化时机
p66 065-第廿讲-DispatcherServlet初始化执行的操作
p67 066-第廿讲-RequestMappingHandlerMapping
p68 067-第廿讲-RequestMappingHandlerAdapter
p69 068-第廿讲-RequestMappingHandlerAdapter-参数和返回值解析器
p70 069-第廿讲-RequestMappingHandlerAdapter-自定义参数解析器
p71 070-第廿讲-RequestMappingHandlerAdapter-自定义返回值解析器
p72 071-第廿一讲-参数解析器-准备
p73 072-第廿一讲-参数解析器-准备
p74 073-第廿一讲-参数解析器-@RequestParam 0-4
p75 074-第廿一讲-参数解析器-组合模式
p76 075-第廿一讲-参数解析器 5-9
p77 076-第廿一讲-参数解析器 10-12
p78 077-第廿二讲-获取参数名
p79 078-第廿二讲-获取参数名
p80 079-第廿三讲-两套底层转换接口
p81 080-第廿三讲-一套高层转换接口
p82 081-第廿三讲-类型转换与数据绑定演示
p83 082-第廿三讲-web环境下数据绑定演示
p84 083-第廿三讲-绑定器工厂
p85 084-第廿三讲-绑定器工厂-@InitBinder扩展
p86 085-第廿三讲-绑定器工厂-ConversionService扩展
p87 086-第廿三讲-绑定器工厂-默认ConversionService
p88 087-第廿三讲-加餐-如何获取泛型参数
p89 088-第廿四讲-@ControllerAdvice-@InitBinder
p90 089-第廿四讲-@ControllerAdvice-@InitBinder
p91 090-第廿五讲-控制器方法执行流程
p92 091-第廿五讲-控制器方法执行流程
p93 092-第廿五讲-控制器方法执行流程-代码
p94 093-第廿六讲-@ControllerAdvice-@ModelAttribute
p95 094-第廿七讲-返回值处理器
p96 095-第廿七讲-返回值处理器-1
p97 096-第廿七讲-返回值处理器-2-4
p98 097-第廿七讲-返回值处理器-5-7
p99 098-第廿八讲-MessageConverter
p100 099-第廿八讲-MessageConverter
p101 100-第廿九讲-@ControllerAdvice-ResponseBodyAdvice
p102 101-第廿九讲-@ControllerAdvice-ResponseBodyAdvice
p103 102-第卅讲-异常处理
p104 103-第卅讲-异常处理
p105 104-第卅一讲-@ControllerAdvice-@ExceptionHandler
p106 105-第卅二讲-tomcat异常处理
p107 106-第卅二讲-tomcat异常处理-自定义错误地址
p108 107-第卅二讲-tomcat异常处理-BasicErrorController
p109 108-第卅二讲-tomcat异常处理-BasicErrorController
p110 109-第卅三讲-HandlerMapping与HandlerAdapter-1
p111 110-第卅三讲-HandlerMapping与HandlerAdapter-自定义
p112 111-第卅四讲-HandlerMapping与HandlerAdapter-2
p113 112-第卅五讲-HandlerMapping与HandlerAdapter-3
p114 113-第卅五讲-HandlerMapping与HandlerAdapter-3-优化
p115 114-第卅五讲-HandlerMapping与HandlerAdapter-3-优化
p116 115-第卅五讲-HandlerMapping与HandlerAdapter-4-欢迎页
p117 116-第卅五讲-HandlerMapping与HandlerAdapter-总结
p118 117-第卅六讲-MVC执行流程
p119 118-第卅六讲-MVC执行流程
p120 119-第卅七讲-构建boot骨架项目
p121 120-第卅八讲-构建boot war项目
p122 121-第卅八讲-构建boot war项目-用外置tomcat测试
p123 122-第卅八讲-构建boot war项目-用内嵌tomcat测试
p124 123-第卅九讲-boot执行流程-构造
p125 124-第卅九讲-boot执行流程-构造-1
p126 125-第卅九讲-boot执行流程-构造-2
p127 126-第卅九讲-boot执行流程-构造-3
p128 127-第卅九讲-boot执行流程-构造-4-5
p129 128-第卅九讲-boot执行流程-run-1
p130 129-第卅九讲-boot执行流程-run-1
p131 130-第卅九讲-boot执行流程-run-8-11
p132 131-第卅九讲-boot执行流程-run-2,12
p133 132-第卅九讲-boot执行流程-run-3
p134 133-第卅九讲-boot执行流程-run-4
p135 134-第卅九讲-boot执行流程-run-5
p136 135-第卅九讲-boot执行流程-run-5
p137 136-第卅九讲-boot执行流程-run-6
p138 137-第卅九讲-boot执行流程-run-7
p139 138-第卅九讲-boot执行流程-小结
p140 139-第卌讲-Tomcat重要组件
p141 140-第卌讲-内嵌Tomcat
p142 141-第卌讲-内嵌Tomcat与Spring整合
p143 142-第卌一讲-自动配置类原理
p144 143-第卌一讲-自动配置类原理
p145 144-第卌一讲-AopAutoConfiguration
p146 145-第卌一讲-AopAutoConfiguration
p147 146-第卌一讲-自动配置类2-4概述
p148 147-第卌一讲-自动配置类2-DataSource
p149 148-第卌一讲-自动配置类3-MyBatis
p150 149-第卌一讲-自动配置类3-mapper扫描
p151 150-第卌一讲-自动配置类4-事务
p152 151-第卌一讲-自动配置类5-MVC
p153 152-第卌一讲-自定义自动配置类
p154 153-第卌二讲-条件装配底层1
p155 154-第卌二讲-条件装配底层2
p156 155-第卌三讲-FactoryBean
p157 156-第卌四讲-@Indexed
p158 157-第卌五讲-Spring代理的特点
p159 158-第卌五讲-Spring代理的特点
p160 159-第卌六讲-@Value注入底层1
p161 160-第卌六讲-@Value注入底层2
p162 161-第卌七讲-@Autowired注入底层-doResolveDependency外1
p163 162-第卌七讲-@Autowired注入底层-doResolveDependency外2
p164 163-第卌七讲-@Autowired注入底层-doResolveDependency内1
p165 164-第卌七讲-@Autowired注入底层-doResolveDependency内2
p166 165-第卌七讲-@Autowired注入底层-doResolveDependency内3
p167 166-第卌七讲-@Autowired注入底层-doResolveDependency内4
p168 167-第卌八讲-事件监听器1
p169 168-第卌八讲-事件监听器2
p170 169-第卌八讲-事件监听器3
p171 170-第卌八讲-事件监听器4
p172 171-第卌八讲-事件监听器5
p173 172-第卌九讲-事件发布器1
p174 173-第卌九讲-事件发布器2
本文发布于:2024-01-29 13:16:00,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170650536315537.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |