21up

21up 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 该用户太懒什么也没留下

个人动态

21up 发布了文章 · 10月14日

工作、学习中遇到的bug-New

1.No converter found for return value of type: class com.wen.mall.tiny.common.api.CommonResult

在测试mall-tiny-01的数据查询时,报错,原因CommonResult的属性没有setter、getter方法
https://www.jianshu.com/p/e5d8ba73b13b

2.Consider defining a bean of type 'XXX.Dao' in your configuration

Mybatis的配置文件中应该添加自定义的xml文件扫描路径

@MapperScan({"com.wen.mall.tiny.mbg.mapper","com.wen.mall.tiny.dao"})

3.Invalid bound statement (not found)
配置文件出错,classpath后没有空格

mybatis:
  mapper-locations:
    - classpath:mapper/*.xml
    - claaspath*:com/**/mapper/*.xml

4.Non-managed pom.xml file found

https://jingyan.baidu.com/art...
5.远程拉取gitlab代码提示early EOF The remote end hung up unexpectedly index-pack failed RPC failed; curl 18 transfer closed
重新拉取即可,下面网址并未参考
https://blog.csdn.net/u011250...
6.idea从gitlab拉取代码
https://blog.csdn.net/wudinan...
7.java.rmi.server.ExportException: Port already in use: 1099;
修改JMX端口即可
8.mybatis plus 使用
https://baomidou.com/
https://www.csdn.net/gather_2...
9.Error:java: Compilation failed: internal java compiler error
https://blog.csdn.net/wo54107...
10.Assert.notNull
https://www.cnblogs.com/mingf...
11.测试父类子类有同名方法时如何调用
https://blog.csdn.net/qq_2180...
github.work_demo
12.rocketmq报错service not available now, maybe disk full
https://blog.csdn.net/weixin_...
https://jingyan.baidu.com/art...
13.Lambda expressions are not supported at language level '1.5'
https://blog.csdn.net/xilin66...
14.IDEA热部署
https://blog.csdn.net/qq_1614...
15.Handler processing failed; nested exception is java.lang.NoSuchMethodError
https://blog.csdn.net/hello_w...
16.java Timer
https://blog.csdn.net/qq_3949...
17.try加()什么意思
https://blog.csdn.net/llkoio/...
18.CopyOnWriteArraySet
https://www.cnblogs.com/xiaol...
19.redis数据结构
https://www.cnblogs.com/haopr...
20.jedis
https://www.cnblogs.com/tengf...
21.utils层与tools层,manager层与service层,helper层概念理解https://blog.csdn.net/cchchun...
22.xxl-job
https://blog.csdn.net/qq92486...
https://www.xuxueli.com/xxl-j...

  1. @Generated

https://wenda.game234.com/q/4...
24.拦截器~~~~
实现拦截器的几种方式,webConfig
25.response.sendRedirect
https://blog.csdn.net/qq_3723...
26.x-requested-with
https://www.baidu.com/s?ie=UT...
27.WebMvcConfigurer、WebMvcConfigurerAdapter
https://blog.csdn.net/zhangpo...
28.No active profile set, falling back to default profiles: default
https://www.cnblogs.com/jpfss...
29.Disconnected from the target VM, address
https://www.cnblogs.com/javaw...
30.Process finished with exit code 0
https://blog.csdn.net/doudou1...
31.方法引用
https://www.cnblogs.com/wuhen...
32.Set转List~~~~
https://blog.csdn.net/kye0559...
33.Function.identity()
https://www.jianshu.com/p/cd6...
34.序列化
https://blog.csdn.net/tree_if...

查看原文

赞 0 收藏 0 评论 0

21up 发布了文章 · 10月8日

工作、学习中遇到的bug

  1. No converter found for return value of type: class com.wen.mall.tiny.common.api.CommonResult

在测试mall-tiny-01的数据查询时,报错,原因CommonResult的属性没有setter、getter方法
https://www.jianshu.com/p/e5d8ba73b13b

  1. Consider defining a bean of type 'XXX.Dao' in your configuration

Mybatis的配置文件中应该添加自定义的xml文件扫描路径

@MapperScan({"com.wen.mall.tiny.mbg.mapper","com.wen.mall.tiny.dao"})
  1. Invalid bound statement (not found)

配置文件出错,classpath后没有空格

mybatis:
  mapper-locations:
    - classpath:mapper/*.xml
    - claaspath*:com/**/mapper/*.xml
  1. Non-managed pom.xml file found

https://jingyan.baidu.com/art...

  1. 远程拉取gitlab代码提示early EOF The remote end hung up unexpectedly index-pack failed RPC failed; curl 18 transfer closed

重新拉取即可,下面网址并未参考
https://blog.csdn.net/u011250...

  1. idea从gitlab拉取代码

https://blog.csdn.net/wudinan...

  1. java.rmi.server.ExportException: Port already in use: 1099;

修改JMX端口即可

  1. mybatis plus 使用

https://baomidou.com/
https://www.csdn.net/gather_2...

  1. Error:java: Compilation failed: internal java compiler error

https://blog.csdn.net/wo54107...
10.Assert.notNull
https://www.cnblogs.com/mingf...
11.测试父类子类有同名方法时如何调用
https://blog.csdn.net/qq_2180...
github.work_demo
12.rocketmq报错service not available now, maybe disk full
https://blog.csdn.net/weixin_...
https://jingyan.baidu.com/art...
13.Lambda expressions are not supported at language level '1.5'
https://blog.csdn.net/xilin66...

查看原文

赞 0 收藏 0 评论 0

21up 赞了文章 · 9月19日

Spring Ioc Bean的注册与注入方式

1、背景

IoC(Inversion of Control)控制反转,IoC是一种通过描述来生成或获取对象的技术。每一个需要管理的对象称为Spring Bean,而Spring管理这些Bean的容器称为Spring IoC容器,也就是我们所说的Spring应用程序上下文ApplicationContext。

IOC容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。交由Spring容器统一进行管理,从而实现松耦合。

  1. 容器启动,创建和初始化IoC 容器
  2. 扫描包下所有class,通过反射解析class类的信息,包括注解信息
  3. 基于反射实例的对象,对其进行封装
  4. 将实例的对象放入集合中保存
  5. 可以通过getBean获取集合中对象

2、@SpringBootApplication

@SpringBootApplication开启了Spring的组件扫描和SpringBoot自动配置功能。实际上它是一个复合注解,包含3个重要的注解: @SpringBootConfiguration、@ComponentScan、@EnableAutoConfiguration

SpringBoot早期的版本中,需要在入口类同时添加这3个注解,但从SpringBoot1.2.0开始,只要在入口类添加@SpringBootApplication注解即可。我们如果想理解它的含义,可以从通过分析这3个注解开始。

2.1、@SpringBootConfiguration

@SpringBootConfiguration 是对 @Configuration 的简单封装,二者功能上没有太大差异。后者我们比较熟悉,所以 @SpringBootConfiguration 可以理解为配置类的注解,会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中。

2.2、@ComponentScan

@ComponentScan主要就是定义扫描的路径,从中找出标识了需要装配的类自动装配到spring的bean容器中。默认扫描路径是当前路径,因为启动类是在项目的根路径,所以启动类中的该注解,默认扫描的当前项目路径。

那么怎么找出需要装配的类呢?我们知道正常装载类,是要在配置类下注册Bean的。但是我们日常使用的@Controller、@Service、@Repository、@Component、@Configuration却可以自动装载,就是因为@ComponentScan这个注解。@Component是个特殊的注解,它可以被@ComponentScan扫描装载,如果查看@Controller、@Service、@Repository、@Configuration等注解的源码就会发现,它们都继承@Component,因此同样也可以自动被扫描装载进容器中。

@ComponentScan注解的常用属性包括:
  • value/basePackages:定义扫描的路径。
  • includeFilters:加入扫描路径下,没有继承@Component注解的类加入spring容器(例如:mybatis中的mapper类就是中BeanFactoryPostProcessor接口实现方法中加入includeFilters)。
  • excludeFilters:过滤出不用加入spring容器的类。

2.3、@EnableAutoConfiguration

项目开发时,经常通过maven等方式引入第三方jar包,那么也同样期望能够将第三方jar包中的Bean也加载进当前项目的Bean容器中。前面说过@ComponentScan可以将定义扫描路径下的Bean装载进容器中,因此可以手动的在@ComponentScan注解的value属性中,填上所有第三方jar包的扫描路径。理论上是这样的,但应该没有人这么干吧?一个项目依赖几十个jar包,那要配置多少扫描路径?

因此一般有另外的两种方法来实现需求,一般我们开发一个供调用的jar包应用(如starter),主要依托本身的配置类来注入Bean(@Configuration+@Bean 或 @ComponentScan+@Component),因此只要保证第三方应用的配置类能加载进当前的Bean容器即可。

@Import 和 spring.factories
  1. 第三方应用,定义一个注解(一般是Enable开头),通过 @Import 注解注入配置类。并且当前项目的配置类(建议启动类)中加上该注解。
//配置类
@Configuration
@EnableConfigurationProperties({ SecurityProperty.class})
@ComponentScan(basePackages = {"com.smec.mpaas.unicorn"})
public class UnicornAutoConfig { }
//注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Import({UnicornAutoConfig.class})
public @interface EnableUnicorn {
}
  1. 在第三方应用/META-INF 目录下创建 spring.factories 文件,并定义需要加载的配置类路径。当前项目的 @EnableAutoConfiguration
// mybatis 的 spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

我们重点谈谈第二种,因为用到了@EnableAutoConfiguration,注解的定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class[] exclude() default {};
    String[] excludeName() default {};
}

@EnableAutoConfiguration实现的关键在于引入了AutoConfigurationImportSelector,其核心逻辑为selectImports方法,借助AutoConfigurationImportSelector,它可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。

xxxAutoConfiguration条件注解

当springboot扫描到@EnableAutoConfiguration注解时则会将spring-boot-autoconfigure.jar/META-INF/spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value里的所有xxxConfiguration类加载到IOC容器中。spring.factories文件里每一个xxxAutoConfiguration文件一般都会有下面的条件注解:

  • @ConditionalOnClass : classpath中存在该类时起效
  • @ConditionalOnMissingClass : classpath中不存在该类时起效
  • @ConditionalOnBean : DI容器中存在该类型Bean时起效
  • @ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
  • @ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
  • @ConditionalOnExpression : SpEL表达式结果为true时
  • @ConditionalOnProperty : 参数设置或者值一致时起效
  • @ConditionalOnResource : 指定的文件存在时起效
  • @ConditionalOnJndi : 指定的JNDI存在时起效
  • @ConditionalOnJava : 指定的Java版本存在时起效
  • @ConditionalOnWebApplication : Web应用环境下起效
  • @ConditionalOnNotWebApplication : 非Web应用环境下起效
AutoConfigurationImportSelector类的逻辑

SpringBoot中EnableAutoConfiguration实现的关键在于引入了AutoConfigurationImportSelector,其核心逻辑为selectImports方法,逻辑大致如下:

  1. 从配置文件META-INF/spring.factories加载所有可能用到的自动配置类;
  2. 去重,并将exclude和excludeName属性携带的类排除;
  3. 过滤,将满足条件(@Conditional)的自动配置类返回;

3、Bean注册

IoC容器中主要就是管理Bean,那么在这之前,最重要的就是如何将自定义的Bean注册进IoC容器中。常见的有两种方式:(1)配置@Component注解,或者包含@Component的注解;(2)@Bean+@Configuration的组合。

3.1、@Component

@Component倾向于组件扫描和自动装配。前文说过了,主要是配合@ComponentScan注解,该注解会扫描指定路径下包含@Component注解的类,并将其注册进IoC容器。spring中有很多其他的注解也是依托@Component实现的,如:@Controller、@Service、@Repository、@Configuration等。

注册进IoC容器后,Bean的名字取决于两种情况:
  1. 如果在注解中指定了value,则为value的值
  2. 如果在注解中没有指定value,默认是类名的第一个字母小写命名(如:类名 UserDepartMerge,bean名 userDepartMerge)
@Component只有一个注解属性:
  • value:在代码中定义了别名,为bean起一个名字。

3.2、@Bean

@Bean修饰的方法,必须中配置类有@Configuration的类)中定义。在方法上打上注解@Bean,方法返回我们要创建的对象,即表示声明该方法返回的实例是受Spring管理的Bean。

相比较于@Component而言,@Bean的方式更加灵活。假如你要引入第三方库,可是如果你没有源代码,也就无法在其上添加@Component,自动设置也就无从下手,但@Bean会返回一个被spring认可的Bean。

@Configuration
public class BeanConfig {

    @Bean(initMethod = "initMethod",destroyMethod = "destroyMethod")
    public User user(){
        return new User();
    }
}
注册进IoC容器后,Bean的名字取决于两种情况:
  1. 如果在注解中指定了name,则为name属性的值
  2. 如果没有指定name,默认是方法名(示例中即:user)
@Bean的注解属性解析:
  • value/name:name和value两个属性是相同的含义的,在代码中定义了别名。为bean起一个名字,如果默认没有写该属性,那么就使用方法的名称为该bean的名称。
  • autowire:装配方式 有三个选项Autowire.NO (默认设置)/Autowire.BY_NAME/Autowire.BY_TYPE。指定bean的装配方式,根据名称和根据类型装配,一般不设置,采用默认即可。
  • initMethod:bean的初始化方法,直接指定方法名称(bean所对应类中,某个实例方法的名称)即可,不用带括号。
  • destroyMethod:bean的销毁方法,在调用IoC容器的close()方法时,会执行到该属性指定的方法。不过,只是单实例的bean才会调用该方法,如果是多实例的情况下,不会调用该方法。

4、Bean注入

bean的完全初始化过程比较复杂,简单来说:先通过构造方法生成一个实例,其次再初始化实例中属性的值。当bean已经被加载进IoC容器后,在有些开发过程中如果需要用到一些bean的实例,就要对其注入。
spring发展中,流行过三种注入方式,当有多个bean注入时,如果存在依赖关系,“谁先加载,谁后加载”有时就很重要。这和下面三种注入方式也有关系,一个经典的案例就是spring的循环依赖问题,这个后面会有单独一篇文章介绍,下文先打好基础。

4.1、基于字段注入

这是早期最常见的注入方式,借助 @Autowired 或 @Resource 来注入,注解可以加在字段上。

byName 和 byType

spring在获取bean时,有两种依赖途径:byName 和 byType。之前在介绍bean的注册时,一直强调bean被加载进IoC容器后命名,byName就是根据bean的名字去IoC容器中寻找bean注入。而byType是根据被注册bean的类型来寻找的,类型即bean对应的类或接口。

@Resource 注解

@Resource注解的路径是 javax.annotation;,它是属于J2EE的注解。
@Resource默认是按照byName方式注入的。
@Resource有两个重要属性:name,type:

  • 如果只指定了name属性,则按照byName方式注入;
  • 如果只指定了type属性,则按照byType方式注入;
  • 如果两个属性都没指定,则默认按照byName方式注入;
  • 如果两个属性都指定了,则要求按照byNamebyType两种方式都匹配,才会注入。

示例代码:

@Component
public class User {
    @Resource(name = "type",type = Type.class)
    private Type type;

}
@Autowired 注解

@Autowired注解的路径是org.springframework.beans.factory.annotation;,它是属于Spring的注解。
@Autowired并不能指定注入方式是byName还是byType,默认只能按照byType方式注入。如果想通过byName方式注入,得配合@Qualifier注解一起使用:

  • 默认按照byType方式注入;
  • 配合@Qualifier注解使用,可以实现byName方式注入;
  • 如果按照byType匹配到多个bean,则会自动将属性名视为bean的名称,通过byName方式匹配注入。

示例代码中,有个Action的接口,两个实现类Human、Dog。因为Action类型能匹配两个bean,根据属性名dog会匹配到dog:

//Action
@Component
public interface Action {
    String run();
}
//Human
@Component
public class Human implements Action{
    @Override
    public String run() {
        return "Human run!";
    }
}
//Dog
@Component
public class Dog implements Action{
    @Override
    public String run() {
        return "Dog run!";
    }
}
//Controller
@RestController
public class DemoController {
    @Autowired
    private Action dog;

    @GetMapping("/demo")
    public String demo() {
        return dog.run();
    }
}

4.2、基于setter注入

基于setter的注入,就是添加属性的setter方法。setter方法上的@Autowired注解可以加,也可以不加,不会影响功能实现,加了可能只是有助于区分其他普通的setter方法。

@Component
public class User {
    private Type type;

    //@Autowired
    public void setType(Type type) {
        this.type = type;
    }
}

上文代码中,User 和 Type 两个类都加上了@Component的注解,即都已经被注入IoC容器中。User在创建实例的时候只执行自身的构造方法即可,但在初始化type属性数据时会调用set方法,会注入Type类的实例。所以会先执行User构造方法,再执行Type构造方法。

相较于字段注入的方式来说,改善了部分问题,但不明显。而且如果需要注入的bean多,settter方法就要写一堆,代码结构很糟糕。所以一般更推荐构造器注入

4.3、构造器注入

下列代码是不是很熟悉?Angular中依赖注入组件,就是这种写法。在User创建实例时,就依赖于Type的实例。因此先执行Type的构造方法,再执行User的构造方法。

@Component
public class User {
    private final Type type;

    public User(Type type) {
        this.type = type;
    }
}

这个构造器注入的方式啊,能够保证注入的组件不可变(final),并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。

4.4、比较

基于字段注入的方式,会使代码看起来更整洁,但是对于空指针等问题无法及时避免。基于构造器注入的方式,貌似是前者短板的最好替代。我不建议考虑setter注入方式。

我们当在字段上使用@Autowired时,IDE一般都会给我们一个警告 Field injection is not recommended,不推荐使用字段注入。这个提示是spring framerwork 4.0以后开始出现的,spring 4.0开始就不推荐使用属性注入,改为推荐构造器注入setter注入

@Autowired因为默认是byType注入,经常会因为注入的类型是接口,在初始化属性数据时,却发现IoC容器中并没有加载实现类。然后就出现了空指针的错误,包括还有可能隐藏的循环依赖错误。因此在很多的开发规范中,都推荐用构造器注入,替代@Autowired

但为啥对于在字段上使用@Resource,IDE不警告呢?个人认为毕竟@Resource的属性很多,可以定制化实现很多功能,这些灵活的功能,构造器注入方式替代不了。不像@Autowired那么鸡肋。

5、Aware属性注入

Spring中有很多继承于aware中的接口,下面列出了一部分:

  • ApplicationContextAware
  • ApplicationEventPublisherAware
  • BeanClassLoaderAware
  • BeanFactoryAware
  • BeanNameAware
  • EnvironmentAware
  • ImportAware
  • MessageSourceAware
  • NotificationPublisherAware
  • ResourceLoaderAware
  • ServletConfigAware
  • ServletContextAware

aware,英文翻译是“知道的,意识到的”。可以理解成,如果实现了这些 xxxAware的接口,就能感知到被修饰的xxx属性。如:实现BeanNameAware接口,能获取到当前bean的名称。实现ApplicationContextAware接口,能获取到ApplicationContex。

这些接口中都有且只有一个去掉接口名中的Aware后缀的设置方法,例如BeanNameAware接口只有一个void setBeanName(String var1)的方法。

所以可以理解成,这些Aware接口的作用,是让开发者实现相应的 属性注入。如下例中,通过单例模式实现一个获取全局ApplicationContext的方法,可以手动获取IoC容器和容器中的bean。

@Component
public class ApplicationContextAwareImpl implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext curApplicationContext) {
        if (applicationContext == null) {
            applicationContext = curApplicationContext;
        }
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

}
查看原文

赞 1 收藏 0 评论 0

21up 发布了文章 · 9月17日

RabbitMQ的五种工作模式

五种消息模式

简单模式

包含一个生产者、一个消费者和一个队列,生产者向队列发送消息,消费者从队列中获取并消费消息。

示意图:
image.png

工作模式

工作模式是指多个相互竞争的消费者发送消息的模式,它包含一个生产者、两个消费者和一个队列。两个消费者同时绑定到一个队列上去,当消费者获取消息处理耗时任务时,空闲的消费者从队列中获取b并消费队列。

示意图:
image.png

发布/订阅模式

指同时向多个消费者发送消息的模式(类似广播),它包括一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的队列上,两个队列绑定到交换机上去,生产者通过发送消息到交换机,所有消费者接收并消费消息。

示意图:
image.png

路由模式

路由模式是可以根据路由键选择性给多个消费者发送消息的模式,它包括一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的不同的队列上去,两个队列通过路由键绑定到交换机上,生产者发送消息到交换机,交换机通过路由键转发到不同队列,队列绑定的消费者接受消息。

示意图:
image.png

通配符模式

通配符模式是可以根据路由键匹配规则选择性给多个消费者发送消息的模式,它包含一个生产者、两个消费者、两个队列和一个交换机。两个消费者同时绑定到不同的队列上去,两个队列通过路由键匹配规则绑定到交换机上去,生产者发送消息到交换机,交换机通过路由键匹配规则转发到不同队列,队列绑定的消费者接收并消费消息。

示意图:
image.png

查看原文

赞 0 收藏 0 评论 0

21up 收藏了文章 · 9月9日

Spring系列(8)- 过滤器+监听器+拦截器+AOP 比较

1、前言

在后端项目开发中,会有一些需要基于全局处理的程序。传统基于Servlet容器的程序中,我们可以使用过滤器和监听器,在Java 框架中还可以使用拦截器,而面向切面编程AOP更是作为Spring框架中的核心思想被大家所关注。本文一方面从概念上讲解Filter、Listener、Interceptor和aop的区别,另一方面更是从代码层面讲解如何在SpringBoot中应用开发。

它们的执行顺序如下(@ControllerAdvice本文暂不做介绍):

拦截顺序:ServletContextListener> Filter > Interception > AOP > 具体执行的方法 > AOP > @ControllerAdvice > Interception > Filter > ServletContextListener

根据实现原理分成下面两大类:

  1. Filter和Listener:依赖Servlet容器,基于函数回调实现。可以拦截所有请求,覆盖范围更广,但无法获取ioc容器中的bean。
  2. Interceptor和aop:依赖spring框架,基于java反射和动态代理实现。只能拦截controller的请求,可以获取ioc容器中的bean。

Filter -> Interceptor -> aop ,拦截的功能越来越细致、强大,尤其是Interceptor和aop可以更好的结合spring框架的上下文进行开发。但是拦截顺序也是越来越靠后,请求是先进入Servlet容器的,越早的过滤和拦截对系统性能的消耗越少。具体选用哪种方法,就需要开发人员根据实际业务情况综合考虑了。

2、过滤器(Filter)

Filter过滤器是Servlet容器层面的,在实现上基于函数回调,可以对几乎所有请求进行过滤。过滤器是对数据进行过滤,预处理过程,当我们访问网站时,有时候会发布一些敏感信息,发完以后有的会用*替代,还有就是登陆权限控制等,一个资源,没有经过授权,肯定是不能让用户随便访问的,这个时候,也可以用到过滤器。过滤器的功能还有很多,例如实现URL级别的权限控制、压缩响应信息、编码格式等等。

SpringBoot实现过滤器,常见有三种方式,越复杂功能越强大。

2.1、无路径无顺序@Component

这种方式最简单,直接实现Filter接口,并使用@Component注解标注为组件自动注入bean。但是缺点是没办法设置过滤的路径,默认是 /* 过滤所有。

Filter接口有 init、doFilter、destroy 三个方法,但 init、destroy 是有默认方法实现,可以不重写。

import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;

@Component
public class DemoFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
        HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
        HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse;
        System.out.println("filter1----------"+httpServletResponse.getStatus());
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

2.2、有路径无顺序@WebFilter+@ServletComponentScan

这种方式要稍微复杂一点,但更全面。使用 @WebFilter替代 @Component,可以通过该注解设置过滤器的匹配路径。不过需要在启动类中使用@ServletComponentScan。@ServletComponentScan扫描带@WebFilter、@WebServlet、@WebListener并将帮我们注入bean。

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(filterName = "filter1",urlPatterns = {"/hello/*"})
public class DemoFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
        HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
        HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse;
        System.out.println("filter1----------"+httpServletResponse.getStatus());
        filterChain.doFilter(servletRequest,servletResponse);
    }

}

2.3、有路径有顺序@Configuration

这种方式完全通过配置类来实现,在只实现过滤器的接口,并不需要通过任何注解注入IOC容器,都通过配置类来注入。

AFilter.java(BFilter.java也类似)

import javax.servlet.*;
import java.io.IOException;

public class AFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filterA----------");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

FilterConfig.java 配置类

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pers.kerry.demo.webfilter.filter.AFilter;
import pers.kerry.demo.webfilter.filter.BFilter;

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean AFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        AFilter aFilter=new AFilter();
        filterRegistrationBean.setFilter(aFilter);
        filterRegistrationBean.addUrlPatterns("*");
        filterRegistrationBean.setName("AFilter");
        filterRegistrationBean.setOrder(1);
        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean BFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        BFilter bFilter=new BFilter();
        filterRegistrationBean.setFilter(bFilter);
        filterRegistrationBean.addUrlPatterns("*");
        filterRegistrationBean.setName("BFilter");
        filterRegistrationBean.setOrder(2);
        return filterRegistrationBean;
    }
}

3、监听器(Listener )

Listener监听器也是Servlet层面的,可以用于监听Web应用中某些对象、信息的创建、销毁和修改等动作发生,然后做出相应的响应处理。根据监听对象,将监听器分为3类:

  1. ServletContext:对应application,实现接口ServletContextListener。在整个Web服务中只有一个,在Web服务关闭时销毁。可用于做数据缓存,例如结合redis,在Web服务创建时从数据库拉取数据到缓存服务器。
  2. HttpSession:对应session会话,实现接口HttpSessionListener。在会话起始时创建,一端关闭会话后销毁。可用作获取在线用户数量。
  3. ServletRequest:对应request,实现接口ServletRequestListener。request对象是客户发送请求时创建的,用于封装请求数据,请求处理完毕后销毁。可用作封装用户信息。

在写Listener的类时,实现方式和Filter一样,同样有两种实时方式。一种是只加@Component;另一种是 @WebListener 和 @ServletComponentScan 配合使用。不过实现接口则根据监听对象区分,如:ServletContextListener、HttpSessionListener和ServletRequestListener。

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class DemoListener implements ServletContextListener{

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContextListener 初始化上下文");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContextListener 销毁");
    }
}

4、拦截器(Interceptor )

Interceptor拦截器和Filter和Listener有本质上的不同,前面二者都是依赖于Servlet容器,而Interceptor则是依赖于Spring框架,是aop的一种表现,基于Java的动态代理实现的。在SpringBoot中实现拦截器的方式,有点类似于实现过滤器的第三种方式,所以要通过下面两个步骤。

  1. 声明拦截器的类:通过实现 HandlerInterceptor接口,实现preHandle、postHandle和afterCompletion方法。
  2. 通过配置类配置拦截器:通过实现WebMvcConfigurer接口,实现addInterceptors方法。

DemoInterceptor.java

import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

public class DemoInterceptor implements HandlerInterceptor{

    /**
     * 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器
     * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        String methodName = method.getName();
        System.out.println("====拦截到了方法:"+methodName+",preHandle====");
        return true;
    }

    /**
     * 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("====postHandle====");
    }

    /**
     *整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("====afterCompletion====");
    }
}

InterceptorConfig.java

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import pers.kerry.demo.webfilter.interceptor.DemoInterceptor;

/**
 * spring 2.x 以前,通过继承 WebMvcConfigurerAdapter 类
 * spring 2.x 之后,实现 WebMvcConfigurer 接口
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
    }

}

5、AOP

相比较于拦截器,Spring 的aop则功能更强大,封装的更细致,需要单独引用 jar包。

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在定义AOP的类时,不需要和前面拦截器一样麻烦了,只需要通过注解,底层实现逻辑都通过IOC框架实现好了,涉及到的注解如下:

  • @Aspect:将一个 java 类定义为切面类。
  • @Pointcut:定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,也可以是一个注解等。
  • @Before:在切入点开始处切入内容。
  • @After:在切入点结尾处切入内容,无论是否有异常,都会执行,类似于finally。
  • @AfterReturning:在切入点 return 内容之后处理逻辑,只有执行成功会执行,异常不会。
  • @Around:在切入点前后切入内容,并自己控制何时执行切入点自身的内容。原则上可以替代@Before和@After。
  • @AfterThrowing:用来处理当切入内容部分抛出异常之后的处理逻辑。
  • @Order(100):AOP 切面执行顺序, @Before 数值越小越先执行,@After 和 @AfterReturning 数值越大越先执行。

DemoAspect.java

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Aspect 注解 使之成为切面类
 * @Component 注解 把切面类加入到IOC容器中
 */
