文章目录
- Spring
-
- Ioc(控制反转)
- DI(依赖注入)
-
- 属性注入
- 构造方法注入
- [Setter 注入](#Setter 注入)
- 三种注入方式的比较
- @Autowired存在的问题
- AOP(面向切面编程)
-
- AOP的实现
- AOP的核心概念
- @PointCut
- [切面优先级 @Order](#切面优先级 @Order)
- 切点表达式
-
- [execution 切点表达式](#execution 切点表达式)
- @annotation
- [Spring AOP原理](#Spring AOP原理)
- [Spring、Spring Boot 、Spring MVC区别](#Spring、Spring Boot 、Spring MVC区别)
Spring
Spring 是包含了众多⼯具⽅法的 IoC 容器,Spring 的核心是IoC 和 AOP
Ioc(控制反转)
Ioc(控制反转)是一种思想不是一个技术实现,在类上⾯添加 @RestController 和 @Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交给Spring管理, 就是IoC思想。
例如,现有类 A 依赖于类 B,类 B 依赖于类 C,对于传统的方式来说,类 A 中手动 new 一个 B 的对象,类 B 中手动 new 一个 C 的对象,此时当C中要添加一个 int 参数,那么类 B 也需要添加参数,同时连带 A 也需要做更改,这样的方式耦合度高。

对于Ioc方式来说,不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面去取即可。

改进之后获取依赖对象的过程进行了反转,不再是使用放对象创建并控制依赖对象了,而是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由当前类控制了。
要把某个对象交给IOC容器管理,需要在类上添加⼀个注解@Component,而Spring框架为了更好的服务web应⽤程序,提供了更丰富的注解
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration.
- 方法注解:@Bean
@Controller
下面的代码使用@Controller将Bean存储到Spring中
此时的Bean就是由Spring IoC容器实例化、组装和管理的对象
java
import org.springframework.stereotype.Controller;
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi(){
System.out.println("UserController");
}
}
@Service
java
import org.springframework.stereotype.Service;
@Service
public class UserService {
public void sayHi() {
System.out.println("UserService");
}
@Repository
java
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public void sayHi() {
System.out.println("UserRepository");
}
}
@Component
java
import org.springframework.stereotype.Component;
@Component
public class UserComponent {
public void sayHi() {
System.out.println("UserComponent");
}
}
@Configuration
java
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfiguration {
public void sayHi() {
System.out.println("UserConfiguration");
}
}
类注解的适用性
| 注解 | 适用性情况 |
|---|---|
| @Controller | Controller控制层, 接收请求, 对请求进⾏处理, 并进⾏响应 |
| @Service | Service业务逻辑层, 处理具体的业务逻辑. |
| @Repository | Dao数据访问层,也称为持久层. 负责数据访问操作 |
| @Configuration | 配置层. 处理项⽬中的⼀些配置信息 |
| @Component | 通用组件注解,任何被 Spring 管理的类都可以使用,是@Service、@Repository、@Controlle、r@Configuration的父注解 |
获取Bean的方式
下面的代码通过Spring上下⽂对象来获取Bean,获取方式包括:按照类型获取、按照名称获取、按照类型+名称获取。
@SpringBootApplication注解标记的这个SpringIocDemoApplication是项目的启动类
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//根据bean类型, 从Spring上下⽂中获取对象
UserController userController1 = context.getBean(UserController.class);
//根据bean名称, 从Spring上下⽂中获取对象
UserController userController2 = (UserController) context.getBean("userController");
//根据bean类型+名称, 从Spring上下⽂中获取对象
UserController userController3 = context.getBean("userController",UserController.class);
//使⽤对象
System.out.println(userController1);
System.out.println(userController2);
System.out.println(userController3);
}
}
如果没有显式的提供bean名称(BeanId),Spring将为该bean⽣成唯⼀的名称。命名约定使⽤Java标准约定作为实例字段名,即bean名称以小写字⺟开头,然后使⽤驼峰式大小写;特殊的当第⼀个和第⼆个字符都是大写时, 将保留原始的大小写。
Bean的名称是唯一的,可以在注解后加括号双引号改名
通过getBean函数来获取Bean,其源代码如下,即通过BeanFactory来获取Bean
java
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String var1) throws BeansException;
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
......
面试问题:BeanFactory 和 ApplicationContext 的区别1.继承关系和功能⽅⾯来说:Spring 容器有两个顶级的接⼝:BeanFactory 和 ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能⼒,而ApplicationContext 属于 BeanFactory 的⼦类,它除了继承了 BeanFactory 的所有功能之外,它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持
2.从性能⽅⾯来说:ApplicationContext 是⼀次性加载并初始化所有的 Bean 对象,而BeanFactory 是需要那个才去加载那个,因此更加轻量. (空间换时间)
@Bean
@Bean用在配置类的方法上(通常是 @Configuration 类)。它的作用是告诉 Spring 容器,这个方法返回的对象,请把它纳入管理,成为一个 Bean。
java
@Component
public class BeanConfig {
@Bean
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2(){
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
@Bean注解标识的对象,默认名称为方法名
同样的,在@Bean("name")中可以进行重命名
java
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//根据bean名称, 从Spring上下⽂中获取对象
User user1 = (User) context.getBean("user1");
User user2 = (User) context.getBean("user2");
System.out.println(user1);
System.out.println(user2);
}
}
输出结果为:User(name=zhangsan,age=18) User(name=lisi,age=19)
扫描路径
Spring默认扫描的范围是SpringBoot启动类所在包及其⼦包,注解必须放在扫描路径下才能生效。
也可以通过 @ComponentScan 来配置扫描路径。
java
@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
//从Spring上下⽂中获取对象
User u1 = (User) context.getBean("u1");
//使⽤对象
System.out.println(u1);
}
}
DI(依赖注入)
DI(Dependency Injection,依赖注⼊)指容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源。Ioc是一种思想,DI 是IoC的⼀种实现。
- 使用注解@Component来将类存入的容器中
- 使用注解@Autowired来获取容器内的实例
属性注入
使用注解@Autowired来进行属性注入
java
@Component
public class BookDao {
public List<BookInfo> mockData() {
......
return books;
}
}
java
@Component
public class BookService {
@Autowired
private BookDao bookDao;
public List<BookInfo> getBookList() {
List<BookInfo> books = bookDao.mockData();
......
return books;
}
}
java
@RequestMapping("/book")
@RestController
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/getList")
public List<BookInfo> getList(){
List<BookInfo> books = bookService.getBookList();
......
return books;
}
}
构造方法注入
构造方法注入是在类的构造方法中实现注入
当只有一个构造函数时,Spring即使用该构造函数;如果有多个构造函数的时候,Spring会使用默认无参构造函数 ,如果没有默认的无参构造函数会报错。
可以通过添上 @Autowired 来明确指定到底使用哪个构造方法
java
@Controller
public class UserController {
//注⼊⽅法2: 构造⽅法
private UserService userService;
private UserConfig userConfig;
public UserController() {
}
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public UserController(UserService userService,UserConfig userConfig) {
this.userService = userService;
this.userConfig = userConfig;
}
public void sayHi(){
System.out.println("hi,UserController2...");
userService.sayHi();
userConfig.sayHi();
}
}
Setter 注入
Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set 方法的时候需要加上@Autowired 注解
java
@Controller
public class UserController3 {
//注⼊⽅法3: Setter⽅法注⼊
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController3...");
userService.sayHi();
}
}
三种注入方式的比较
| 注入方式 | 优点 | 缺点 |
|---|---|---|
| 属性注入 | 简洁⽅便 | 只能⽤于 IoC 容器; 不能注⼊⼀个Final修饰的属性; |
| 属性注入 | 可以注⼊final修饰的属性; 注⼊的对象不会被修改; 依赖对象在使⽤前⼀定会被完全初始化; 通⽤性好, 构造⽅法是JDK⽀持的, 更换框架后也适⽤; | 注⼊多个对象时, 代码繁琐 |
| Setter注入 | ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊ | 不能注⼊⼀个Final修饰的属性; setter⽅法可能会被多次调用, 注入对象有被修改的⻛险; |
final修饰的属性,要么在声明时进行初始化,要么在构造函数中进行赋值
@Autowired存在的问题
当同⼀类型存在多个bean时, 使⽤@Autowired会存在问题,如下所示,通过UserController.class 获取 Bean 时,通过@Autowired注入user时,会先根据类型来获取,如果匹配不到就通过名称(此时名称为user)来获取,此时若存在多个对象,会出现报错,
java
@Component
public class BeanConfig {
@Bean("u1")
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
java
@Controller
public class UserController {
//注⼊user
@Autowired
private User user;
public void sayHi(){
System.out.println("UserController");
System.out.println(user);
}
}
针对这种问题,Spring提供了以下几种解决⽅案,
- @Primary:当存在多个相同类型的Bean注⼊时,加上@Primary注解,来确定默认的实现
- @Qualifier注解:指定当前要注⼊的bean对象。 在@Qualifier的value属性中,指定注⼊的bean的名称。 @Qualifier注解不能单独使用,必须配合@Autowired使用
- @Resource注解:按照bean的名称进行注入。通过name属性指定要注⼊的bean的名称。
java
@Component
public class BeanConfig {
@Primary //指定该bean为默认bean的实现
@Bean("u1")
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
java
@Controller
public class UserController {
@Qualifier("user2") //指定bean名称
@Autowired
private User user;
public void sayHi(){
System.out.println("UserController");
System.out.println(user);
}
}
java
@Controller
public class UserController {
@Resource(name = "user2")
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
面试问题:@Resource 和 @Autowired 的区别
- @Autowired 是spring框架提供的注解,@Resource 是JDK提供的注解
- @Autowired 默认是按照类型进行注入,如果匹配不到就通过名称来获取,若名称有@Qualifier指定,则按照指定名称,若没有则按照默认的名称进行查找;@Resource是按照名称注入,比 @Autowired⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。
AOP(面向切面编程)
AOP(面向切面编程) 是 OOP(面向对象编程)的一种延续,二者互补,并不对立。AOP 允许把那些横跨多个业务模块的通用功能(比如日志记录、权限检查、事务管理)从业务代码中抽离出来,单独管理和复用,从而让业务代码更干净、更专注。
例如,现在有⼀些业务的执行效率比较低,耗时较长,需要对接口进行优化。需要统计当前项目中每⼀个业务方法的执行耗时。可以在方法前后记录时间并计算差值。但⼀个项目中会包含很多业务模块,每个业务模块又有很多接口,⼀个接口又包含很多方法, 如果要在每个业务方法中都记录会增加工作量。AOP就可以做到在不改动这些原始方法的基础上, 针对特定的方法进行功能的增强。
AOP的实现
下面是对Spring AOP的一个实现,先引入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
实现切面逻辑,@Aspect 标识这是⼀个切面类;@Around表示环绕通知,在目标方法的前后都会被执行为,后面的表达式表示对哪些方法进行增强。
java
@Slf4j
@Aspect
@Component
public class TimeAspect {
/**
* 记录方法耗时
*/
@Around("execution(* com.example.demo.controller.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
//记录方法执行开始时间
long begin = System.currentTimeMillis();
//执行原始方法
Object result = pjp.proceed();
//记录方法执行耗时
log.info(pjp.getSignature() + "执行耗时: {}ms", System.currentTimeMillis() - begin);
return result;
}
}
AOP的核心概念
整个代码称之为一个切面,代码中相关定义如下,
- @Around("execution(* com.example.demo.controller.*.*(...))")表示切点,双引号中的内容是切点表达式
- ProceedingJoinPoint pjp表示连接点
- long begin = System.currentTimeMillis();是目标方法执行前;
- Object result = pjp.proceed();表示目标方法执行
- log.info(pjp.getSignature() + "执行耗时: {}ms", System.currentTimeMillis() - begin);表示目标方法执行后
-
切点(Pointcut),也称之为"切入点"Pointcut 的作用就是提供一组规则 (使用 AspectJ pointcut expression language 来描述),告诉程序对哪些方法来进行功能增强。
-
连接点就是满足切点表达式规则的方法,也就是可以被AOP控制的方法。
-
通知:通知就是具体要做的工作,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)。比如上述程序中记录业务方法的耗时时间,就是通知。Spring中AOP的通知类型有以下几种:
- @Around: 环绕通知, 此注解标注的通知方法在目标方法前, 后都被执行
- @Before: 前置通知, 此注解标注的通知方法在目标方法前被执行
- @After: 后置通知, 此注解标注的通知方法在目标方法后被执行, 无论是否有异常都会执行
- @AfterReturning: 返回后通知, 此注解标注的通知方法在目标方法后被执行, 有异常不会执行
- @AfterThrowing: 异常后通知,此注解标注的通知方法发生异常后执行
-
切面(Aspect) = 切点(Pointcut) + 通知(Advice)。通过切面就能够描述当前AOP程序需要针对于哪些方法,在什么时候执行什么样的操作。
现有Java程序如下,
java
@Slf4j
@Aspect
@Component
public class AspectDemo {
//前置通知
@Before("execution(* com.example.demo.controller.*.*(..))")
public void doBefore() {
log.info("执行AspectDemo.before 方法...");
}
//后置通知
@After("execution(* com.example.demo.controller.*.*(..))")
public void doAfter() {
log.info("执行 AspectDemo.After 方法...");
}
//返回后通知
@AfterReturning("execution(* com.example.demo.controller.*.*(..))")
public void doAfterReturning() {
log.info("执行 AspectDemo.AfterReturning 方法...");
}
//抛出异常后通知
@AfterThrowing("execution(* com.example.demo.controller.*.*(..))")
public void doAfterThrowing() {
log.info("执行 doAfterThrowing 方法...");
}
//添加环绕通知
@Around("execution(* com.example.demo.controller.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("执行AspectDemo.doAround 目标方法前...");
Object result = joinPoint.proceed();
log.info("执行AspectDemo.doAround 目标方法后...");
return result;
}
}
方法正确执行后的日志打印如下,

方法异常情况执行的 打印情况如下,此时Object result = joinPoint.proceed();产生异常,doAround 目标方法后没有进行打印。

@PointCut
上面代码存在⼀个问题,就是存在大量重复的切点表达式Spring提供了 @PointCut 注解,把公共的切点表达式提取出来,需要用时引用该切入点表达式即可。
java
@Slf4j
@Aspect
@Component
public class AspectDemo {
//定义切点(公共的切点表达式)
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
//...代码省略
}
//后置通知
@After("pt()")
public void doAfter() {
//...代码省略
}
//返回后通知
@AfterReturning("pt()")
public void doAfterReturning() {
//...代码省略
}
//抛出异常后通知
@AfterThrowing("pt()")
public void doAfterThrowing() {
//...代码省略
}
//添加环绕通知
@Around("pt()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//...代码省略
}
}
当切点定义使用 private 修饰时,仅能在当前切面类中使用,当其他切面类也要使用当前切点定义时,就需要把 private 改为public。引用方式为:全限定类名.方法名()
java
public void pt(){}
java
@Slf4j
@Component
@Aspect
public class AspectDemo2 {
@Before("com.bite.aop.aspect.AspectDemo.pt()")
public void doBefore(){
log.info("执行AspectDemo2.before方法....");
}
@After("com.bite.aop.aspect.AspectDemo.pt()")
public void doAfter(){
log.info("执行AspectDemo2.after方法....");
}
}
此时的运行结果如下,

切面优先级 @Order
当我们在⼀个项目中,定义了多个切面类时,,优先级默认按照名称排序,@Before 通知字母排名靠前的先执行; @After 通知:字母排名靠前的后执行。

可以通过@Order注解来控制这些切面通知的执行顺序,
java
@Aspect
@Component
@Order(4)
public class AspectDemo2 {
//...代码省略
}
java
@Aspect
@Component
@Order(1)
public class AspectDemo3 {
//...代码省略
}
java
@Aspect
@Component
@Order(3)
public class AspectDemo4 {
//...代码省略
}
打印结果如下,先执行AspectDemo3-4-2,再执行AspectDemo2-4-3;@Order 注解标识的切面类,执行顺序如下:@Before 通知:数字越小先执行;@After 通知:数字越大先执行

切点表达式
execution 切点表达式
execution() 是最常用的切点表达式,用来匹配方法,语法如下,其中访问修饰符和异常可以省略。

切点表达式支持通配符表达:
- * 表示匹配任意字符,只匹配一个元素(返回类型, 包, 类名, 方法或者方法参数)
- 包名使用 * 表示任意包(一层包使用一个*)
- 类名使用 * 表示任意类
- 返回值使用 * 表示任意返回值类型
- 方法名使用 * 表示任意方法
- 参数使用 * 表示一个任意类型的参数(类型固定,内容任意)
- ... 表示匹配多个连续的任意符号, 可以通配任意层级的包, 或任意类型, 任意个数的参数
a. 使用 ... 配置包名,标识此包以及此包下的所有子包
b. 可以使用 ... 配置参数,任意个任意类型的参数
@annotation
execution表达式更适用于有规则的,如果要匹配多个无规则方法时,使用 execution 就不是很方便了。此时可以借助自定义注解的方式以及另一种切点表达式 @annotation 来描述这一类的切点,实现步骤如下:
- 编写自定义注解
- 使用 @annotation 表达式来描述切点
- 在连接点的方法上添加自定义注解
java
//@Target标明注解可以声明在类上还是方法上
//@Retention标明注解的生命周期
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
使用 @annotation 切点表达式定义切点, 只对 @MyAspect ⽣效切类代码如下:
java
@Slf4j
@Component
@Aspect
public class MyAspectDemo {
//前置通知
@Before("@annotation(com.example.demo.aspect.MyAspect)")
public void before(){
log.info("MyAspect -> before ...");
}
//后置通知
@After("@annotation(com.example.demo.aspect.MyAspect)")
public void after(){
log.info("MyAspect -> after ...");
}
}
在TestController中的t1()和UserController中的u1()这两个方法上添加⾃定义注解 @MyAspect , 其他方法不添加
java
@MyAspect
@RequestMapping("/t1")
public String t1() {
return "t1";
}
java
@MyAspect
@RequestMapping("/u1")
public String u1(){
return "u1";
}
面试问题:Spring AOP的实现方式
- 基于注解 @Aspect
- 基于⾃定义注解 (参考自定义注解 @annotation)
- 基于Spring API (通过xml配置的方式)
- 基于代理来实现
Spring AOP原理
代理模式
代理模式即为其他对象提供一种代理以控制对这个对象的访问。此时不再直接对目标方法进行调用,而是通过代理类间接调用。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
静态代理
静态代理指在程序运行前,代理类的 .class文件就已经存在了。(在出租房子之前,中介已经做好了相关的工作,就等租户来租房子了),如下例所示,
HouseSubject 接口定义了" 出租房子 "和" 售卖房子 "的行为
java
// 1. 定义接口
public interface HouseSubject {
void rentHouse(); // 出租房子
void saleHouse(); // 出售房子
}
RealHouseSubject 表示真实的房东
java
// 2. 真实房东实现接口
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东, 我出租房子");
}
@Override
public void saleHouse() {
System.out.println("我是房东, 我出售房子");
}
}
HouseProxy表示中介代理,同样实现 HouseSubject 接口。它内部持有一个 HouseSubject 类型的对象(即被代理的真实对象)
java
// 3. 代理类(中介)
public class HouseProxy implements HouseSubject {
private HouseSubject houseSubject; // 被代理对象
public HouseProxy(HouseSubject houseSubject) {
this.houseSubject = houseSubject;
}
@Override
public void rentHouse() {
System.out.println("我是中介, 开始代理出租");
houseSubject.rentHouse();
System.out.println("我是中介, 代理出租结束");
}
@Override
public void saleHouse() {
System.out.println("我是中介, 开始代理出售");
houseSubject.saleHouse();
System.out.println("我是中介, 代理出售结束");
}
}
客户端创建真实房东,创建中介代理,并将房东传入,最终的结果是通过代理租房/卖房。
java
// 4. 客户端测试
public class Client {
public static void main(String[] args) {
HouseSubject landlord = new RealHouseSubject();
HouseSubject proxy = new HouseProxy(landlord);
proxy.rentHouse();
proxy.saleHouse();
}
}
动态代理
在上面静态代理的例子中,每次修改接口(Subject)和业务实现类(RealSubject)时, 还需要修改代理类(Proxy)。如果有新增接口(Subject)和业务实现类(RealSubject),也需要对每一个业务实现类新增代理类(Proxy)。
对于动态代理来说,不需要针对每个目标对象都单独创建一个代理对象,而是把这个创建代理对象的工作推迟到程序运行时,由JVM来实现。也就是说动态代理在程序运行时,根据需要动态创建生成。
Java也对动态代理提供了常见的API,
- JDK 动态代理
- CGLIB 动态代理
(1)JDK 动态代理的步骤如下
- 定义一个接口及其实现类(静态代理中的 HouseSubject 和 RealHouseSubject )
- 自定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中会调用目标方法(被代理类的方法)并自定义一些处理逻辑
- 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h) 方法创建代理对象
java
@Slf4j
public class JDKInvocationHandler implements InvocationHandler {
//目标对象即就是被代理对象
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
/**
* 调用目标方法,并对方法进行增强
* @param proxy 代理类
* @param method 目标方法
* @param args 参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理增强内容
log.info("JDK动态代理开始");
//通过反射调用被代理类的方法
Object result= method.invoke(target, args);
//代理增强内容
log.info("JDK动态代理结束");
return result;
}
}
java
public class Main {
public static void main(String[] args) {
// JDK 动态代理
// 目标类
HouseSubject target = new RealHouseSubject();
//生成代理对象
HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocationHandler(target));
proxy.rentHouse();
proxy.saleHouse();
}
}

Proxy 类中的 newProxyInstance 方法用于生成一个代理对象,参数Class<?>[] interfaces 决定了JDK动态代理只能代理实现了接口的⼀些类。
java
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//...代码省略
}
(2)CGLIB 动态代理的实现步骤如下,
- 定义一个类(被代理类)
- 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于增强目标方法,和 JDK 动态代理中的 invoke 方法类似
- 通过 Enhancer 类的 create()创建代理类
java
@Slf4j
public class CGLIBInterceptor implements MethodInterceptor {
//目标对象, 即被代理对象
private Object target;
public CGLIBInterceptor(Object target){
this.target = target;
}
/**
* 调用目标方法,并对目标方法进行功能增强
*
* @param o 代理对象
* @param method 目标方法
* @param objects 参数
* @param methodProxy 方法代理
* @return 返回值
* @throws Throwable 异常
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 代理增强内容
log.info("CGLib动态代理开始");
//通过反射调用被代理类的方法
Object result= methodProxy.invoke(target, objects);
//代理增强内容
log.info("CGLib动态代理开始");
return result;
}
}
java
public class DynamicMain {
public static void main(String[] args) {
HouseSubject target = new RealHouseSubject();
HouseSubject proxy = (HouseSubject)
Enhancer.create(target.getClass(), new CGLIBInterceptor(target));
proxy.rentHouse();
proxy.saleHouse();
}
}

Spring Framework 使用 JDK 代理和 CGLIB 代理,两种方式。如果代理的是接口就使用JDK代理,如果代理的是类(未实现接口)就使用CGLIB 代理。
可以通过@EnableAspectJAutoProxy(proxyTargetClass = true)来设置默认的代理方式,如果默认代理是JDK,那么处理接口时使用JDK代理,处理类(未实现接口)时使用CGLIB 代理;如果默认代理是CGLIB 代理,那么无论有没有实现接口,都是用CGLIB 代理;
Spring Boot 2.X之前, 默认使用 JDK 代理,此时的代理方式和Spring Framework一样;Spring Boot 2.X开始, 默认全部使用 CGLIB 代理;
Spring、Spring Boot 、Spring MVC区别
-
Spring:Spring是一个开发应用框架,有这么几个标签:轻量级、一站式、模块化,其目的是用于简化企业级应用程序开发。
- Spring的主要功能:管理对象,以及对象之间的依赖关系, ⾯向切⾯编程, 数据库事务管理, 数据访问, web框架⽀持等.
- 但是Spring具备高度可开放性, 并不强制依赖Spring, 开发者可以⾃由选择Spring的部分或者全部, Spring可以⽆缝继承第三⽅框架, ⽐如数据访问框架(Hibernate 、JPA), web框架(如Struts、JSF)
-
Spring MVC:Spring MVC是Spring的一个子框架,Spring诞生之后,许多人觉得很好用,于是按照MVC模式设计了一个 MVC框架(一些用Spring 解耦的组件),主要用于开发WEB应用和网络接口,所以,Spring MVC是一个Web框架。
- Spring MVC基于Spring 进行开发的,天生的与Spring框架集成。可以让我们更简洁的进行Web层开发,⽀持灵活的 URL 到页面控制器的映射,提供了强大的约定大于配置的契约式编程支持, 非常容易与其他视图框架集成,如 Velocity、FreeMarker等
-
Spring Boot:Spring Boot是对Spring的一个封装,为了简化Spring应用的开发而出现的,中小型企业没有成本研究自己的框架,使用Spring Boot可以更加快速的搭建框架,降级开发成本,让开发人员更加专注于Spring应用的开发,而无需过多关注XML的配置和一些底层的实现。
- Spring Boot 是个脚手架,插拔式搭建项目,可以快速的集成其他框架进来
- 比如想使用SpringBoot开发Web项目,只需要引入Spring MVC框架即可, Web开发的工作是SpringMVC完成的,而不是SpringBoot,想完成数据访问, 只需要引入 Mybatis 框架即可。Spring Boot只是辅助简化项目开发的,让开发变得更加简单,甚至不需要额外的web服务器,直接生成 jar 包执行即可。