用这篇文章来梳理一下这些杂七杂八的Spring MVC中的基础概念,顺便讲一下在项目中的一些基本使用和常见应用(其实主要是针对AOP的),至于使用他们实现具体的功能,后续可能会独立写出来(谁知道呢)
执行顺序
执行的顺序:
项目初始化:filter:init()->filter:doFilter()->preHandle->Controller->postHandle->afterComplition ->destory()
过滤器
过滤器(Filter),由servlet提供,拦截URL(其实是servlet),经过代理,执行想要的方法,最基本的使用是集成Filter类并重写方法,因为是从url层面上直接拦截,可以有很多用途,比如用于用户身份校验,比如某些页面需要有用户权限才能访问,就可以利用过滤器进行拦截,一些安全框架的鉴权本身也是过滤器的实现。
过滤器代码实现
@WebFilter(filterName="loginFilter",urlPatterns="/*.html") public class TestFilter implements Filter { private String param1; @Override public void destroy() { System.out.println("销毁容器"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { if(!UserUtil.checkLogin()){ response.sendRedirect("login.html"); }else{ filterChain.doFilter(request, response); } System.out.println("过滤权限"); } @Override public void init(FilterConfig filterConfig) throws ServletException { this.param1 = filterConfig.getInitParameter("param1"); } }
我们经常重写doFilter()方法实现拦截,这里模拟了一个全网的用户身份校验,没有登录的用户会重定向到登录页面,其中filterChain.doFilter(arg0, arg1)代表执行请求内容,destroy()和init方法是在容器创建前后执行的,多用于配置。
注册过滤器
上面利用@WebFilter注解不需要配置,如要配置过滤器可以在web.xml中这样配置注册(这也是最初版本的filter配置内容):
<filter> <filter-name>testFilter</filter-name> <filter-class>com.fish.filter.loginFilter</filter-class> <init—param> <param—name>param1</param-name> <param-value>0</param-value> </init—pamm> </filter> <filter-mapping> <filter-name>testFilter</filter-name> <url—pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher> //这可以根据来源筛选 如REQUEST、FROWARD等 </filter-mapping>
后面也可以使用注解配置:
@Configuration public class TestFilterConfig extends WebMvcConfigurerAdapter{ @Bean public TestFilter testFilter() { return new TestFilter(); } @Bean public FilterRegistrationBean filterProxy() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(testFilter()); registration.addUrlPatterns("/*"); registration.setName("testFilter"); return registration; } }
拦截器
拦截器(interceptor),是具体视图层的实现,此处的拦截器由Spring提供,有人喜欢将过滤器与拦截器混为一谈,但这其实是两个不同的东西,只是达成的目的基本一致,过滤器是针对url匹配具体的servlet,拦截器则是直接匹配url,实际上拦截器是在过滤器doFilter()中执行的。
与过滤器的对比
以下内容引自HandlerInterceptor的源码文档注释:
HandlerInterceptor is basically similar to a Servlet Filter, but in contrast to the latter it just allows custom pre-processing with the option of prohibiting the execution of the handler itself, and custom post-processing. Filters are more powerful, for example they allow for exchanging the request and response objects that are handed down the chain. Note that a filter gets configured in web.xml, a HandlerInterceptor in the application context.
大意就是:拦截器类似于过滤器,但是不同于过滤器的是,拦截器只允许在请求基础上自定义其它处理内容,并不能直接操作请求本身,同时过滤器配置在web.xml,而拦截器配置于ApplicationContext中。
过滤器中的filterChain.doFilter()会直接涉及到具体的请求,显然,拦截器的权限更低,但是也更加安全易用。
拦截器代码实现
@Component public class TestInterceptor implements HandlerInterceptor{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("postHandle"); } @Override public void afterCompletion(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion"); } }
这三个方法与filter不同,都是在请求中发生的,preHandle()是请求url之前执行的方法,postHandle()是在请求之后,返回视图前的方法,而afterCompletion是在整个请求执行完毕后执行的方法。preHandle()含有返回值,当返回false后,请求将不过Controller层,直接执行afterCompletion()并结束请求。
注册拦截器
使用注解:
@Configuration public class TestInterceptorConfig implements WebMvcConfigurer { @Autowired TestInterceptor testInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(testInterceptor).addPathPatterns("/*"); } }
在项目xml中的配置:
application.xml
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**" /> <mvc:exclude-mapping path="/api/login"/> <bean id="testInterceptor" class="com.fish.interceptor.TestInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
监听器
此处特指Servlet的监听器,其余(如Listener)不包含在其中。
类似于javascript中被广泛使用的事件监听,java中的监听器监听对象是web应用中某些对象的初始化、销毁、改变等事件,并对事件作出相应响应,可以用于埋点统计网站数据。
监听器的分类
监听器主要是如下三类:
ServletContext对象监听器
HttpSession对象监听器
ServletRequest对象监听器
可以监听的范围包含对象和属性的创建和销毁。
监听实例
比如,可以使用如下代码监听用户会话的创建和销毁从而在线人数:
@WebListener public class onLineCount implements HttpSessionListener { public int count=0;//记录session的数量 @Override public void sessionCreated(HttpSessionEvent arg0) {//监听session的创建 count++; arg0.getSession().getServletContext().setAttribute("Count", count); } @Override public void sessionDestroyed(HttpSessionEvent arg0) {//监听session的撤销 count--; arg0.getSession().getServletContext().setAttribute("Count", count); } }
除了使用注解配置一个监听器,还可以在web.xml中:
<listener> <listener-class>com.ygj.control.onLineCount</listener-class> </listener>
监听器优先于过滤器。
监听器还有很多内容,先不写在这里了。
AOP
其实这篇文章主要就是为了说明AOP的使用和原理。
AOP(面向切面)是Spring两大核心之一,其实也是一种代理(可以参阅设计模式中的代理模式),过滤器与拦截器都是针对一个servlet或是url的,但是AOP是针对具体类的代理,在各个关键点执行对应的方法。
原理简述与动态代理
AOP采用JDKProxy和Cglib进行代理,默认使用JDKProxy来进行动态代理,接下来解释一下动态代理。
AOP的动态代理要区别与静态态代理,静态代理指直接将代理代码织入字节码中。
代理类实现InvocationHandler,实现invoke(Object proxy, Method method, Object[] args)方法,方法的调用都会被转发到invoke方法
调用方法Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)生成代理对象,其中handler指定了代理类,interfaces指定了实现的接口类(即可转化的类),对该对象的所有方法调用都会被转发到invoke方法上(不包括hashCode(),equals()和toString()),
//TODO
相关概念
在AOP中,我们要先了解以下几点概念:
Aspect:切面,一个类,用于拦截待拦截的类,在里面定义切入点和通知;
JoinPoint:连接点,程序中需要切入的点,通常是一个方法;
Advice:通知,在切入点上执行的具体处理,包括before、after等;
PointCut:切入点,带有通知的连接点,其实是一个符号或标志;
常用通知分类
Before:连接点执行前执行,不干扰连接点内容执行,没有拦截功能;
Around:连接点前后执行,有过滤的功能,可选择性的执行方法,类似于前文中的doFilter();
After:连接点后执行,类比于try语句的finally,无论如何都会执行;
AfterReturning:连接点后执行,且仅在不抛出异常的情况下执行;
AfterThrowing:连接点抛出异常后执行;
使用AOP
1.使用xml启动AOP功能:
<aop:aspectj-autoproxy />
2.使用注解定义一个切面类
使用@Aspect来标示一个切面。
@Aspect //声明切面类 @Service public class AspectAop { @Pointcut("execution(* com.lili.maven.service.annotationaop.UserService.addUser(..))") //切入点,在其中定义将要拦截的类 public void pointcut() { } @Before("pointcut()") public void doBefore() { System.out.println("doBefore advice"); } @AfterReturning("pointcut()") public void doAfterReturning() { System.out.println("doAfterReturning advice"); } @After("pointcut()") public void doAfter() { System.out.println("doAfter advice"); } @AfterThrowing("pointcut()") public void doAfterThrowing() { System.out.println("doAfterThrowing advice"); } @Around("pointcut()") public Object doAround(ProceedingJoinPoint pjp) throws Throwable { System.out.println("doAround advice start"); Object result = pjp.proceed(); System.out.println("doAround advice end"); return result; } }
在定义通知时,直接指明了切入点方法,也可以按照与切入点相同的方式定义,比如:
@Before("execution (* com.lilin.maven.service.annotationaop.UserService.addUser(..))")
切入点标志pointcut(),可以更便捷的配置通知。
参考链接:Java监听器Listener使用详解