@Aspect
@Component
public class DemoAspect {

    @Pointcut( "execution( public * pers.kerry.demo.webfilter.controller.DemoController.*(..) )")
    public void doPointcut(){
    }

    @Before("doPointcut()")
    public void doBefore(){
        System.out.println("==doBefore==");
    }

    @After("doPointcut()")
    public void doAfter(){
        System.out.println("==doAfter==");
    }

    @AfterReturning("doPointcut()")
    public void doAfterReturning(){
        System.out.println("==doAfterReturning==");
    }

    /**
     * 返回值类型Object,对应所有被拦截方法的返回值类型,不能为void
     */
    @Around("doPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
        System.out.println("==doAround.before==");
        Object ret=proceedingJoinPoint.proceed();
        System.out.println("==doAround.after==");
        return ret;
    }
}

查看原文

21up 赞了文章 · 9月9日

Spring系列(8)- 过滤器+监听器+拦截器+AOP 比较

1、前言

在后端项目开发中,会有一些需要基于全局处理的程序。传统基于Servlet容器的程序中,我们可以使用过滤器和监听器,在Java 框架中还可以使用拦截器,而面向切面编程AOP更是作为Spring框架中的核心思想被大家所关注。本文一方面从概念上讲解Filter、Listener、Interceptor和aop的区别,另一方面更是从代码层面讲解如何在SpringBoot中应用开发。

