JDK动态代理(JDK Proxy)和CGLib都是Spring中用于实现AOP代理的技术,但是它们之间存在以下区别:
总结:JDK Proxy是Java自带的,在JDK高版本性能比较高的动态代理工具,但是它要求被代理类必须实现接口,它的性能在JDK 7之后是略高于CGLib;而CGLib是基于字节码技术实现的第三方动态代理,它是通过生成代理对象的子类来实现代理的,所以要求被代理类不能被final修饰
Spring默认使用的是JDK动态代理,这在官方问档中有所说明:官方文档
如下图所示:
Spring默认使用的是JDK动态代理,这在官方问档中有所说明:官方文档
如下图所示:
然而Spring Boot2.0之后默认使用的是GCLib,如图:
//创建一个普通的目标类,该类没有实现任何接口
public class MyTargetClass {
public void doSomething() {
System.out.println("Doing something in the target class");
}
}
//创建一个AOP切面类
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.example.MyTargetClass.*(..))")
public void beforeMethod() {
System.out.println("Before method execution");
}
}
//创建一个Spring Boot应用类
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
MyTargetClass target = new MyTargetClass();
target.doSomething();
}
}
当我们运行的时候发现,是正常执行的所以 Spring Boot中AOP默认使用了CGLib
Bean对象中有以下几种注入方式:
属性注入是我们最熟悉的,也是日常开发最常使用的一种注入方式,它的实现代码如下:
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/add")
public UserInfo add(@RequestParam String username, @RequestParam String password) {
return userService.add(username, password);
}
}
优点:属性注入最大的优点就是实现简单、使用简单。只需要给变量添加一个@AutoWried注释,就可以在不new对象的情况下,直接获得注入对象
缺点:属性注入的缺点主要是有以下两种:
功能性问题:无法注入一个不可变的对象(final修饰)
在Java中final修饰的对象要么直接赋值,要么在构造方法中赋值,所以当使用属性注入final对象时,不符合Java中的final使用规范,所以注入不成功
通用性问题:只能适应与IoC容器,Idea也会提醒不建议使用:
Setter注入的实现代码如下:
@RestController
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@RequestMapping("/add")
public UserInfo add(@RequestParam String username, @RequestParam String password) {
return userService.add(username, password);
}
}
优点:它符合单一职责的设计原则(一个类应该只负责一项职责或一个功能),因为每一个Setter只针对一个对象
缺点:不能注入不可变的对象(final修饰);注入的对象可以调用多次,也就是注入对象会被修改
构造方法注入是Spring官方从4.x之后推荐的注入方式,它的实现代码如下:
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@RequestMapping("/add")
public UserInfo add(@RequestParam String username, @RequestParam String password) {
return userService.add(username, password);
}
}
如果当前的类只有一个构造方法,那么@Autowired有也可以省略,如:
@RestController
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@RequestMapping("/add")
public UserInfo add(@RequestParam String username, @RequestParam String password) {
return userService.add(username, password);
}
}
优点:
注入不可变对象:使用构造方法可以注入不可变对象,如下代码:
注入对象不会被修改:构造方法不会像Setter注入那样,构造方法在对象创建只会执行依次,因此它不存在注入对象被随时(调用)修改的情况
完全初始化:因为依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全初始化
通用性更好:构造注入和属性注入不同,构造方法注入可适用于任何环境,无论是IoC框架还是非IoC框架,构造方法注入的代码都是通用的
缺点:不如属性注入简单
Bean的生命周期是指在Spring(IoC)中从创建到销毁的过程。Bean的生命周期主要是包含以下五个流程:
BeanPostProcessor
实现,它们的后置处理方法将在 Bean 初始化之后被调用。这个阶段同样可以执行一些额外的操作默认情况下,Bean是非线程安全的。因为默认情况下Bean的作用域是单例模式,那么此时,所有的请求都会共享一个Bean实例,这意味着如果这个Bean实例在多线程下,会被同时修改(成员变量),就可能出现线程安全问题
单例模式就是所有线程可见共享的,而原型模式则是每次请求都创建一个新的原型对象
并不是,单例Bean主要是分为以下两种类型:
无状态Bean(线程安全):
public class StatelessBean {
public int add(int a, int b) {
return a + b;
}
}
有状态Bean(非线程安全):
public class StatefulBean {
private int count = 0;
public int increment() {
return count++;
}
}
想要保证有状态Bean线程安全,可以通过以下几种方法:
使用ThreadLocal:
ThreadLocal
,每个线程都拥有自己的变量副本,从而避免了线程安全问题。public class MyThreadLocalBean {
private static final ThreadLocal counter = new ThreadLocal<>();
public int increment() {
counter.set(counter.get() == null ? 1 : counter.get() + 1);
return counter.get();
}
}
使用锁机制:
public class MySynchronizedBean {
private int counter = 0;
private final Object lock = new Object();
public synchronized int increment() {
return ++counter;
}
}
设置Bean为原型作用域(Prototype):
@Scope("prototype")
public class MyPrototypeBean {
private int counter = 0;
public int increment() {
return ++counter;
}
}
使用线程安全容器(Atomic):
Atomic
类,如 AtomicInteger
,来保证线程安全。这些类提供了一些原子操作,避免了使用锁的复杂性。import java.util.concurrent.atomic.AtomicInteger;
public class MyAtomicBean {
private AtomicInteger counter = new AtomicInteger(0);
public int increment() {
return counter.incrementAndGet();
}
}
实际工作中,通常会根据具体业务来选择合适的线程安全方案,但是以上解决线程安全的方案中:
ThreadLocal和原型作用域会使用更多的资源,占用更多的空间来保证线程安全,所以在使用时通常不会作为最佳的考虑方案
而锁机制和线程安全容器通常会优先考虑,但是需要注意的是AtomicInteger底层是乐观锁CAS实现的,因此存在乐观锁的典型问题ABA问题(如果有状态的Bean中既有++操作,又有--操作的时候,可能出现ABA问题),此时就要使用锁机制,或者AtomicStampedReference来解决ABA问题
Spring Boot的自动装配是指在应用程序启动时,根据类路径下的依赖、配置文件以及预定义规则,自动配置和初始化Spring应用程序中的各种组件、模块和功能的过程
这种自动配置机制大大减少了开发人员手动配置的工作,使得开发者可以更专注注重业务逻辑的实现,同时提供了更高效、快速的应用程序启动和开发体验
例如,在Spring我们需要手动设置数据库的连接URL、用户名、密码等参数,并将其实例化为一个Bean。如下列代码:
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
@Value
注解用于注入配置文件中的属性值。在 application.properties
或 application.yml
中,你可以设置数据库连接的 URL、用户名和密码,例如:
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=username
spring.datasource.password=password
自定义注解可以标记在方法上或类上,用于编译器或运行期进行待定的业务功能处理。在Java中,自定义注解使用@interface关键字来定义,它可以实现如:日志记录、性能监控、权限校验等功能
在Spring Boot中实现一个自定义注解可以通过以下两种方式:
实际工作中我们通常会使用自定义注解来实现如权限校验或幂等性判断等功能
幂等性判断是指在分布式系统或并发环境中,对于同一操作的多次重复请求,系统的响应结果应该是一致的。简无论接收到多少次相同的请求,系统的行为和结果都应该是相同的
拦截器(Interceptor)是一种在应用程序中用于拦截、处理和转换请求和响应的组件。在Web开发中,拦截器是一种常见的技术,用于在请求到达控制器之前或响应返回浏览器之前进行干预和处理
Speing Boot中拦截器实现主要是分为以下两步:
创建拦截器类
创建一个实现HandlerInterceptor接口,并实现其中的方法。例如:
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyCustomInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在请求处理之前执行,返回 true 则继续执行后续的拦截器和处理器方法,返回 false 则中断执行
System.out.println("Pre-handle method is called");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在请求处理之后,视图渲染之前执行
System.out.println("Post-handle method is called");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在整个请求处理完毕,视图渲染完毕后执行
System.out.println("After-completion method is called");
}
}
配置拦截器
创建一个配置类,继承WebMvcConfigurerAdapter或实现WebMvcConfigurer接口,并重写addInterceptors。例如:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyCustomInterceptor())
.addPathPatterns("/my/**") // 设置拦截路径
.excludePathPatterns("/my/exclude"); // 设置排除拦截路径
}
}
过滤器(Filter)是一种常见的 Web 组件,用于在 Servlet 容器中对请求和响应进行预处理和后处理。过滤器提供了一种在请求和响应的处理链上干预和修改数据的机制,可以用于实现一些与应用程序业务逻辑无关的通用功能
过滤器可以使用 Servlet 3.0 提供的 @WebFilter 注解,配置过滤的 URL 规则,然后再实现 Filter 接口,重写接口中的 doFilter 方法。具体实现代码如下:
@WebFilter(
filterName = "timingFilter",
urlPatterns = "/*",
initParams = {
// 可以配置一些初始化参数
}
)
public class TimingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化操作,这里暂时不需要进行额外的初始化
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 在请求处理前记录请求的时间戳
long startTime = System.currentTimeMillis();
// 调用 chain.doFilter() 将请求传递给下一个过滤器或 Servlet
chain.doFilter(request, response);
// 在请求处理后记录请求的处理时间
long endTime = System.currentTimeMillis();
long processingTime = endTime - startTime;
System.out.println("Request processed in " + processingTime + " milliseconds.");
}
@Override
public void destroy() {
// 资源释放操作,这里暂时不需要进行额外的销毁
}
}