为啥要转成java_config配置
普通的springmvc项目一般是xml结合注解配置:在web.xml中设置DispatcherServlet启动入口扫描各种xml,在xml中开启注解配置(<context:annotation-config/>),我们就可以用@Service,@Controlle...。
然鹅这种配置文件+注解的方式会让人觉得臃肿,看起来也很乱,如果项目能转成纯java配置,无疑令人赏心悦目(可能有代码洁癖)。还有重要的一点是本人感觉采用java config的方式扩展性要强一些,无论是spring cglib增强(配置了@Configuration)还是spring新特性(自动装配)或者是将来升级为springboot,写更少的代码完成更多地功能特性,java config模式都能做更多的事情。
还有其他补充的可以继续往下吹。。。
xml转java config(以auth项目为例)
1.解放web.xml
what? web.xml这么重要的文件也能去掉???
是的,借助于servlet3.0的一个特性:当容器启动时容器会在类路径查找ServletContainerinitializer接口的实现类,如果发现这样的类就用用这个类来配置Servlet容器,Spring web框架中存在一个类SpringServletContainerInitializer,它实现了上述接口:
public class SpringServletContainerInitializer implements ServletContainerInitializer {
public SpringServletContainerInitializer() {
}
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
...
部分代码省略
...
while(var4.hasNext()) {
WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
initializer.onStartup(servletContext);
}
...
部分代码省略
...
}
}
在这个类中的onStartup方法中,会查找实现了WebApplicationInitializer接口的类,因此,只要 实现了它或者继承它的子类AbstractAnnotationConfigDispatcherServletInitializer,我们就可以配置Servlet上下文啦~
首先,我们看下原有的web.xml配置文件都有啥:
<web-app>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>-->
<param-value>classpath:applicationContext-*.xml</param-value>-->
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
有Servlet,Filter...,这些都是通过xml标签配置的,我们的目的就是用代码替换这些配置,首先我们先写一个类,继承自AbstractAnnotationConfigDispatcherServletInitializer(或者实现WebApplicationInitializer接口):
public class AuthWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{AuthRootAppConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{AuthWebAppConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected Filter[] getServletFilters() {
return super.getServletFilters();
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addFilter("targetFilterLifecycle", new CharacterEncodingFilter("UTF-8", true))
.addMappingForUrlPatterns(null, false, "/*");
servletContext.addFilter("shiroFilter", SessionFilter.class).addMappingForUrlPatterns(null, false, "/*");
super.onStartup(servletContext);
}
}
如上述代码,我新建了一个AuthWebAppInitializer类,继承了AbstractAnnotationConfigDispatcherServletInitializer,它有很多个可以重写的方法:
getRootConfigClasses(): 配置RootApplicationContext
getServletConfigClasses():配置springmvc的contenxt(DispatcherServlet)
getServletMappings():对应web.xml中的<servlet>标签
getServletMappings():DispatcherServlet拦截的规则
getServletFilters():和DispatcherServlet绑定的过滤器
说明:在上述文件web.xml中配置的filter,其实并没有和dispatcherServlet有任何关联,所以需要将其配置到onStartup()方法中,将filter放入servletContext中
那getRootConfitClasses()和getServletConfigClasses()是啥?这个会在下一步中解答
这样,我们替换web.xml配置文件就完成啦,不过仅仅这样还不可以,因为在打包的时候,如果springmvc项目没有web.xml文件会报错,不信的自己可以试下,保证100%复现:
Failed to execute goal org.apache.maven.plugins:maven-war-plugin:2.2:war (default-war) on project auth-web: Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode)
我们需要引入一个maven编译插件,忽略对web.xml文件的检测:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<!--忽略对web.xml文件的检查-->
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
到此为止,就可以干脆的delete掉web.xml文件啦~~
2.解放spring*.xml
我们经常会看到在web.xml文件中作如下配置:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatch</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--springMVC相关-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:dispatchServlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatch</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
我们经常通过这样的配置,先加载listerner相关配置,扫描spring基础配置,然后再加载dispatcherServlet去初始化springmvc相关信息(两者关系可自行google~)
切换到java config,我们自己实现的AuthWebAppInitializer类,其中
getRootConfigClasses():基础配置
getServletConfigClasses():DispatcherServlet
这两个方法可以让我们手动配置自己的初始化方法,在getRootConfigClasses()中,我们可以配置加载spring的基础信息,比如开启apollo,扫描dao,service等:
@EnableApolloConfig // 开启apollo
public class AuthRootAppConfig {
}
在getServletConfigClasses()中,配置springmvc相关的配置(拦截器,视图解析器等):
@EnableWebMvc
public class AuthWebAppConfig extends WebMvcConfigurerAdapter {
...
省略部分代码
...
/**
* 配置JSP视图解析器
*
* @return
*/
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
// 可以在JSP页面中通过${}访问beans
viewResolver.setExposeContextBeansAsAttributes(true);
return viewResolver;
}
//添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authControllerInterceptor).addPathPatterns("/**");
registry.addInterceptor(authServerInterceptor).addPathPatterns("/**");
super.addInterceptors(registry);
}
//添加参数解析器
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(authParamResolver);
super.addArgumentResolvers(argumentResolvers);
}
//资源过滤
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**").addResourceLocations("/images/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
super.addResourceHandlers(registry);
}
...
省略部分代码
...
}
这样,通过spring的root ApplicationContext和 Web ApplicationContext,之前spring配置的xml文件就可以解放啦~
3.解放application-*.xml
除了spring基础配置和webmvc配置外,可能还有些配置信息遗留在xml,比如说数据库连接信息,redis配置信息等等,剩下的这些配置迁移就简单多啦~
我们可以在基础包下新建一个config目录(叫啥名都行),然后再此包下新建各种Config类,比如说数据库的就DataBaseConfig,redis的就redisConfig(名称啥的都看个人习惯啦),然后直接java config就行啦,举个例子:
@Configuration
public class DataSourceConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean("datasourceAuth")
public DataSource datasourceAuth() {
BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
basicDataSource.setUrl(url);
basicDataSource.setUsername(userName);
basicDataSource.setPassword(password);
return basicDataSource;
}
}
上述代码配置了数据库信息,代替了xml文件中的<bean id='datasource' class=‘xxx’ .../>,用@Configuration注解表明此类是个配置类,需要被解析,但是spring不清楚它在哪里,所以需要我们在Root ApplicationContext中配置扫描路径@ComponentScan。
这样,把xml文件中剩余的信息都以类似这种方式迁移过来就ok啦,跑跑试试吧~
还可以进化
上面配置完成后我们其实已经完成了xml转java config的工作,其实我们还可以抛弃tomcat(不是真的抛弃,是引入插件,不需要再打包之后再扔到tomcat运行了,因为天亮了就先不写了~)
Tips
1.spring派生注解
能被spring扫描到的注解有很多,比如说@Component,@Service,@Controller,@Configuration,@Repository...这些个注解都是被@Component派生出来的(在其注解上包含@Component),派生出来就是想不同的地方有不同的区分,比如在controller层用@Controller,service层用@Service等,其实作用都是能够被spring扫描到
2.Full模式 & Litle模式
上述我们在配置数据库相关信息时,用的是@Configuration,被这个注解的类会被spring当做一个配置类,相当于一个xml配置文件,同时它还是个Full模式的注解,即:被spring扫描到配置类被@Configuration修饰时,会将其用enhancer增强,就是用cglib生成其代理类(听说在这个配置类下的@Bean可以是private/final 或者static的,不过应该没有这么干的),如果从网上搜索下spring full模式,大多都是说用cglib代理过后提高运行性能巴拉巴拉:
其实,它还有个隐藏的特性,是网上搜不到的~
敲黑板!!!这个真的从网上搜不到~
在spring容器初始化时,会将ConfiturationClassPostProcessor这个类注入到spring容器中,目的是帮助spring容器初始化,比如说扫描解析各种注解(具体可查看spring源码)。当发现被扫描到的类被@Configuration修饰时,会调用enhancer对象进行增强:
public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor, PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
...
省略部分代码
...
//对@Configuration标记的类进行enhancer增强
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
...
省略部分代码
...
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
...
省略部分代码
...
}
...
省略部分代码
...
}
可以看到在方法中调用了ConfigurationClassEnhancer对象的enhance方法:
class ConfigurationClassEnhancer {
...
省略部分代码
...
public Class<?> enhance(Class<?> configClass, ClassLoader classLoader) {
...
省略部分代码
...
Class<?> enhancedClass = this.createClass(this.newEnhancer(configClass, classLoader));
...
省略部分代码
...
return enhancedClass;
}
private Enhancer newEnhancer(Class<?> configSuperClass, ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(configSuperClass);
enhancer.setInterfaces(new Class[]{ConfigurationClassEnhancer.EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ConfigurationClassEnhancer.BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
public interface EnhancedConfiguration extends BeanFactoryAware {
}
...
省略部分代码
...
}
重点来了:在这个enhance方法中,调用了newEnhancer方法创建Enhancer对象,我们可以看到在创建enhancer对象时,set了一个接口ConfigurationClassEnhancer.EnhancedConfiguration.class,也就是说我们将来拿到的这个对象,继承了这个接口,那这个接口是干嘛的呢?它也在当前类中被定义出来,我们可以看到这个接口继承了BeanFactoryAware,是不是瞬间明白了些(不懂Aware的可以自行google)
总结下:被@Configuration标注的类,在被扫描到时会被spring容器进行增强,就是通过enhancer生成Class对象,而且此对象继承了Aware接口,拥有了获取bean容器的能力,在之后spring初始化bean时(调用getBean())会将容器对象注入到该对象中,使得该对象拥有了感知spring容器的功能。
思考题:为什么spring要对@Configuration如此兴师动众?欢迎解答~
未完待续。。。