它们的执行顺序如下(@ControllerAdvice本文暂不做介绍):

拦截顺序:ServletContextListener> Filter > Interception > AOP > 具体执行的方法 > AOP > @ControllerAdvice > Interception > Filter > ServletContextListener

根据实现原理分成下面两大类:

  1. Filter和Listener:依赖Servlet容器,基于函数回调实现。可以拦截所有请求,覆盖范围更广,但无法获取ioc容器中的bean。
  2. Interceptor和aop:依赖spring框架,基于java反射和动态代理实现。只能拦截controller的请求,可以获取ioc容器中的bean。

Filter -> Interceptor -> aop ,拦截的功能越来越细致、强大,尤其是Interceptor和aop可以更好的结合spring框架的上下文进行开发。但是拦截顺序也是越来越靠后,请求是先进入Servlet容器的,越早的过滤和拦截对系统性能的消耗越少。具体选用哪种方法,就需要开发人员根据实际业务情况综合考虑了。

2、过滤器(Filter)

Filter过滤器是Servlet容器层面的,在实现上基于函数回调,可以对几乎所有请求进行过滤。过滤器是对数据进行过滤,预处理过程,当我们访问网站时,有时候会发布一些敏感信息,发完以后有的会用*替代,还有就是登陆权限控制等,一个资源,没有经过授权,肯定是不能让用户随便访问的,这个时候,也可以用到过滤器。过滤器的功能还有很多,例如实现URL级别的权限控制、压缩响应信息、编码格式等等。

