IoC,Inversion of Control,直译为控制反转。
IoC又称为依赖注入(DI:Dependency Injection),它解决了一个最主要的问题:将组件的创建+配置与组件的使用相分离,并且,由IoC容器负责管理组件的生命周期。
如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。
核心问题:
解决问题的核心方案就是IoC。
因为IoC容器要负责实例化所有的组件,因此,有必要告诉容器如何创建组件,以及各组件的依赖关系。一种最简单的配置是通过XML文件。
<beans>
<bean id="dataSource" class="HikariDataSource" />
<bean id="bookService" class="BookService">
<property name="dataSource" ref="dataSource" />
bean>
<bean id="userService" class="UserService">
<property name="dataSource" ref="dataSource" />
bean>
beans>
上述XML配置文件指示IoC容器创建3个JavaBean组件,并把id为dataSource的组件通过属性dataSource(即调用setDataSource()方法)注入到另外两个组件中。
**Bean:**在Spring的IoC容器中,我们把==所有组件统称为JavaBean==,即配置一个组件就是配置一个Bean。
**无侵入容器:**Spring的IoC容器是一个高度可扩展的无侵入容器。所谓无侵入,是指应用程序的组件无需实现Spring的特定接口,或者说,组件根本不知道自己在Spring的容器中运行
原来的写法:BookService需要自身创建数据库实例dataSource
public class BookService {
private HikariConfig config = new HikariConfig();
private DataSource dataSource = new HikariDataSource(config);
public Book getBook(long bookId) {
try (Connection conn = dataSource.getConnection()) {
...
return book;
}
}
}
依赖注入的方式:作为构造函数的参数 或 set的方式注入
// 构造函数
public class BookService {
private DataSource dataSource;
public BookService(DataSource dataSource) {
this.dataSource = dataSource;
}
}
// set函数
public class BookService {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
注意到UserService通过setMailService()注入了一个MailService。然后,我们需要编写一个特定的application.xml配置文件,告诉Spring的IoC容器应该如何创建并组装Bean:
application.xml:
<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.itranswarp.learnjava.service.UserService">
<property name="mailService" ref="mailService" />
bean>
<bean id="mailService" class="com.itranswarp.learnjava.service.MailService" />
beans>
注意观察上述配置文件,其中与XML Schema相关的部分格式是固定的,我们只关注两个的配置:
都有一个id标识,相当于Bean的唯一ID;userServiceBean中,通过注入了另一个Bean;把上述XML配置文件用Java代码写出来,就像这样:
UserService userService = new UserService();
MailService mailService = new MailService();
userService.setMailService(mailService);
只不过Spring容器是通过读取XML文件后使用反射完成的。
总结
Spring的IoC容器接口是ApplicationContext,并提供了多种实现类;
通过XML配置文件创建IoC容器时,使用ClassPathXmlApplicationContext;
持有IoC容器后,通过getBean()方法获取Bean的引用。
使用XML配置的优点是所有的Bean都能一目了然地列出来,并通过配置注入能直观地看到每个Bean的依赖。它的缺点是写起来非常繁琐,每增加一个组件,就必须把新的Bean配置到XML中。
解决·:可以使用Annotation配置,可以完全不需要XML,让Spring自动扫描Bean并组装它们。
具体操作:
@Component就相当于定义了一个Bean@Autowired就相当于把指定类型的Bean注入到指定的字段中。@Component
public class UserService {
@Autowired
MailService mailService;
...
}
使用Annotation配合自动扫描能大幅简化Spring的配置,我们只需要保证:
@Component并正确使用@Autowired注入;@Configuration和@ComponentScan;@Configuration
@ComponentScan
public class AppConfig {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
User user = userService.login("bob@example.com", "password");
System.out.println(user.getName());
}
}
List;@Autowired(required=false)允许可选注入;@Bean标注的方法创建Bean;@PostConstruct和@PreDestroy对Bean进行初始化和清理;@Primary,其他必须用@Quanlifier("beanName")指定别名;@Quanlifier("beanName")指定某个Bean;FactoryBean来使用工厂模式创建Bean。classpath:/path/to/file的形式注入。即@Value("classpath:/logo.txt")src/main/resources即可(classpath类路径)。@PropertySource自动读取配置,并以@Value("${key}")的形式注入;${key:defaultValue}指定默认值;#{bean.property}形式注入时,Spring容器自动把指定Bean的指定属性值注入。@Profile配置不同的Bean;@Conditional来进行条件装配,Spring Boot在此基础上进一步提供了基于配置、Class、Bean等条件进行装配。参考:
什么是Spring Bean:Bean就是由IOC容器初始化、装配及管理的对象,除此之外,和程序中的其他对象没有区别
怎么使用Bean:bean是由spring容器创建和管理的,各组件之间的依赖关系也是由spring容器管理的
Spring Bean的装配(三种方式):
基于xml配置装配
基于Java代码装配@Configuration
基于注解的装配@Component
组件扫描(component scanning),Spring会自动发现应用上下文中所创建的bean。
自动装配( autowiring),Spring自动满足bean之间的依赖
Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。
什么是依赖注入(DI,Dependency Injection):
每个基于应用程序的 java 都有几个对象,由这些对象一起工作来呈现出终端用户所看到的工作的应用程序。当编写一个复杂的 Java 应用程序时,应用程序类应该尽可能独立于其他 Java 类来增加这些类重用的可能性,并且在做单元测试时,测试独立于其他类的独立性。依赖注入(或有时称为布线)有助于把这些类粘合在一起,同时保持他们独立。
在这里我们所做的就是创建一个 TextEditor 和 SpellChecker 之间的依赖关系。而在控制反转IoC的场景中,我们会这样做:
package com.tutorialspoint;
public class TextEditor {
private SpellChecker spellChecker;
public TextEditor(SpellChecker spellChecker) {
System.out.println("Inside TextEditor constructor." );
this.spellChecker = spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}
在这里,我们已经从 TextEditor 中删除了全面控制,并且把它保存到其他地方(即 XML 配置文件),且依赖关系(即 SpellChecker 类)通过类构造函数被注入到 TextEditor 类中。因此,控制流通过依赖注入(DI)已经“反转”,因为你已经有效地委托依赖关系到一些外部系统。
具体可以看:Constructor-based dependency injection的例子。要点:
构造函数中传参
src文件夹下要创建对应的Beans配置文件——Beans.xml。
<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
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<constructor-arg ref="spellChecker"/>
bean>
<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
bean>
beans>
依赖注入的第二种方法是通过 TextEditor 类的 Setter 方法,我们将创建 SpellChecker 实例,该实例将被用于调用 setter 方法来初始化 TextEditor 的属性。
package com.tutorialspoint;
public class TextEditor {
private SpellChecker spellChecker;
// a setter method to inject the dependency.
public void setSpellChecker(SpellChecker spellChecker) {
System.out.println("Inside setSpellChecker." );
this.spellChecker = spellChecker;
}
// a getter method to return spellChecker
public SpellChecker getSpellChecker() {
return spellChecker;
}
public void spellCheck() {
spellChecker.checkSpelling();
}
}
Bean.xml的内容略有不同:
<bean id="textEditor" class="com.tutorialspoint.TextEditor">
<property name="spellChecker" ref="spellChecker"/>
bean>
<bean id="spellChecker" class="com.tutorialspoint.SpellChecker">
bean>
唯一的区别就是在基于构造函数注入中,我们使用的是〈bean〉标签中的〈constructor-arg〉元素,而在基于设值函数的注入中,我们使用的是〈bean〉标签中的〈property〉元素
因此,DI 主要有两种变体和下面的两个子章将结合实例涵盖它们:
| 序号 | 依赖注入类型 & 描述 |
|---|---|
| 1 | Constructor-based dependency injection当容器调用带有多个参数的构造函数类时,实现基于构造函数的 DI,每个代表在其他类中的一个依赖关系。 |
| 2 | Setter-based dependency injection基于 setter 方法的 DI 是通过在调用无参数的构造函数或无参数的静态工厂方法实例化 bean 之后,容器调用 beans 的 setter 方法来实现的。 |
你可以混合这两种方法,基于构造函数和基于 setter 方法的 DI,然而使用有强制性依存关系的构造函数和有可选依赖关系的 setter是一个好的做法。
PS:
参考:
依赖注入:可以使用元素来声明 bean 和通过使用 XML 配置文件中的和元素来注入 。
Spring 容器可以在不使用和 元素的情况下==自动装配==相互协作的 bean 之间的关系,这有助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量。
下列自动装配模式,它们可用于指示 Spring 容器为来使用自动装配进行依赖注入。你可以使用元素的 autowire 属性为一个 bean 定义指定自动装配模式。
| 模式 | 描述 |
|---|---|
| no | 这是默认的设置,它意味着没有自动装配,你应该使用显式的bean引用来连线。你不用为了连线做特殊的事。在依赖注入章节你已经看到这个了。 |
| byName | 由属性名自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byName。然后尝试匹配,并且将它的属性与在配置文件中被定义为相同名称的 beans 的属性进行连接。 |
| byType | 由属性数据类型自动装配。Spring 容器看到在 XML 配置文件中 bean 的自动装配的属性设置为 byType。然后如果它的类型匹配配置文件中的一个确切的 bean 名称,它将尝试匹配和连接属性的类型。如果存在不止一个这样的 bean,则一个致命的异常将会被抛出。 |
| constructor | 类似于 byType,但该类型适用于构造函数参数类型。如果在容器中没有一个构造函数参数类型的 bean,则一个致命错误将会发生。 |
| autodetect(3.0版本不支持) | Spring首先尝试通过 constructor 使用自动装配来连接,如果它不执行,Spring 尝试通过 byType 来自动装配。 |
可以使用 byType 或者 constructor 自动装配模式来连接数组和其他类型的集合。
例子:
什么是byName和byType:
byName就是通过Bean的属性名称**(id或name)**自动装配。
byType就是通过Bean的Class类型来自动装配。使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
<bean id = "cat2" class="com.hello.pojo.Cat"/>
<bean id = "dog" class="com.hello.pojo.Dog"/>
<bean id = "people" class="com.hello.pojo.People" autowire="byName">
<property name="name" value="wzh"/>
</bean>
当一个bean节点带有 autowire byName的属性时。
(1)将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
(2)去spring容器中寻找是否有此字符串名称id的对象。
(3)如果有,就取出注入;如果没有,就报空指针异常。
| 限制 | 描述 |
|---|---|
| 重写的可能性 | 你可以使用总是重写自动装配的 和 设置来指定依赖关系。 |
| 原始数据类型 | 你不能自动装配所谓的简单类型包括基本类型,字符串和类。 |
| 混乱的本质 | 自动装配不如显式装配精确,所以如果可能的话尽可能使用显式装配。 |
参考:
使用 Spring 开发时,进行配置主要有两种方式:
从 Spring 2.5 开始就可以使用注解来配置依赖注入。而不是采用 XML 来描述一个 bean 连线,你可以使用相关类,方法或字段声明的注解,将 bean 配置移动到组件类本身。
在使用 java config 的过程当中,我们不可避免的会有各种各样的注解打交道,其中,我们使用最多的注解应该就是 @Autowired 注解了。这个注解的功能就是为我们注入一个定义好的 bean。
在可以使用基于注解的连线之前,我们将需要在我们的 Spring 配置文件中启用它。
一旦 被配置后,你就可以开始注解你的代码,表明 Spring 应该自动连接值到属性,方法和构造函数。让我们来看看几个重要的注解,并且了解它们是如何工作的:
| 序号 | 注解 & 描述 |
|---|---|
| 1 | @Required@Required 注解应用于 bean 属性的 setter 方法。 |
| 2 | @Autowired@Autowired 注解可以应用到 bean 属性的 setter 方法,非 setter 方法,构造函数和属性。 |
| 3 | @Qualifier通过指定确切的将被连线的 bean,@Autowired 和 @Qualifier 注解可以用来删除混乱。 |
| 4 | JSR-250 AnnotationsSpring 支持 JSR-250 的基础的注解,其中包括了 @Resource,@PostConstruct 和 @PreDestroy 注解。 |
可能会有这样一种情况,当你创建多个具有相同类型的 bean 时,并且想要用一个属性只为它们其中的一个进行装配,在这种情况下,你可以使用 @Qualifier 注解和 @Autowired 注解通过指定哪一个真正的 bean 将会被装配来消除混乱。
示例:
Profile.java
public class Profile {
@Autowired
@Qualifier("student1")
private Student student;
public Profile(){
System.out.println("Inside Profile constructor." );
}
}
}
配置文件 Beans.xml 的示例(有student1和student2):
<?xml version="1.0" encoding="UTF-8"?>
<beans 省略一堆
<context:annotation-config/>
<!-- Definition for profile bean -->
<bean id="profile" class="com.tutorialspoint.Profile">
</bean>
<!-- Definition for student1 bean -->
<bean id="student1" class="com.tutorialspoint.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>
<!-- Definition for student2 bean -->
<bean id="student2" class="com.tutorialspoint.Student">
<property name="name" value="Nuha" />
<property name="age" value="2"/>
</bean>
</beans>
参考:基于java代码的配置
参考:SpringBoot 配置类 @Configuration和@Bean的使用
基于 Java 的配置选项,可以使你在不用配置 XML 的情况下编写大多数的 Spring
1)什么是@Configuration 和 @Bean
@Configuration:
声明当前类是一个配置类,相当于 Spring 中的一个 XML 文件。可理解为用spring的时候xml里面的标签
带有 @Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源。
@Bean:
最简单可行的 @Configuration 类如下所示:
package com.tutorialspoint;
import org.springframework.context.annotation.*;
@Configuration
public class HelloWorldConfig {
@Bean
public HelloWorld helloWorld(){
return new HelloWorld();
}
}
上面的代码将等同于下面的 XML 配置:
<beans>
<bean id="helloWorld" class="com.tutorialspoint.HelloWorld" />
</beans>
Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。
SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理。
2)@Configuration 和@Component 的区别?
两者都是将注释的内容,交给spring管理。 而@configuration注解类,会去扫描类中携带@bean的方法,然后默认去执行这个方法。
而@component 注解的类,只是将类交给spring管理,而不会去执行方法。 只有当使用@autowired或者@resource注入对象的时候,使用对象去调用方法的时候才会执行方法。
====:
3)@Bean 和 @Autowired的关系
@Bean 和 @Autowired 做了两件完全不同的事情:
参考:使用AOP
AOP是Aspect Oriented Programming,即面向切面编程。
而AOP是一种新的编程方式,它和OOP(Object Oriented Programming)不同,OOP把系统看作多个对象的交互,AOP把系统分解为不同的关注点,或者称之为切面(Aspect)。
背景:
要理解AOP的概念,我们先用OOP举例,比如一个业务组件BookService,它有几个业务方法:
OOP对每个业务方法,例如,createBook(),除了业务逻辑,还需要安全检查、日志记录和事务处理。
如果我们以AOP的视角来编写上述业务,可以依次实现:
然后,以某种方式,让框架来把上述3个Aspect以Proxy的方式“织入”到BookService中,这样一来,就不必编写复杂而冗长的Proxy模式。
如何把切面织入到核心逻辑中?这正是AOP需要解决的问题。换句话说,如果客户端获得了BookService的引用,当调用bookService.createBook()时,如何对调用方法进行拦截,并在拦截前后进行安全检查、日志、事务等处理,就相当于完成了所有业务功能。
Spring的AOP实现就是基于JVM的动态代理(运行期进行AOP织入)。由于JVM的动态代理要求必须实现接口,如果一个普通类没有业务接口,就需要通过CGLIB或者Javassist这些第三方库实现。
AOP技术看上去比较神秘,但实际上,它本质就是一个动态代理,让我们把一些常用功能如权限检查、日志、事务等,从每个业务方法中剥离出来。
需要特别指出的是,AOP对于解决特定问题,例如事务管理非常有用,这是因为分散在各处的事务代码几乎是完全相同的,并且它们需要的参数(JDBC的Connection)也是固定的。
另一些特定问题,如日志,就不那么容易实现,因为日志虽然简单,但打印日志的时候,经常需要捕获局部变量,如果使用AOP实现日志,我们只能输出固定格式的日志,因此,使用AOP时,必须适合特定的场景。
在AOP编程中,我们经常会遇到下面的概念:
其实,我们不用关心AOP创造的“术语”,只需要理解AOP本质上只是一种代理模式的实现方式,在Spring的容器中实现AOP特别方便。
虽然Spring容器内部实现AOP的逻辑比较复杂(需要使用AspectJ解析注解,并通过CGLIB实现代理类),但我们使用AOP非常简单,一共需要三步:
@Component和@Aspect;@Configuration类上标注@EnableAspectJAutoProxy。1)定义一个LoggingAspect:
@Aspect
@Component
public class LoggingAspect {
// 在执行UserService的每个方法前执行:
@Before("execution(public * com.itranswarp.learnjava.service.UserService.*(..))")
public void doAccessCheck() {
System.err.println("[Before] do access check...");
}
// 在执行MailService的每个方法前后执行:
@Around("execution(public * com.itranswarp.learnjava.service.MailService.*(..))")
public Object doLogging(ProceedingJoinPoint pjp) throws Throwable {
System.err.println("[Around] start " + pjp.getSignature());
Object retVal = pjp.proceed();
System.err.println("[Around] done " + pjp.getSignature());
return retVal;
}
}
观察doAccessCheck()方法,我们定义了一个@Before注解,后面的字符串是告诉AspectJ应该在何处执行该方法,这里写的意思是:执行UserService的每个public方法前执行doAccessCheck()代码。
再观察doLogging()方法,我们定义了一个@Around注解,它和@Before不同,@Around可以决定是否执行目标方法,因此,我们在doLogging()内部先打印日志,再调用方法,最后打印日志后返回结果。
在LoggingAspect类的声明处,除了用@Component表示它本身也是一个Bean外,我们再加上@Aspect注解,表示它的@Before标注的方法需要注入到UserService的每个public方法执行前,@Around标注的方法需要注入到MailService的每个public方法执行前后。
2)紧接着,我们需要给@Configuration类加上一个@EnableAspectJAutoProxy注解:
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AppConfig {
...
}
@Aspect的Bean,然后根据每个方法的@Before、@Around等注解把AOP注入到特定的Bean中。拦截器类型:
顾名思义,拦截器有以下类型:
@Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;
@After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;
@AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;
@AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;
@Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。
使用AOP时,被装配的Bean最好自己能清清楚楚地知道自己被安排了。例如,Spring提供的@Transactional(事务)就是一个非常好的例子。如果我们自己写的Bean希望在一个数据库事务中被调用,就标注上@Transactional:
@Component
public class UserService {
// 有事务:
@Transactional
public User createUser(String name) {
...
}
// 无事务:
public boolean isValidName(String name) {
...
}
// 有事务:
@Transactional
public void updateUser(User user) {
...
}
}
或者直接在class级别注解,表示“所有public方法都被安排了”:
@Component
@Transactional
public class UserService {
...
}
JavaEE是Java Platform Enterprise Edition的缩写,即Java企业平台。JavaEE也不是凭空冒出来的,它实际上是完全基于JavaSE,只是多了一大堆服务器相关的库以及API接口。所有的JavaEE程序,仍然是运行在标准的JavaSE的虚拟机上的。
JavaEE最核心的组件就是基于Servlet标准的Web服务器,开发者编写的应用程序是基于Servlet API并运行在Web服务器内部的:
在JavaEE平台上,处理TCP连接,解析HTTP协议这些底层工作统统扔给现成的Web服务器去做,我们只需要把自己的应用程序跑在Web服务器上。为了实现这一目的,JavaEE提供了Servlet API,我们使用Servlet API编写自己的Servlet来处理HTTP请求,Web服务器实现Servlet API接口,实现底层功能:
最简单的Servlet:
// WebServlet注解表示这是一个Servlet,并映射到地址/:
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 设置响应类型:
resp.setContentType("text/html");
// 获取输出流:
PrintWriter pw = resp.getWriter();
// 写入响应:
pw.write("Hello, world!
");
// 最后不要忘记flush强制输出:
pw.flush();
}
}
另外,我们首先要找一个支持Servlet API的Web服务器来运行war文件。常用的服务器有:
实际上,类似Tomcat这样的服务器也是Java编写的,启动Tomcat服务器实际上是启动Java虚拟机,执行Tomcat的main()方法,然后由Tomcat负责加载我们的.war文件,并创建一个HelloServlet实例,最后以多线程的模式来处理HTTP请求。
因为我们编写的Servlet并不是直接运行,而是由Web服务器加载后创建实例运行,所以,类似Tomcat这样的Web服务器也称为Servlet容器。
开发Servlet时,推荐使用main()方法启动嵌入式Tomcat服务器并加载当前工程的webapp,便于开发调试,且不影响打包部署,能极大地提升开发效率。
Servlet就是一个能处理HTTP请求,发送HTTP响应的小程序,而发送响应无非就是获取PrintWriter,然后输出HTML。
用PrintWriter输出HTML比较痛苦,因为不但要正确编写HTML,还需要插入各种变量。如果想在Servlet中输出一个类似新浪首页的HTML,写对HTML基本上不太可能。
PrintWriter pw = resp.getWriter();
pw.write("");
pw.write("");
pw.write("Welcome, "
+ name + "!");
pw.write("");
pw.write("");
pw.flush();
更简单的输出HTML的办法——JSP
JSP是Java Server Pages的缩写,它的文件必须放到/src/main/webapp下,文件名必须以.jsp结尾,整个文件与HTML并无太大区别,但需要插入变量,或者动态输出的地方,使用特殊指令<% ... %>。
总结:
JSP是一种在HTML中嵌入动态输出的文件,它和Servlet正好相反,Servlet是在Java代码中嵌入输出HTML;
JSP可以引入并使用JSP Tag,但由于其语法复杂,不推荐使用;
JSP本身目前已经很少使用,我们只需要了解其基本用法即可。
UserServlet代码:
@WebServlet(urlPatterns = "/user")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 假装从数据库读取:
School school = new School("No.1 Middle School", "101 South Street");
User user = new User(123, "Bob", school);
// 放入Request中:
req.setAttribute("user", user);
// forward给user.jsp:
req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp);
}
}
我们把UserServlet看作业务逻辑处理,把User看作模型,把user.jsp看作渲染,这种设计模式通常被称为MVC:Model-View-Controller,即UserServlet作为控制器(Controller),User作为模型(Model),user.jsp作为视图(View),整个MVC架构如下:
总结:
@WebFilter(urlPatterns = "/*")
public class EncodingFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
System.out.println("EncodingFilter:doFilter");
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
}
Filter是一种对HTTP请求进行预处理的组件,它可以构成一个处理链,使得公共处理代码能集中到一起;
Filter适用于日志、登录检查、全局设置等;
设计合理的URL映射可以让Filter链更清晰。
修改请求、响应:借助HttpServletRequestWrapper,我们可以在Filter中实现对原始HttpServletRequest、HttpServletResponse的的修改。
通过Listener我们可以监听Web应用程序的生命周期,获取HttpSession等创建和销毁的事件;
ServletContext是一个WebApp运行期的全局唯一实例,可用于设置和共享配置信息。
Spring虽然都可以集成任何Web框架,但是,Spring本身也开发了一个MVC框架,就叫Spring MVC。这个MVC框架设计得足够优秀以至于我们已经不想再费劲去集成类似Struts这样的框架了
使用Spring MVC时,整个Web应用程序按如下顺序启动:
启动后,浏览器发出的HTTP请求全部由DispatcherServlet接收,并根据配置转发到指定Controller的指定方法处理。
@RestController
@RequestMapping("/api")
public class ApiController {
@Autowired
UserService userService;
@GetMapping("/users")
public List<User> users() {
return userService.getUsers();
}
@GetMapping("/users/{id}")
public User user(@PathVariable("id") long id) {
return userService.getUserById(id);
}
@PostMapping("/signin")
public Map<String, Object> signin(@RequestBody SignInRequest signinRequest) {
try {
User user = userService.signin(signinRequest.email, signinRequest.password);
return Map.of("user", user);
} catch (Exception e) {
return Map.of("error", "SIGNIN_FAILED", "message", e.getMessage());
}
}
public static class SignInRequest {
public String email;
public String password;
}
}
使用@RestController可以方便地编写REST服务,Spring默认使用JSON作为输入和输出。
要控制序列化和反序列化,可以使用Jackson提供的@JsonIgnore和@JsonProperty注解。
在Spring MVC中,DispatcherServlet只需要固定配置到web.xml中,剩下的工作主要是专注于编写Controller。
可以自己编写一个EncodingFilter,也可以直接使用Spring MVC自带的一个CharacterEncodingFilter。配置Filter时,只需在web.xml中声明即可
如果只基于Spring MVC开发应用程序,还可以使用Spring MVC提供的一种功能类似Filter的拦截器:Interceptor。和Filter相比,Interceptor拦截范围不是后续整个处理流程,而是仅针对Controller拦截
一个Interceptor必须实现HandlerInterceptor接口,可以选择实现preHandle()、postHandle()和afterCompletion()方法。
preHandle()是Controller方法调用前执行
postHandle()是Controller方法正常返回后执行,
afterCompletion()无论Controller方法是否抛异常都会执行,参数ex就是Controller方法抛出的异常(未抛出异常是null)。
参考:
java注解:
java的注解(Annotation),实际上是给语法元素打一个标记。比如你可以给一个函数打一个标记,给一个类打一个标记等等。Java只保证记录这个标记,但是不会主动根据这给标记做任何事。
如果你不用Spring框架的话,不会有任何事情发生,直接访问这个字段就是空。当如果你配置了合适的处理流程,而这个流程就会根据有没有这个标记干活。比如你要求Spring “Auto Scan” 并且注入依赖,这个处理过程会用反射去读哪些元素被做了某个特定标记。没有标记就不理,有标记就注入。
Python装饰器:
总结:
第一点:对代码块的影响
第二点:共通处
第三点:从用途看
第四点:从原理看
参考:
| Tables | 过滤器(Filter) | 监听器(Listener) | 拦截器(Interceptor) |
|---|---|---|---|
| 关注的点 | wed请求 | 系统级别参数、对象 | Action(部分web请求) |
| 如何实现的 | 函数回调 | 事件 | Java反射机制(动态代理) |
| 应用场景 | 设置字符编码 | 统计网站在线人数 | 拦截未登录用户 |
| URL级别的权限访问控制 | 清除过期session | 审计日志 | |
| 过滤敏感词汇 | |||
| 压缩响应信息 | |||
| 是否依赖servlet容器 | 依赖 | 不依赖 | |
| Serverlet提供的支持 | Filter接口 | ServletContextListener抽象接口 | Action(部分web请求) |
| HttpSessionListener抽象接口 | |||
| Spring提供的支持 | HandlerinterceptorAdapter类 | ||
| HandlerInterceptor接口 | |||
| 级别 | 系统级 | 系统级 | 非系统级 |
注意:拦截器的对象只能是实现了接口的类,而不能拦截URL这种链接。
参考:
Filter也称之为过滤器,它是Servlet技术中最实用的技术,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。
它主要用于对用户请求HttpServletRequest进行预处理,也可以对响应HttpServletResponse进行后处理。使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MPbn2t50-1662295786941)(images/Spring学习.assets/1444343-20190809135320083-426474192.png)]
在HttpServletRequest到达 Servlet 之前,拦截客户的HttpServletRequest 。根据需要检查HttpServletRequest,也可以修改HttpServletRequest 头和数据。
在HttpServletResponse到达客户端之前,拦截HttpServletResponse 。根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据
Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter。通过Filter技术,开发人员可以实现用户在访问某个目标资源之前,对访问的请求和响应进行拦截,Filter接口源代码:
public abstract interface Filter{
public abstract void init(FilterConfig paramFilterConfig) throws ServletException;
public abstract void doFilter(ServletRequest paramServletRequest, ServletResponse paramServletResponse, FilterChain
paramFilterChain) throws IOException, ServletException;
public abstract void destroy();
}
Filter接口中有一个doFilter方法,当开发人员编写好Filter,并配置对哪个web资源进行拦截后,Web服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法,因此,在该方法内编写代码可达到如下目的
Filter开发分为2步:
过滤器范例:
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* @author yangcq
* @description 过滤器Filter的工作原理
*/
public class FilterTest implements Filter{
public void destroy() {
System.out.println("----Filter销毁----");
}
public void doFilter(ServletRequest request, ServletResponse response,FilterChain filterChain) throws IOException, ServletException {
// 对request、response进行一些预处理
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
System.out.println("----调用service之前执行一段代码----");
filterChain.doFilter(request, response); // 执行目标资源,放行
System.out.println("----调用service之后执行一段代码----");
}
public void init(FilterConfig arg0) throws ServletException {
System.out.println("----Filter初始化----");
}
}
Web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name></display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<!--配置过滤器-->
<filter>
<filter-name>FilterTest</filter-name>
<filter-class>com.yangcq.filter.FilterTest</filter-class>
</filter>
<!--映射过滤器-->
<filter-mapping>
<filter-name>FilterTest</filter-name>
<!--“/*”表示拦截所有的请求 -->
/*
参考:
监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行。
监听器可以用来检测网站的在线人数,统计网站的访问量等等!
监听器涉及三个组件:事件源,事件对象,事件监听器
当事件源发生某个动作的时候,它会调用事件监听器的方法,并在调用事件监听器方法的时候把事件对象传递进去。
我们在监听器中就可以通过事件对象获取得到事件源,从而对事件源进行操作!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W32oyhha-1662295786943)(images/Spring学习.assets/webp)]
可以看参考的例子。还是比较好理解的。
事件源要与监听器有关系,就得注册监听器【提供方法得到监听器对象】
触发事件源的事件,实际会提交给监听器对象处理,并且把事件对象传递过去给监听器。
在Servlet规范中定义了多种类型的监听器,它们用于监听的事件源分别ServletContext, HttpSession和ServletRequest这三个域对象
HttpSessionListener、ServletContextListener、ServletRequestListener分别监控着Session、Context、Request对象的创建和销毁
示例【统计当前在线人数】:
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* @description HttpSessionListener监听器实现统计网站在线人数的功能
*/
public class SessionListener implements HttpSessionListener{
public static int TOTAL_ONLINE_USERS = 0;
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
TOTAL_ONLINE_USERS = (Integer) servletContext.getAttribute("TOTAL_ONLINE_USERS");
// 如果用户退出,TOTAL_ONLINE_USERS自减1
if(TOTAL_ONLINE_USERS == 0){
servletContext.setAttribute("TOTAL_ONLINE_USERS", 1);
}
else{
TOTAL_ONLINE_USERS--;
servletContext.setAttribute("TOTAL_ONLINE_USERS", TOTAL_ONLINE_USERS);
}
}
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
ServletContext servletContext = httpSessionEvent.getSession().getServletContext();
TOTAL_ONLINE_USERS = (Integer) servletContext.getAttribute("TOTAL_ONLINE_USERS");
// 如果用户登录,TOTAL_ONLINE_USERS自增1
if(TOTAL_ONLINE_USERS == 0){
servletContext.setAttribute("TOTAL_ONLINE_USERS", 1);
}
else{
TOTAL_ONLINE_USERS++;
servletContext.setAttribute("TOTAL_ONLINE_USERS", TOTAL_ONLINE_USERS);
}
}
}
参考:
java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者:
大部分时候,拦截器方法都是通过代理的方式来调用的。
过滤器可以简单的理解为“取你所想取”,过滤器关注的是web请求;拦截器可以简单的理解为“拒你所想拒”,拦截器关注的是方法调用,比如拦截敏感词汇。
1.拦截器是基于java反射机制来实现的,而过滤器是基于函数回调来实现的。(有人说,拦截器是基于动态代理来实现的)
2.拦截器不依赖servlet容器,过滤器依赖于servlet容器。
3.拦截器只对Action起作用,过滤器可以对所有请求起作用。
4.拦截器可以访问Action上下文和值栈中的对象,过滤器不能。
5.在Action的生命周期中,拦截器可以多次调用,而过滤器只能在容器初始化时调用一次。
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MyInterceptor implements HandlerInterceptor {
/**
* 该方法是在执行执行servlet的 service方法之前执行的
* 即在进入controller之前调用
* @return 如果返回true表示继续执行下一个拦截器的PreHandle方法;如果没有拦截器了,则执行controller
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
String url = request.getRequestURI();
System.out.println("进入Controller之前");
if(url.indexOf("login") >= 0){
return true;
}
String username = request.getParameter("username");
if(username == null){
return false;
}
return true;
}
/**
*在执行完controller之后,返回视图之前执行,我们可以对controller返回的结果做处理
* 执行顺序:先执行最后一个拦截器的postHandle方法,一次向前
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception{
System.out.println("解析视图之前.....");
}
/**
* 整个请求结束之后,即返回视图之后执行
*该方法需要同一拦截器的preHandle返回true时执行,
* 如果该拦截器preHandle返回false,则该拦截器的afterCompletion不执行
* 执行顺序:先执行最后一个返回true的拦截器的afterCompletion,在依次向前
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception{
System.out.println("视图解析完成...");
}
}
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor interceptor;
public void addInterceptors(InterceptorRegistry registry) {
//addPathPatterns指定拦截器要拦截的路径
//excludePathPatterns指定拦截器不拦截的路径
registry.addInterceptor(interceptor).addPathPatterns("/**").excludePathPatterns("/logout");
}
}
@RestController
public class LoginController {
@RequestMapping("/login")
public String intercept(){
System.out.println("Controller执行中....");
return "Hello Interceptor!";
}
@RequestMapping("/logout")
public String logout(){
System.out.println("注销中....");
return "logouting.....!";
}
}
先访问login,测试结果如下:
进入Controller之前
Controller执行中....
解析视图之前.....
视图解析完成...
访问logout:
注销中....
过滤器可以简单的理解为“取你所想取”,过滤器关注的是web请求;拦截器可以简单的理解为“拒你所想拒”,拦截器关注的是方法调用,比如拦截敏感词汇。
1.过滤器(Filter):所谓过滤器顾名思义是用来过滤的,Java的过滤器能够为我们提供系统级别的过滤,也就是说,能过滤所有的web请求,这一点,是拦截器无法做到的。在Java Web中,你传入request,response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet进行业务逻辑,比如过滤掉非法url(不是login.do的地址请求,如果用户没有登陆都过滤掉),或者在传入servlet前统一设置字符集,或者去除掉一些非法字符(聊天室经常用到的,一些骂人的话)。filter 流程是线性的,url传来之后,检查之后,可保持原来的流程继续向下执行,被下一个filter, servlet接收。
2.监听器(Listener):Java的监听器,也是系统级别的监听。监听器随web应用的启动而启动。Java的监听器在c/s模式里面经常用到,它会对特定的事件产生一个处理。监听在很多模式下用到,比如说观察者模式,就是一个使用监听器来实现的,在比如统计网站的在线人数。Servlet监听器用于监听一些重要事件的发生,监听器对象可以在事情发生前、发生后可以做一些必要的处理。
3.拦截器(Interceptor):java里的拦截器提供的是非系统级别的拦截,也就是说,就覆盖面来说,拦截器不如过滤器强大,但是更有针对性。Java中的拦截器是基于Java反射机制实现的,更准确的划分,应该是基于JDK实现的动态代理。它依赖于具体的接口,在运行期间动态生成字节码。拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。在AOP中,拦截器用于在某个方法或者字段被访问之前,进行拦截然后再之前或者之后加入某些操作。java的拦截器主要是用在插件上,扩展件上比如 Hibernate Spring Struts2等,有点类似面向切片的技术,在用之前先要在配置文件即xml,文件里声明一段的那个东西。