首页
统计
留言板
友接
推荐
免费图床
服务监控
Search
1
immich开源相册部署教程
746 阅读
2
GraalVM将Java打包原生Native应用
318 阅读
3
将旧手机改造成Linux服务器
273 阅读
4
Java函数式编程
180 阅读
5
FRP内网穿透教程
161 阅读
编程语言
Java
Python
Go
单片机
Arduino
ESP8266
ESP32
STM32
51单片机
树莓派
运维
Docker容器
随身小记
登录
Search
标签搜索
Spring
SpringMVC
Java
docker
DSM
群晖
iptables
ssh
spring
mybaits
redis
SpringBoot
消息队列
科长
累计撰写
36
篇文章
累计收到
6
条评论
首页
栏目
编程语言
Java
Python
Go
单片机
Arduino
ESP8266
ESP32
STM32
51单片机
树莓派
运维
Docker容器
随身小记
页面
统计
留言板
友接
推荐
免费图床
服务监控
搜索到
11
篇与
Java
的结果
2023-07-03
Java Spring框架
1、什么是Spring?Spring 是一个支持快速开发的Java EE应用程序框架。他提供了一系列底层容器和基础设施,并可以快速集成大量常用的开源框架。是开发Java EE项目的必备。2、Spring FrameworkSpring Framework 主要包括几个模块:支持 IoC 和 AOP 的容器支持 JDBC 和 ORM 的数据访问模块支持申明式事物模块支持基于 Servlet 的 MVC开发支持基于 Reactive 的 WEB 开发以及集成 JMS、JavaMail、JMX、缓存等其他模块3、IoC 容器3-1、初识IoC、 JavaBeanSpring 提供的容器又叫做 IoC容器。IoC全称Inversion of Control,直译为控制反转。为什么需要IoC?按照以往的开发的方式假设我们需要通过UserService来获取指定用户的信息:public class UserService { private HikariConfig config = new HikariConfig(); private DataSource dataSource = new HikariDataSource(config); public User getUserById(Long userId){ try(Connection conn = dataSource.getConnection()){ ... return user; } } }为了从数据库中查询出用户,UserService持有一个DataSource。为了实例化一个HikariDataSource,又需要实例化一个HikariConfig查询完用户,比如需要通过UserRoleService再去查询用户的角色信息:public class UserRoleService { private HikariConfig config = new HikariConfig(); private DataSource dataSource = new HikariDataSource(config); public User getUserRoleByUserId(Long userId){ try(Connection conn = dataSource.getConnection()){ ... return role; } } }因为UserRoleService中也需要去访问数据库,所以不得不又实例化了一个HikariDataSource现在我们处理用户发送过来的请求:public class UserInfoServlet extends HttpServlet { private UserService userService = new UserService(); private UserRoleService userRoleService = new UserRoleService(); protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { long userId = getFromCookie(req); User user = userService.getUserById(userId); Role role = userRoleService.getUserRoleByUserId(userId); .... } }上述的每一个Service组件都是以new的形式创建出来的,其有部分缺点:实例化一个组件比较复杂,例如UserService 和 UserRoleService 都需要创建HikariDataSource ,实际上需要读取配置,才能实例化HikariConfig,然后再去实例化HikariDataSource没有必要让每个Service组件分别去创建DataSource的实例,完全可以共享一个DataSource 实例,但是由谁去创建,谁去获取创建好的实例,都不好处理。同理UserInfoServlet或者其他的xxxServlet 需要使用UserService实例或者UserRoleService实例时,也应当共享同一个实例,但是比较复杂不好处理很多组件需要销毁以便释放资源,例如DataSource,但如果组件被对多个组件共享,怎样确保他的使用方已被全部销毁随着组件越来越多,需要共享的组件写起来非常困难,依赖关系也更加复杂从上面几个问题中,不难看出,随着系统越来越庞大,组件越来越多,其组件的生命周期和相互之间的依赖关系如果由组件自身来维护,则会大大增加系统的复杂度,也会导致组件之间耦合度越来越高,测试维护越来越困难因此,核心问题是:谁负责创建组件谁负责根据依赖关系组装组件销毁的时候,如何按照依赖顺序正确销毁解决这些问题的核心方案就是IoC容器在传统的项目中,控制权在程序本身,程序的控制流程完全由开发者控制,例如:UserInfoServlet 创建了UserService, 在创建UserService的时候又创建了DataSource 组件。这种模式的缺点就是,一个组件如果要使用另一个组件,就必须要先知道如何正确的创建它,否则无法正常使用在IoC模式下,控制权发生了反转,即从应用程序转移到了IoC容器,所以组件不再由应用程序自己创建和配置,而是由IoC容器负责。应用程序只需要直接使用已经创建好并且配置好的组件。为了能让组件在IoC容器中被“装配”出来,需要某种“注入”机制。例如UserService自己并不会去创建DataSource,而是等待外部通过setDataSource()方法来注入一个配置好的DataSource:public class UserService { private DataSource dataSource; public void setDataSource(DataSource dataSource){ this.dataSource = dataSource; } }不需要再通过new的形式来实例化一个DataSource组件,而是通过注入的方式注入一个DataSource,这种方式带来了很多好处:UserService不需要关心如何创建的DataSource,因此不需要再去写读取数据库配置之类的代码DataSource 被注入到 UserService 同样也可以被注入到 UserRoleService 中,共享一个组件变得非常简单因此IoC又称为依赖注入,它解决了一个最主要的问题:将组件的创建+配置与组件的使用分离开,并且由IoC容器负责管理组件的生命周期因为IoC容器要负责实例化所有的组件,因此需要告诉容器如何创建组件,以及之间的依赖关系。通过配置XML文件的来实现:<beans> <bean id="dataSource" class="HirkariDataSource" /> <bean id="userService" class="UserService"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="userRoleService" class="UserRoleService"> <property name="dataSource" ref="dataSource" /> </bean> </beans>上述XML配置文件指示IoC容器创建三个JavaBean组件,并把id为dataSource的组件通过属性dataSource(调用setDataSource()函数)注入到了另外两个组件中,在Spring IoC中,我们把所有的组件统称为JavaBean,即配置一个组件就是配置一个JavaBean依赖注入方式从上面可以看出,依赖注入可以通过set()方法实现。同时依赖注入也可以通过构造方法实现通过有参构造注入依赖:public class UserService { private DataSource dataSource; public UserService(DataSource dataSource) { this.dataSource = dataSource; } }无侵入容器 Spring的IoC容器是一个高度可拓展的无侵入容器。所谓无侵入就是指应用程序的组件无需实现Spring的特定接口,或者说组件根本不知道自己在Spring的容器中运行3-2、配置JavaBean的方式项目目录结构spring-ioc-appcontext ├── pom.xml └── src └── main ├── java │ └── com │ └── bystart │ └── learnjava │ ├── Main.java │ └── service │ ├── User.java │ ├── UserService.java │ └── UserRoleService.java └── resources └── application.xml3-2-1、使用XML配置application.xml<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userService" class="com.bystart.learnjava.service.UserService"/> <bean id="userRoleService" class="com.bystart.learnjava.service.UserRoleService" /> <!-- 注入一些属性案例 <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="password" /> <property name="maximumPoolSize" value="10" /> <property name="autoCommit" value="true" /> </bean> --> <!-- 注入一个Bean 案例 <bean id="userService" class="com.bystart.learnjava.service.UserService"> <property name="dataSource" ref="dataSource" /> </bean> --> </beans>Main.java 文件内容:public class Main { public static void main(String[] args){ ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); UserService userService = context.getBean(UserService.class); UserRoleService userRoleService = context.getBean(UserRoleService.class); User loginUser = userService.login("username@qq.com","password"); loginUser.setRoleName(userRoleService.getUserRoleNameByUserId(loginUser.getUserId())); } }我们从创建Spring的代码中可以看出,Spring容器就是ApplicationContext,它是一个接口,有很多实现类, 这里我们选择ClassPathXmlApplicationContext表示它会自动从classpath中查找指定的xml配置文件ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");获得了ApplicationContext的实例,就获得了IoC容器的引用。从ApplicationContext中我们可以根据Bean的id获取Bean,但更多的时候一般根据Bean的类型来获取Bean的引用UserService userService = context.getBean(UserService.class);Spring还提供了另一种IoC容器叫BeanFactory,使用方式和ApplicationContext 类似:BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml")); UserService userService = factory.getBean(UserService.class);BeanFactory和ApplicationContext的区别在于,BeanFactory的实现是按需创建,即第一次获取Bean时才创建这个Bean,而ApplicationContext会一次性创建所有的Bean。实际上,ApplicationContext接口是从BeanFactory接口继承而来的,并且,ApplicationContext提供了一些额外的功能,包括国际化支持、事件和通知机制等。通常情况下,我们总是使用ApplicationContext,很少会考虑使用BeanFactory3-2-2、使用注解配置我们可以使用更简单的方式来配置一个JavaBean,那就是使用注解@Component:@Component public class UserRoleService { }@Component 注解就相当于定义了一个Bean,它有一个可选的名称,默认是userRoleService,即小写开头的类名想使用这个Bean也是非常的简单只需要加一个 @Autowired,自动注入到属性,想要注入对应的依赖,则自己也需要被IoC托管,需要加上@Component注解:@Component public class UserService { @Autowired private UserRoleService userRoleService; }@Autowired 还可以加入到构造方法中@Component public class UserService { private UserRoleService userRoleService; public UserService(@Autowired UserRoleService userRoleService){ this.userRoleService = userRoleService; } }开启注解扫描,我们在Main.java添加部分代码:@Configuration @ComponentScan public class Main { public static void main(String[] args){ ApplicationContext context = new AnnotationConfigApplicationContext(Main.class); UserService userService = context.getBean(UserService.class); UserRoleService userRoleService = context.getBean(UserRoleService.class); User loginUser = userService.login("username@qq.com","password"); loginUser.setRoleName(userRoleService.getUserRoleNameByUserId(loginUser.getUserId())); } }Main.class中加入一个注解@Configuration,表示他是一个配置类,因为在创建ApplicationContext时:ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);使用的实现类是AnnotationConfigApplicationContext,他的构造方法必须传入一个标注了@Configuration 的类,此外Main.java还标注了@ComponentScan它表示告诉容器,自动搜索扫描当前类所在的包及子包下所有标注了 @Component的类给创建到IoC容器中进行托管,并根据@Autowired 自动装配使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:每个Bean被标注为@Component并正确使用@Autowired注入配置类被标注为@Configuration和@ComponentScan所有Bean均在指定包以及子包内4、Spring AOP在AOP编程中,我们经常会遇到下面的概念:Aspect:切面,即一个横跨多个核心逻辑的功能,或者称之为系统关注点Joinpoint:连接点,即定义在应用程序流程的何处插入切面的执行Pointcut:切入点,即一组连接点的集合Advice:增强,指特定连接点上执行的动作Introduction:引介,指为一个已有的Java对象动态地增加新的接口Weaving:织入,指将切面整合到程序的执行流程中Interceptor:拦截器,是一种实现增强的方式Target Object:目标对象,即真正执行业务的核心逻辑对象AOP Proxy:AOP代理,是客户端持有的增强后的对象引用4-1、装配AOP我们以UserService 和 UserRoleService 为例,我们给UserService的每个业务的方法执行前添加日志,给UserRoleService每个方法执行前后添加日志记录我们定义一个LoggingAspect:@Aspect @Component public class LoggingAspect { // 在 UserService 的每个方法执行前执行该部分代码 @Before("execution(public * com.bystart.learnjava.service.UserService.*(..))") public void doAddUserServiceLog(){ System.out.println("开始添加日志"); ... } // 在 UserRoleService 的每个方法 执行前/后 执行该部分代码 @Around("execution(public * com.bystart.learnjava.service.UserRoleService.*(..))") public Object doAddUserRoleServiceLog(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("开始添加执行前日志"); // 获取目标方法的返回内容 Object retVal = pjp.proceed(); System.err.println("开始添加执行后的日志,目标方法返回内容:" + pjp.getSignature()); // 真正返回内容给调用方 return retVal; } }观察doAddUserServiceLog()方法,我们定义了一个@Before注解,后面的字符串是告诉AspectJ应该在何处执行该方法,这里写的意思是:执行UserService的每个public方法前执行doAddUserServiceLog()代码再观察doAddUserRoleServiceLog()方法,我们定义了一个@Around注解,它和@Before不同,@Around可以决定是否执行目标方法,因此,我们在doAddUserRoleServiceLog()内部先打印日志,再调用方法,最后打印日志后返回结果在LoggingAspect类的声明处,除了用@Component表示它本身也是一个Bean外,我们再加上@Aspect注解,表示它的@Before标注的方法需要注入到UserService的每个public方法执行前,@Around标注的方法需要注入到UserRoleService的每个public方法执行前后紧接着,我们需要给@Configuration类加上一个@EnableAspectJAutoProxy注解:@Configuration @ComponentScan @EnableAspectJAutoProxy public class Main { public static void main(String[] args){ ApplicationContext context = new AnnotationConfigApplicationContext(Main.class); UserService userService = context.getBean(UserService.class); UserRoleService userRoleService = context.getBean(UserRoleService.class); User loginUser = userService.login("username@qq.com","password"); loginUser.setRoleName(userRoleService.getUserRoleNameByUserId(loginUser.getUserId())); } }Spring的IoC容器看到这个注解,就会自动查找带有@Aspect的Bean,然后根据每个方法的@Before、@Around等注解把AOP注入到特定的Bean中那么LoggingAspect是怎么实现将方法注入到其他Bean,并且在目标方法执行前后触发LoggingAspect的方法的呢其实原理非常的简单,它是创建了一个代理对象:public class UserServiceAopProxy extends UserService { private UserService target; private LoggingAspect aspect; public UserServiceAopProxy(UserService target, LoggingAspect aspect){ this.target = target; this.aspect = aspect; } public User login(String email, String password){ // 先执行Aspect的代码 aspect.doAddUserServiceLog(); // 再执行原有的逻辑 return target.login(email, password); } }这些都是Spring容器启动时为我们自动创建的注入了Aspect的子类,它取代了原始的UserService(原始的UserService实例作为内部变量隐藏在UserServiceAopProxy中)。如果我们打印从Spring容器获取的UserService实例类型,它类似UserService$$EnhancerBySpringCGLIB$$1f44e01c,实际上是Spring使用CGLIB动态创建的子类,但对于调用方来说,感觉不到任何区别。Spring对接口类型使用JDK动态代理,对普通类使用CGLIB创建子类。如果一个Bean的class是final,Spring将无法为其创建子类。可见,虽然Spring容器内部实现AOP的逻辑比较复杂(需要使用AspectJ解析注解,并通过CGLIB实现代理类),但我们使用AOP非常简单,一共需要三步:定义执行方法,并在方法上通过AspectJ的注解告诉Spring应该在何处调用此方法标记@Component和@Aspect在@Configuration类上标注@EnableAspectJAutoProxy4-2、拦截器类型顾名思义,拦截器有以下类型:@Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了@After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行@AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码@AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码@Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能
2023年07月03日
79 阅读
0 评论
2 点赞
1
2