SpringBoot实现过滤器,常见有三种方式,越复杂功能越强大。

2.1、无路径无顺序@Component

这种方式最简单,直接实现Filter接口,并使用@Component注解标注为组件自动注入bean。但是缺点是没办法设置过滤的路径,默认是 /* 过滤所有。

Filter接口有 init、doFilter、destroy 三个方法,但 init、destroy 是有默认方法实现,可以不重写。

import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;

@Component
public class DemoFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
        HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
        HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse;
        System.out.println("filter1----------"+httpServletResponse.getStatus());
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

2.2、有路径无顺序@WebFilter+@ServletComponentScan

这种方式要稍微复杂一点,但更全面。使用 @WebFilter替代 @Component,可以通过该注解设置过滤器的匹配路径。不过需要在启动类中使用@ServletComponentScan。@ServletComponentScan扫描带@WebFilter、@WebServlet、@WebListener并将帮我们注入bean。

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(filterName = "filter1",urlPatterns = {"/hello/*"})
public class DemoFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
        HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
        HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse;
        System.out.println("filter1----------"+httpServletResponse.getStatus());
        filterChain.doFilter(servletRequest,servletResponse);
    }

}

2.3、有路径有顺序@Configuration

这种方式完全通过配置类来实现,在只实现过滤器的接口,并不需要通过任何注解注入IOC容器,都通过配置类来注入。

AFilter.java(BFilter.java也类似)

import javax.servlet.*;
import java.io.IOException;

public class AFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filterA----------");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

FilterConfig.java 配置类

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pers.kerry.demo.webfilter.filter.AFilter;
import pers.kerry.demo.webfilter.filter.BFilter;

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean AFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        AFilter aFilter=new AFilter();
        filterRegistrationBean.setFilter(aFilter);
        filterRegistrationBean.addUrlPatterns("*");
        filterRegistrationBean.setName("AFilter");
        filterRegistrationBean.setOrder(1);
        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean BFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        BFilter bFilter=new BFilter();
        filterRegistrationBean.setFilter(bFilter);
        filterRegistrationBean.addUrlPatterns("*");
        filterRegistrationBean.setName("BFilter");
        filterRegistrationBean.setOrder(2);
        return filterRegistrationBean;
    }
}

3、监听器(Listener )

Listener监听器也是Servlet层面的,可以用于监听Web应用中某些对象、信息的创建、销毁和修改等动作发生,然后做出相应的响应处理。根据监听对象,将监听器分为3类:

  1. ServletContext:对应application,实现接口ServletContextListener。在整个Web服务中只有一个,在Web服务关闭时销毁。可用于做数据缓存,例如结合redis,在Web服务创建时从数据库拉取数据到缓存服务器。
  2. HttpSession:对应session会话,实现接口HttpSessionListener。在会话起始时创建,一端关闭会话后销毁。可用作获取在线用户数量。
  3. ServletRequest:对应request,实现接口ServletRequestListener。request对象是客户发送请求时创建的,用于封装请求数据,请求处理完毕后销毁。可用作封装用户信息。

在写Listener的类时,实现方式和Filter一样,同样有两种实时方式。一种是只加@Component;另一种是 @WebListener 和 @ServletComponentScan 配合使用。不过实现接口则根据监听对象区分,如:ServletContextListener、HttpSessionListener和ServletRequestListener。

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class DemoListener implements ServletContextListener{

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContextListener 初始化上下文");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContextListener 销毁");
    }
}

4、拦截器(Interceptor )

Interceptor拦截器和Filter和Listener有本质上的不同,前面二者都是依赖于Servlet容器,而Interceptor则是依赖于Spring框架,是aop的一种表现,基于Java的动态代理实现的。在SpringBoot中实现拦截器的方式,有点类似于实现过滤器的第三种方式,所以要通过下面两个步骤。

  1. 声明拦截器的类:通过实现 HandlerInterceptor接口,实现preHandle、postHandle和afterCompletion方法。
  2. 通过配置类配置拦截器:通过实现WebMvcConfigurer接口,实现addInterceptors方法。

DemoInterceptor.java

import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

public class DemoInterceptor implements HandlerInterceptor{

    /**
     * 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器
     * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        String methodName = method.getName();
        System.out.println("====拦截到了方法:"+methodName+",preHandle====");
        return true;
    }

    /**
     * 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("====postHandle====");
    }

    /**
     *整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("====afterCompletion====");
    }
}

InterceptorConfig.java

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import pers.kerry.demo.webfilter.interceptor.DemoInterceptor;

/**
 * spring 2.x 以前,通过继承 WebMvcConfigurerAdapter 类
 * spring 2.x 之后,实现 WebMvcConfigurer 接口
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
    }

}

5、AOP

相比较于拦截器,Spring 的aop则功能更强大,封装的更细致,需要单独引用 jar包。

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在定义AOP的类时,不需要和前面拦截器一样麻烦了,只需要通过注解,底层实现逻辑都通过IOC框架实现好了,涉及到的注解如下:

  • @Aspect:将一个 java 类定义为切面类。
  • @Pointcut:定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,也可以是一个注解等。
  • @Before:在切入点开始处切入内容。
  • @After:在切入点结尾处切入内容,无论是否有异常,都会执行,类似于finally。
  • @AfterReturning:在切入点 return 内容之后处理逻辑,只有执行成功会执行,异常不会。
  • @Around:在切入点前后切入内容,并自己控制何时执行切入点自身的内容。原则上可以替代@Before和@After。
  • @AfterThrowing:用来处理当切入内容部分抛出异常之后的处理逻辑。
  • @Order(100):AOP 切面执行顺序, @Before 数值越小越先执行,@After 和 @AfterReturning 数值越大越先执行。

DemoAspect.java

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Aspect 注解 使之成为切面类
 * @Component 注解 把切面类加入到IOC容器中
 */
@Aspect
@Component
public class DemoAspect {

    @Pointcut( "execution( public * pers.kerry.demo.webfilter.controller.DemoController.*(..) )")
    public void doPointcut(){
    }

    @Before("doPointcut()")
    public void doBefore(){
        System.out.println("==doBefore==");
    }

    @After("doPointcut()")
    public void doAfter(){
        System.out.println("==doAfter==");
    }

    @AfterReturning("doPointcut()")
    public void doAfterReturning(){
        System.out.println("==doAfterReturning==");
    }

    /**
     * 返回值类型Object,对应所有被拦截方法的返回值类型,不能为void
     */
    @Around("doPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
        System.out.println("==doAround.before==");
        Object ret=proceedingJoinPoint.proceed();
        System.out.println("==doAround.after==");
        return ret;
    }
}

查看原文

赞 12 收藏 11 评论 0

21up 赞了文章 · 9月9日

Spring系列(8)- 过滤器+监听器+拦截器+AOP 比较

1、前言

在后端项目开发中,会有一些需要基于全局处理的程序。传统基于Servlet容器的程序中,我们可以使用过滤器和监听器,在Java 框架中还可以使用拦截器,而面向切面编程AOP更是作为Spring框架中的核心思想被大家所关注。本文一方面从概念上讲解Filter、Listener、Interceptor和aop的区别,另一方面更是从代码层面讲解如何在SpringBoot中应用开发。

它们的执行顺序如下(@ControllerAdvice本文暂不做介绍):

拦截顺序:ServletContextListener> Filter > Interception > AOP > 具体执行的方法 > AOP > @ControllerAdvice > Interception > Filter > ServletContextListener

根据实现原理分成下面两大类:

  1. Filter和Listener:依赖Servlet容器,基于函数回调实现。可以拦截所有请求,覆盖范围更广,但无法获取ioc容器中的bean。
  2. Interceptor和aop:依赖spring框架,基于java反射和动态代理实现。只能拦截controller的请求,可以获取ioc容器中的bean。

Filter -> Interceptor -> aop ,拦截的功能越来越细致、强大,尤其是Interceptor和aop可以更好的结合spring框架的上下文进行开发。但是拦截顺序也是越来越靠后,请求是先进入Servlet容器的,越早的过滤和拦截对系统性能的消耗越少。具体选用哪种方法,就需要开发人员根据实际业务情况综合考虑了。

2、过滤器(Filter)

Filter过滤器是Servlet容器层面的,在实现上基于函数回调,可以对几乎所有请求进行过滤。过滤器是对数据进行过滤,预处理过程,当我们访问网站时,有时候会发布一些敏感信息,发完以后有的会用*替代,还有就是登陆权限控制等,一个资源,没有经过授权,肯定是不能让用户随便访问的,这个时候,也可以用到过滤器。过滤器的功能还有很多,例如实现URL级别的权限控制、压缩响应信息、编码格式等等。

SpringBoot实现过滤器,常见有三种方式,越复杂功能越强大。

2.1、无路径无顺序@Component

这种方式最简单,直接实现Filter接口,并使用@Component注解标注为组件自动注入bean。但是缺点是没办法设置过滤的路径,默认是 /* 过滤所有。

Filter接口有 init、doFilter、destroy 三个方法,但 init、destroy 是有默认方法实现,可以不重写。

import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;

@Component
public class DemoFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
        HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
        HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse;
        System.out.println("filter1----------"+httpServletResponse.getStatus());
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

2.2、有路径无顺序@WebFilter+@ServletComponentScan

这种方式要稍微复杂一点,但更全面。使用 @WebFilter替代 @Component,可以通过该注解设置过滤器的匹配路径。不过需要在启动类中使用@ServletComponentScan。@ServletComponentScan扫描带@WebFilter、@WebServlet、@WebListener并将帮我们注入bean。

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebFilter(filterName = "filter1",urlPatterns = {"/hello/*"})
public class DemoFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException{
        HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
        HttpServletResponse httpServletResponse=(HttpServletResponse)servletResponse;
        System.out.println("filter1----------"+httpServletResponse.getStatus());
        filterChain.doFilter(servletRequest,servletResponse);
    }

}

2.3、有路径有顺序@Configuration

这种方式完全通过配置类来实现,在只实现过滤器的接口,并不需要通过任何注解注入IOC容器,都通过配置类来注入。

AFilter.java(BFilter.java也类似)

import javax.servlet.*;
import java.io.IOException;

public class AFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("filterA----------");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

FilterConfig.java 配置类

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pers.kerry.demo.webfilter.filter.AFilter;
import pers.kerry.demo.webfilter.filter.BFilter;

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean AFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        AFilter aFilter=new AFilter();
        filterRegistrationBean.setFilter(aFilter);
        filterRegistrationBean.addUrlPatterns("*");
        filterRegistrationBean.setName("AFilter");
        filterRegistrationBean.setOrder(1);
        return filterRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean BFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        BFilter bFilter=new BFilter();
        filterRegistrationBean.setFilter(bFilter);
        filterRegistrationBean.addUrlPatterns("*");
        filterRegistrationBean.setName("BFilter");
        filterRegistrationBean.setOrder(2);
        return filterRegistrationBean;
    }
}

3、监听器(Listener )

Listener监听器也是Servlet层面的,可以用于监听Web应用中某些对象、信息的创建、销毁和修改等动作发生,然后做出相应的响应处理。根据监听对象,将监听器分为3类:

  1. ServletContext:对应application,实现接口ServletContextListener。在整个Web服务中只有一个,在Web服务关闭时销毁。可用于做数据缓存,例如结合redis,在Web服务创建时从数据库拉取数据到缓存服务器。
  2. HttpSession:对应session会话,实现接口HttpSessionListener。在会话起始时创建,一端关闭会话后销毁。可用作获取在线用户数量。
  3. ServletRequest:对应request,实现接口ServletRequestListener。request对象是客户发送请求时创建的,用于封装请求数据,请求处理完毕后销毁。可用作封装用户信息。

在写Listener的类时,实现方式和Filter一样,同样有两种实时方式。一种是只加@Component;另一种是 @WebListener 和 @ServletComponentScan 配合使用。不过实现接口则根据监听对象区分,如:ServletContextListener、HttpSessionListener和ServletRequestListener。

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class DemoListener implements ServletContextListener{

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("ServletContextListener 初始化上下文");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("ServletContextListener 销毁");
    }
}

4、拦截器(Interceptor )

Interceptor拦截器和Filter和Listener有本质上的不同,前面二者都是依赖于Servlet容器,而Interceptor则是依赖于Spring框架,是aop的一种表现,基于Java的动态代理实现的。在SpringBoot中实现拦截器的方式,有点类似于实现过滤器的第三种方式,所以要通过下面两个步骤。

  1. 声明拦截器的类:通过实现 HandlerInterceptor接口,实现preHandle、postHandle和afterCompletion方法。
  2. 通过配置类配置拦截器:通过实现WebMvcConfigurer接口,实现addInterceptors方法。

DemoInterceptor.java

import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

public class DemoInterceptor implements HandlerInterceptor{

    /**
     * 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器
     * 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        String methodName = method.getName();
        System.out.println("====拦截到了方法:"+methodName+",preHandle====");
        return true;
    }

    /**
     * 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        System.out.println("====postHandle====");
    }

    /**
     *整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        System.out.println("====afterCompletion====");
    }
}

InterceptorConfig.java

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import pers.kerry.demo.webfilter.interceptor.DemoInterceptor;

/**
 * spring 2.x 以前,通过继承 WebMvcConfigurerAdapter 类
 * spring 2.x 之后,实现 WebMvcConfigurer 接口
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");
    }

}

5、AOP

相比较于拦截器,Spring 的aop则功能更强大,封装的更细致,需要单独引用 jar包。

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在定义AOP的类时,不需要和前面拦截器一样麻烦了,只需要通过注解,底层实现逻辑都通过IOC框架实现好了,涉及到的注解如下:

  • @Aspect:将一个 java 类定义为切面类。
  • @Pointcut:定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,也可以是一个注解等。
  • @Before:在切入点开始处切入内容。
  • @After:在切入点结尾处切入内容,无论是否有异常,都会执行,类似于finally。
  • @AfterReturning:在切入点 return 内容之后处理逻辑,只有执行成功会执行,异常不会。
  • @Around:在切入点前后切入内容,并自己控制何时执行切入点自身的内容。原则上可以替代@Before和@After。
  • @AfterThrowing:用来处理当切入内容部分抛出异常之后的处理逻辑。
  • @Order(100):AOP 切面执行顺序, @Before 数值越小越先执行,@After 和 @AfterReturning 数值越大越先执行。

DemoAspect.java

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @Aspect 注解 使之成为切面类
 * @Component 注解 把切面类加入到IOC容器中
 */
@Aspect
@Component
public class DemoAspect {

    @Pointcut( "execution( public * pers.kerry.demo.webfilter.controller.DemoController.*(..) )")
    public void doPointcut(){
    }

    @Before("doPointcut()")
    public void doBefore(){
        System.out.println("==doBefore==");
    }

    @After("doPointcut()")
    public void doAfter(){
        System.out.println("==doAfter==");
    }

    @AfterReturning("doPointcut()")
    public void doAfterReturning(){
        System.out.println("==doAfterReturning==");
    }

    /**
     * 返回值类型Object,对应所有被拦截方法的返回值类型,不能为void
     */
    @Around("doPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
        System.out.println("==doAround.before==");
        Object ret=proceedingJoinPoint.proceed();
        System.out.println("==doAround.after==");
        return ret;
    }
}

查看原文

赞 12 收藏 11 评论 0

21up 赞了文章 · 9月7日

Java 枚举实战

摘要:本文主要讨论生产环境中枚举类的使用。首先会通过对枚举类概念进行简单的介绍,引入我们讨论的主题;然后就直接进入实战部分,本文只会介绍在实战中用的比较多,也比较常用的情况,所以希望老铁可以用心体会并实践,最终化为己有;最后会大致在对枚举的 API 做了一个简单的介绍。其余没有介绍的内容,基本上在我们的生产环境中极少用到,如果有兴趣的可以自己在深入研究。

枚举

概念:枚举类型是 Java 5 中新增特性的一部分,它是一种特殊的数据类型,它的特点就是使我们的代码更加简洁,安全,从某种程度来说,使我们可以站在全局的角度更加清晰的理解业务逻辑(比如说一个订单的状态被我们定义成了枚举类之后,我们不用看业务逻辑代码,只需要从这个枚举类中都可以了解到我们这个订单的所有状态,给我们一种全局观的印象在脑海中,更利于后期代码的梳理。)

定义:首先使用 enum 来定义一个枚举类;然后每个枚举值(即声明的枚举)是使用逗号隔开,如果枚举值后面还有操作代码,那么在最后一个枚举值后面加上分号结尾;最后一定要记住,枚举类中声明的每一个值都是一个实例,也就是说有 n 个枚举值,构造函数就被调用 n 次来创建出 n 个枚举实例。这里举一个小例子看看:

enum SeasonType {
    SPRING, SUMMER, AUTUMN, WINTER;
    SeasonType() {
        System.out.println("看看此构造函数被调用了几次");
    }
}
public class Season {
    public static void main(String[] args) {
        System.out.println(SeasonType.SPRING);
    }
}

控制台输出:

看看此构造函数被调用了几次
看看此构造函数被调用了几次
看看此构造函数被调用了几次
看看此构造函数被调用了几次
SPRING

总结: 从这里可以看出来,枚举类中声明的每一枚举值其实都调用了构造函数并创建了实例,简单理解就是: 每一个枚举值都是一个实例对象。

实战一无参

(1)定义一个无参枚举类

enum SeasonType {
    SPRING, SUMMER, AUTUMN, WINTER
}

(2)实战中的使用

// 根据实际情况选择下面的用法即可
SeasonType springType = SeasonType.SPRING;    // 输出 SPRING 
String springString = SeasonType.SPRING.toString();    // 输出 SPRING

实战二有一参

(1)定义只有一个参数的枚举类

enum SeasonType {
    // 通过构造函数传递参数并创建实例
    SPRING("spring"),
    SUMMER("summer"),
    AUTUMN("autumn"),
    WINTER("winter");

    // 定义实例对应的参数
    private String msg;

    // 必写:通过此构造器给枚举值创建实例
    SeasonType(String msg) {
        this.msg = msg;
    }

    // 通过此方法可以获取到对应实例的参数值
    public String getMsg() {
        return msg;
    }
}

(2)实战中的使用

// 当我们为某个实例类赋值的时候可使用如下方式
String msg = SeasonType.SPRING.getMsg();    // 输出 spring

实战三有两参

(1)定义有两个参数的枚举类

public enum Season {
    // 通过构造函数传递参数并创建实例
    SPRING(1, "spring"),
    SUMMER(2, "summer"),
    AUTUMN(3, "autumn"),
    WINTER(4, "winter");

    // 定义实例对应的参数
    private Integer key;
    private String msg;

    // 必写:通过此构造器给枚举值创建实例
    Season(Integer key, String msg) {
        this.key = key;
        this.msg = msg;
    }

    // 很多情况,我们可能从前端拿到的值是枚举类的 key ,然后就可以通过以下静态方法获取到对应枚举值
    public static Season valueofKey(Integer key) {
        for (Season season : Season.values()) {
            if (season.key.equals(key)) {
                return season;
            }
        }
        throw new IllegalArgumentException("No element matches " + key);
    }

    // 通过此方法可以获取到对应实例的 key 值
    public Integer getKey() {
        return key;
    }

    // 通过此方法可以获取到对应实例的 msg 值
    public String getMsg() {
        return msg;
    }
}

(2)实战中的使用

// 输出 key 为 1 的枚举值实例
Season season = Season.valueofKey(1);
// 输出 SPRING 实例对应的 key
Integer key = Season.SPRING.getKey();
// 输出 SPRING 实例对应的 msg
String msg = Season.SPRING.getMsg();

枚举类总结

其实枚举类懂了其概念后,枚举就变得相当简单了,随手就可以写一个枚举类出来。所以如上几个实战小例子一定要先搞清楚概念,然后在练习几遍就 ok 了。重要的概念,我在这里在赘述一遍,帮助老铁们快速掌握这块知识,首先记住,枚举类中的枚举值可以没有参数,也可以有多个参数,每一个枚举值都是一个实例;并且还有一点很重要,就是如果枚举值有 n 个参数,那么构造函数中的参数值肯定有 n 个,因为声明的每一个枚举值都会调用构造函数去创建实例,所以参数一定是一一对应的;既然明白了这一点,那么我们只需要在枚举类中把这 n 个参数定义为 n 个成员变量,然后提供对应的 get() 方法,之后通过实例就可以随意的获取实例中的任意参数值了。如果想让枚举类更加的好用,就可以模仿我在实战三中的写法那样,通过某一个参数值,比如 key 参数值,就能获取到其对应的枚举值,然后想要什么值,就 get 什么值就好了。

枚举 API

我们使用 enum 定义的枚举类都是继承 java.lang.Enum 类的,那么就会继承其 API ,常用的 API 如下:

  • String name()

获取枚举名称

  • int ordinal()

获取枚举的位置(下标,初始值为 0 )

  • valueof(String msg)

通过 msg 获取其对应的枚举类型。(比如实战二中的枚举类或其它枚举类都行,只要使用得当都可以使用此方法)

  • values()

获取枚举类中的所有枚举值(比如在实战三中就使用到了)

欢迎老铁们一起讨论。。。

查看原文

赞 6 收藏 6 评论 0

21up 赞了文章 · 9月7日

Java 枚举实战

摘要:本文主要讨论生产环境中枚举类的使用。首先会通过对枚举类概念进行简单的介绍,引入我们讨论的主题;然后就直接进入实战部分,本文只会介绍在实战中用的比较多,也比较常用的情况,所以希望老铁可以用心体会并实践,最终化为己有;最后会大致在对枚举的 API 做了一个简单的介绍。其余没有介绍的内容,基本上在我们的生产环境中极少用到,如果有兴趣的可以自己在深入研究。

枚举

概念:枚举类型是 Java 5 中新增特性的一部分,它是一种特殊的数据类型,它的特点就是使我们的代码更加简洁,安全,从某种程度来说,使我们可以站在全局的角度更加清晰的理解业务逻辑(比如说一个订单的状态被我们定义成了枚举类之后,我们不用看业务逻辑代码,只需要从这个枚举类中都可以了解到我们这个订单的所有状态,给我们一种全局观的印象在脑海中,更利于后期代码的梳理。)

定义:首先使用 enum 来定义一个枚举类;然后每个枚举值(即声明的枚举)是使用逗号隔开,如果枚举值后面还有操作代码,那么在最后一个枚举值后面加上分号结尾;最后一定要记住,枚举类中声明的每一个值都是一个实例,也就是说有 n 个枚举值,构造函数就被调用 n 次来创建出 n 个枚举实例。这里举一个小例子看看:

enum SeasonType {
    SPRING, SUMMER, AUTUMN, WINTER;
    SeasonType() {
        System.out.println("看看此构造函数被调用了几次");
    }
}
public class Season {
    public static void main(String[] args) {
        System.out.println(SeasonType.SPRING);
    }
}

控制台输出:

看看此构造函数被调用了几次
看看此构造函数被调用了几次
看看此构造函数被调用了几次
看看此构造函数被调用了几次
SPRING

总结: 从这里可以看出来,枚举类中声明的每一枚举值其实都调用了构造函数并创建了实例,简单理解就是: 每一个枚举值都是一个实例对象。

实战一无参

(1)定义一个无参枚举类

enum SeasonType {
    SPRING, SUMMER, AUTUMN, WINTER
}

(2)实战中的使用

// 根据实际情况选择下面的用法即可
SeasonType springType = SeasonType.SPRING;    // 输出 SPRING 
String springString = SeasonType.SPRING.toString();    // 输出 SPRING

实战二有一参

(1)定义只有一个参数的枚举类

enum SeasonType {
    // 通过构造函数传递参数并创建实例
    SPRING("spring"),
    SUMMER("summer"),
    AUTUMN("autumn"),
    WINTER("winter");

    // 定义实例对应的参数
    private String msg;

    // 必写:通过此构造器给枚举值创建实例
    SeasonType(String msg) {
        this.msg = msg;
    }

    // 通过此方法可以获取到对应实例的参数值
    public String getMsg() {
        return msg;
    }
}

(2)实战中的使用

// 当我们为某个实例类赋值的时候可使用如下方式
String msg = SeasonType.SPRING.getMsg();    // 输出 spring

实战三有两参

(1)定义有两个参数的枚举类

public enum Season {
    // 通过构造函数传递参数并创建实例
    SPRING(1, "spring"),
    SUMMER(2, "summer"),
    AUTUMN(3, "autumn"),
    WINTER(4, "winter");

    // 定义实例对应的参数
    private Integer key;
    private String msg;

    // 必写:通过此构造器给枚举值创建实例
    Season(Integer key, String msg) {
        this.key = key;
        this.msg = msg;
    }

    // 很多情况,我们可能从前端拿到的值是枚举类的 key ,然后就可以通过以下静态方法获取到对应枚举值
    public static Season valueofKey(Integer key) {
        for (Season season : Season.values()) {
            if (season.key.equals(key)) {
                return season;
            }
        }
        throw new IllegalArgumentException("No element matches " + key);
    }

    // 通过此方法可以获取到对应实例的 key 值
    public Integer getKey() {
        return key;
    }

    // 通过此方法可以获取到对应实例的 msg 值
    public String getMsg() {
        return msg;
    }
}

(2)实战中的使用

// 输出 key 为 1 的枚举值实例
Season season = Season.valueofKey(1);
// 输出 SPRING 实例对应的 key
Integer key = Season.SPRING.getKey();
// 输出 SPRING 实例对应的 msg
String msg = Season.SPRING.getMsg();

枚举类总结

其实枚举类懂了其概念后,枚举就变得相当简单了,随手就可以写一个枚举类出来。所以如上几个实战小例子一定要先搞清楚概念,然后在练习几遍就 ok 了。重要的概念,我在这里在赘述一遍,帮助老铁们快速掌握这块知识,首先记住,枚举类中的枚举值可以没有参数,也可以有多个参数,每一个枚举值都是一个实例;并且还有一点很重要,就是如果枚举值有 n 个参数,那么构造函数中的参数值肯定有 n 个,因为声明的每一个枚举值都会调用构造函数去创建实例,所以参数一定是一一对应的;既然明白了这一点,那么我们只需要在枚举类中把这 n 个参数定义为 n 个成员变量,然后提供对应的 get() 方法,之后通过实例就可以随意的获取实例中的任意参数值了。如果想让枚举类更加的好用,就可以模仿我在实战三中的写法那样,通过某一个参数值,比如 key 参数值,就能获取到其对应的枚举值,然后想要什么值,就 get 什么值就好了。

枚举 API

我们使用 enum 定义的枚举类都是继承 java.lang.Enum 类的,那么就会继承其 API ,常用的 API 如下:

  • String name()

获取枚举名称

  • int ordinal()

获取枚举的位置(下标,初始值为 0 )

  • valueof(String msg)

通过 msg 获取其对应的枚举类型。(比如实战二中的枚举类或其它枚举类都行,只要使用得当都可以使用此方法)

  • values()

获取枚举类中的所有枚举值(比如在实战三中就使用到了)

欢迎老铁们一起讨论。。。

查看原文

赞 6 收藏 6 评论 0

21up 收藏了文章 · 9月6日

泛型就这么简单

前言

从今天开始进入Java基础的复习,可能一个星期会有一篇的<十道简单算法>,我写博文的未必都是正确的~如果有写错的地方请大家多多包涵并指正~

今天要复习的是泛型,泛型在Java中也是个很重要的知识点,本文主要讲解基础的概念,并不是高深的知识,如果基础好的同学可以当复习看看~

一、什么是泛型?

Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常.

泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型

参数化类型:

  • 把类型当作是参数一样传递
  • <数据类型> 只能是引用类型

相关术语:

  • ArrayList<E>中的E称为类型参数变量
  • ArrayList<Integer>中的Integer称为实际类型参数
  • 整个称为ArrayList<E>泛型类型
  • 整个ArrayList<Integer>称为参数化的类型ParameterizedType

二、为什么需要泛型

早期Java是使用Object来代表任意类型的,但是向下转型有强转的问题,这样程序就不太安全

首先,我们来试想一下:没有泛型,集合会怎么样

  • Collection、Map集合对元素的类型是没有任何限制的。本来我的Collection集合装载的是全部的Dog对象,但是外边把Cat对象存储到集合中,是没有任何语法错误的。
  • 把对象扔进集合中,集合是不知道元素的类型是什么的,仅仅知道是Object。因此在get()的时候,返回的是Object。外边获取该对象,还需要强制转换

有了泛型以后:

  • 代码更加简洁【不用强制转换】
  • 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】
  • 可读性和稳定性【在编写集合的时候,就限定了类型】

2.1有了泛型后使用增强for遍历集合

在创建集合的时候,我们明确了集合的类型了,所以我们可以使用增强for来遍历集合!

        //创建集合对象
        ArrayList<String> list = new ArrayList<>();

        list.add("hello");
        list.add("world");
        list.add("java");

        //遍历,由于明确了类型.我们可以增强for
        for (String s : list) {
            System.out.println(s);
        }

三、泛型基础

3.1泛型类

泛型类就是把泛型定义在类上,用户使用该类的时候,才把类型明确下来....这样的话,用户明确了什么类型,该类就代表着什么类型...用户在使用的时候就不用担心强转的问题,运行时转换异常的问题了。

  • 在类上定义的泛型,在类的方法中也可以使用!

/*
    1:把泛型定义在类上
    2:类型变量定义在类上,方法中也可以使用
 */
public class ObjectTool<T> {
    private T obj;

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
}
  • 测试代码:

用户想要使用哪种类型,就在创建的时候指定类型。使用的时候,该类就会自动转换成用户想要使用的类型了。


    public static void main(String[] args) {
        //创建对象并指定元素类型
        ObjectTool<String> tool = new ObjectTool<>();

        tool.setObj(new String("钟福成"));
        String s = tool.getObj();
        System.out.println(s);


        //创建对象并指定元素类型
        ObjectTool<Integer> objectTool = new ObjectTool<>();
        /**
         * 如果我在这个对象里传入的是String类型的,它在编译时期就通过不了了.
         */
        objectTool.setObj(10);
        int i = objectTool.getObj();
        System.out.println(i);
    }

3.2泛型方法

前面已经介绍了泛型类了,在类上定义的泛型,在方法中也可以使用.....

现在呢,我们可能就仅仅在某一个方法上需要使用泛型....外界仅仅是关心该方法,不关心类其他的属性...这样的话,我们在整个类上定义泛型,未免就有些大题小作了。

  • 定义泛型方法....泛型是先定义后使用的

    //定义泛型方法..
    public <T> void show(T t) {
        System.out.println(t);

    }
  • 测试代码:

用户传递进来的是什么类型,返回值就是什么类型了

    public static void main(String[] args) {
        //创建对象
        ObjectTool tool = new ObjectTool();

        //调用方法,传入的参数是什么类型,返回值就是什么类型
        tool.show("hello");
        tool.show(12);
        tool.show(12.5);

    }

3.3泛型类派生出的子类

前面我们已经定义了泛型类,泛型类是拥有泛型这个特性的类,它本质上还是一个Java类,那么它就可以被继承

那它是怎么被继承的呢??这里分两种情况

  1. 子类明确泛型类的类型参数变量
  2. 子类不明确泛型类的类型参数变量

3.3.1子类明确泛型类的类型参数变量

  • 泛型接口

/*
    把泛型定义在接口上
 */
public interface Inter<T> {
    public abstract void show(T t);

}
  • 实现泛型接口的类.....

/**
 * 子类明确泛型类的类型参数变量:
 */

public class InterImpl implements Inter<String> {
    @Override
    public void show(String s) {
        System.out.println(s);

    }
}

3.3.2子类不明确泛型类的类型参数变量

  • 当子类不明确泛型类的类型参数变量时,外界使用子类的时候,也需要传递类型参数变量进来,在实现类上需要定义出类型参数变量

/**
 * 子类不明确泛型类的类型参数变量:
 *      实现类也要定义出<T>类型的
 *
 */
public class InterImpl<T> implements Inter<T> {

    @Override
    public void show(T t) {
        System.out.println(t);

    }
}

测试代码:

    public static void main(String[] args) {
        //测试第一种情况
        //Inter<String> i = new InterImpl();
        //i.show("hello");

        //第二种情况测试
        Inter<String> ii = new InterImpl<>();
        ii.show("100");

    }

值得注意的是:

  • 实现类的要是重写父类的方法,返回值的类型是要和父类一样的!
  • 类上声明的泛形只对非静态成员有效

3.4类型通配符

为什么需要类型通配符????我们来看一个需求.......

现在有个需求:方法接收一个集合参数,遍历集合并把集合元素打印出来,怎么办?

  • 按照我们没有学习泛型之前,我们可能会这样做:

public void test(List list){


    for(int i=0;i<list.size();i++){
        
        System.out.println(list.get(i));
    
    }
}

上面的代码是正确的,只不过在编译的时候会出现警告,说没有确定集合元素的类型....这样是不优雅的...

  • 那我们学习了泛型了,现在要怎么做呢??有的人可能会这样做:

public void test(List<Object> list){


    for(int i=0;i<list.size();i++){
        
        System.out.println(list.get(i));
    
    }
}

这样做语法是没毛病的,但是这里十分值得注意的是:该test()方法只能遍历装载着Object的集合!!!

强调:泛型中的<Object>并不是像以前那样有继承关系的,也就是说List<Object>List<String>是毫无关系的!!!!

那现在咋办???我们是不清楚List集合装载的元素是什么类型的,List<Objcet>这样是行不通的........于是Java泛型提供了类型通配符 ?

所以代码应该改成这样:


public void test(List<?> list){


    for(int i=0;i<list.size();i++){
        
        System.out.println(list.get(i));
    
    }
}

?号通配符表示可以匹配任意类型,任意的Java类都可以匹配.....

现在非常值得注意的是,当我们使用?号通配符的时候:就只能调对象与类型无关的方法,不能调用对象与类型有关的方法。

记住,只能调用与对象无关的方法,不能调用对象与类型有关的方法。因为直到外界使用才知道具体的类型是什么。也就是说,在上面的List集合,我是不能使用add()方法的。因为add()方法是把对象丢进集合中,而现在我是不知道对象的类型是什么。


3.4.1设定通配符上限

首先,我们来看一下设定通配符上限用在哪里....

现在,我想接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么做???

我们学习了通配符,但是如果直接使用通配符的话,该集合就不是只能操作数字了。因此我们需要用到设定通配符上限

    List<? extends Number>

上面的代码表示的是:List集合装载的元素只能是Number的子类或自身


    public static void main(String[] args) {


        //List集合装载的是Integer,可以调用该方法
        List<Integer> integer = new ArrayList<>();
        test(integer);

        //List集合装载的是String,在编译时期就报错了
        List<String> strings = new ArrayList<>();
        test(strings);

    }


    public static void test(List<? extends Number> list) {
        
    }

3.4.2设定通配符下限

既然上面我们已经说了如何设定通配符的上限,那么设定通配符的下限也不是陌生的事了。直接来看语法吧

    //传递进来的只能是Type或Type的父类
    <? super Type>

设定通配符的下限这并不少见,在TreeSet集合中就有....我们来看一下

    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

那它有什么用呢??我们来想一下,当我们想要创建一个TreeSet<String>类型的变量的时候,并传入一个可以比较String大小的Comparator。

那么这个Comparator的选择就有很多了,它可以是Comparator<String>,还可以是类型参数是String的父类,比如说Comparator<Objcet>....

这样做,就非常灵活了。也就是说,只要它能够比较字符串大小,就行了

经评论去补充:在泛型的上限和下限中有一个原则:PECS(Producer Extends Consumer Super)

书上是这样写的:

带有子类限定的可以从泛型读取【也就是--->(? extend T)】-------->Producer Extends
带有超类限定的可以从泛型写入【也就是--->(? super T)】-------->Consumer Super
也有相关博文写得很好:

http://blog.51cto.com/flyingc...
https://blog.csdn.net/xx32666...

3.5通配符和泛型方法

大多时候,我们都可以使用泛型方法来代替通配符的.....


    //使用通配符
    public static void test(List<?> list) {

    }

    //使用泛型方法
    public <T> void  test2(List<T> t) {
        
    }

上面这两个方法都是可以的.....那么现在问题来了,我们使用通配符还是使用泛型方法呢??

原则:

  • 如果参数之间的类型有依赖关系,或者返回值是与参数之间有依赖关系的。那么就使用泛型方法
  • 如果没有依赖关系的,就使用通配符,通配符会灵活一些.

3.6泛型擦除

泛型是提供给javac编译器使用的,它用于限定集合的输入类型,让编译器在源代码级别上,即挡住向集合中插入非法数据。但编译器编译完带有泛形的java程序后,生成的class文件中将不再带有泛形信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。

3.6.1兼容性

JDK5提出了泛型这个概念,但是JDK5以前是没有泛型的。也就是泛型是需要兼容JDK5以下的集合的。

当把带有泛型特性的集合赋值给老版本的集合时候,会把泛型给擦除了。

值得注意的是:它保留的就类型参数的上限。


        List<String> list = new ArrayList<>();

        //类型被擦除了,保留的是类型的上限,String的上限就是Object
        List list1 = list;

如果我把没有类型参数的集合赋值给带有类型参数的集合赋值,这又会怎么样??


        List list = new ArrayList();
        List<String> list2 = list;

它也不会报错,仅仅是提示“未经检查的转换”


四、泛型的应用

当我们写网页的时候,常常会有多个DAO,我们要写每次都要写好几个DAO,这样会有点麻烦。

这里写图片描述

那么我们想要的效果是什么呢??只写一个抽象DAO,别的DAO只要继承该抽象DAO,就有对应的方法了。

要实现这样的效果,肯定是要用到泛型的。因为在抽象DAO中,是不可能知道哪一个DAO会继承它自己,所以是不知道其具体的类型的。而泛型就是在创建的时候才指定其具体的类型。

  • 抽象DAO


public abstract class BaseDao<T> {

    //模拟hibernate....
    private Session session;
    private Class clazz;
    
    
    //哪个子类调的这个方法,得到的class就是子类处理的类型(非常重要)
    public BaseDao(){
        Class clazz = this.getClass();  //拿到的是子类
        ParameterizedType  pt = (ParameterizedType) clazz.getGenericSuperclass();  //BaseDao<Category>
        clazz = (Class) pt.getActualTypeArguments()[0];
        System.out.println(clazz);
        
    }
    

    public void add(T t){
        session.save(t);
    }
    
    public T find(String id){
        return (T) session.get(clazz, id);
    }
    
    public void update(T t){
        session.update(t);
    }
    
    public void delete(String id){
        T t = (T) session.get(clazz, id);
        session.delete(t);
    }
    
}
  • 继承抽象DAO,该实现类就有对应的增删改查的方法了。

CategoryDao


public class CategoryDao extends BaseDao<Category> {

}

BookDao


public class BookDao extends BaseDao<Book> {


}

五、最后

泛型的基础就介绍到这里了,如果以后有需要的话再进行深入研究吧~如果觉得该文章帮助到你,不妨点个赞,关注公众号一波~

参考资料:

  • Core Java
如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y
查看原文

认证与成就

  • 获得 0 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-06-10
个人主页被 153 人浏览