多的人,花少的钱,用高的效率,学多的知识,早日实现 职业自由
SpringMVC:srv2bll&底层原理:Bean / MapStruct / SpringORM / log / scope / proxy

当前系列: Java Web开发 修改

开始通过ProdService将用户输入录入数据库……


SpringBean

复习:IoC和DI

SpringMVC是建立在Spring基础上的,除了SpringMVC,还有Springboot等框架。

Spring的一个核心功能,就是自动生成对象,或者说将对象的创建权交给Spring管理。

所以我们的代码可以写成这样:

public class RegisterControler {
	private IUserService userService;    //没有赋值

	@RequestMapping(method = RequestMethod.GET)
	public ModelAndView input(Model model)  {
		userService.getByName("fg");
如果没有Spring,userService没有被赋值,就会是null值,程序运行时就会报空指针异常(演示)

@AutoWired

可以告诉Spring,自动装配这个字段(给这个字段赋值一个实例)
@Autowired
private IUserService userService;

但什么类型的实例字段呢?演示:运行报错

需要在springmvc-servlet.xml中进行配置:

<bean class="srv.prod.UserService"></bean>

这样,Spring以后碰到IUserService的字段/变量,就会用srv.prod.UserService生成对象!这又被称之为解析(resolve)

@想一想@:freemarker的配置是怎么一回事?

演示:

  • 没有空指针异常
  • userService的类型

@AutoWired还可以放置在构造函数、属性、方法参数上……

@Autowired
public RegisterControler(IUserService userService) {
	this.userService = userService;
}

@Component

实际开发中,会有很多很多需要注入的类,一个一个的配置会非常累。所以SpringMVC为我们提供了一种简便方式:

  • 首先在可以被用作SpringBean的类上添加@Component注解
    @Service
    public class UserService implements IUserService {
  • 然后在springmvc-servlet.xml中添加一个节点,指定在package下自动扫描所有带有@Component注解的类,充做SpringBean
    <context:component-scan base-package="srv.prod" />

为了有更好的语义,Spring还提供了:

  • @Service:表示该类作为service使用
  • @Repository::表示该类作为repository使用
  • @Controller:现在明白了些什么吧?
    <context:component-scan base-package="controllers" />

@想一想@:如何利用这个特性快速切换prod和mock service?


从SRV到BLL

复习:为什么不要直接用entity做model

演示生成:


因为独立了entity和model,所以必然产生以下转化(映射):

  • 从entity到model
    @Override
    public UserModel getByName(String name) {
    	User user = userRepository.GetByName(name);
    	
    	UserModel userModel = new UserModel();
    	userModel.setId(user.getId());
    	userModel.setUsername(user.getUsername());
    	
    	return userModel;
    }
  • 从model到entity
    @Override
    public int Register(RegisterModel model) {
    	User user = new User();
    	user.setUsername(model.getUsername());
    	user.setPassword(model.getPassword());
    	
    	userRepository.Save(user);		
    	return user.getId();		
    } 


MapStruct

现目前最流行的DTO映射工具。最大的特色:不使用反射,而是自动生成进行映射源代码,以提高性能!

使用MapStruct的核心是声明这样的接口:

@Mapper
public interface UserConvert {
	//接口中的所有字段都是隐式的public,static和final
	UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
	
	User toUser(RegisterModel model);
	UserModel toUserModel(User entity);
}

然后直接使用这个接口的INSTANCE字段,调用接口方法:

UserModel userModel =  UserConvert.INSTANCE.toUserModel(user);
你肯定会奇怪,这个接口都没有实现类的嘛?不,有的!演示:

public class UserConvertImpl implements UserConvert {

    @Override
    public User toUser(RegisterModel model) {

实现这种效果,依赖于:

  • maven dependency
    <dependency>
    	<groupId>org.mapstruct</groupId>
    	<artifactId>mapstruct</artifactId>
    	<version>1.4.2.Final</version>
    </dependency>
  • pom.xml文件project - properties中添加:
    <m2e.apt.activation>jdt_apt</m2e.apt.activation>
    指示激活m2e-apt
  • m2e-apt:这是一个eclipse插件,可以在eclipse market下载安装;然后,还需要在项目属性中找到:Java Compiler - annotation processing,启动
  • 在pom.xml中指定编译时指定annotationProcessorPaths:
    <plugin>
    	<artifactId>maven-compiler-plugin</artifactId>
    	<version>3.8.0</version>
    	<configuration>
    		<source>1.8</source>
    		<target>1.8</target>
    		<annotationProcessorPaths>
    			<path>
    				<groupId>org.mapstruct</groupId>
    				<artifactId>mapstruct-processor</artifactId>
    				<version>${org.mapstruct.version}</version>
    			</path>
    		</annotationProcessorPaths>
    	</configuration>
    </plugin>
    成功之后:

apt(annotatioin processing tool):它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码。原理类似于Hibernate Criteria中的jpamodelgen。

了解更多

Spring项目中,我们可以省略掉:
//UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
而是在@Mapping上添加属性:
@Mapper(componentModel = "spring")
然后利用@AutoWired注解由Spring依赖注且实例对象:
@Autowired
private IUserRepository userRepository;
默认映射规则(不需要任何额外注解):
  • 按source和target字段名匹配
  • 如果targert中的字段在source中找不到,报警告:
    Unmapped target properties: "id, inviteCode, invitedBy, invitedCode".

@Mapping()中常用自定义配置:

  • 字段名不同
    @Mapping(target = "pwd", source = "password")
  • 双向映射
    User toUser(RegisterModel model);
    	
    //将toUser()上的规则“倒”过来用
    @InheritInverseConfiguration(name = "toUser")
    RegisterModel toRegister(User entity);
  • 更新:方法不需要返回值(void),用注解@TargetType标明需要被更新的对象
    void toArticle(EditModel model, @MappingTarget Article article);
    和“生成”不同,unmapped字段会被target保留。@想一想@:为什么?对比:
    EditModel model = new EditModel();
    model.setTitle("model: 飞哥飞哥我爱你");
    
    Article article = new Article();
    article.setId(98);
    article.setTitle("article:就像老鼠爱大米");
    
    //articleConvert.toArticle(model, article);
    article = articleConvert.toArticle(model);
    System.out.println(article.getTitle());
    System.out.println(article.getId());
  • ignore:如果不想看到警告,或者某些特殊场合不要求映射某个字段(该字段另行赋值)
    @Mapping(target = "id", ignore = true)
  • 如果单个字段需要配置,可以使用Mapping;多个字段,使用Mappings
    @Mappings({
    	@Mapping(target = "pwd", source = "password"),
    	@Mapping(target = "id", ignore = true)	
    })
还可以配合:


SpringORM

Spring还集成了数据库访问功能。和ORM(比如Hibernate)集成的组件就是hibernate-orm。

需要通过maven引入:
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-orm</artifactId>
	<version>5.0.2.RELEASE</version>
</dependency>
其他hibernate相关依赖(包括mysql、连接池、缓存、log等)引入,略

在SpringORM中,引入了LocalContainerEntityManagerFactoryBean类,我们就可以直接(在repository中)通过依赖注入拿到

@Autowired	
private LocalContainerEntityManagerFactoryBean emFactoryContainer;
然后通过LocalContainerEntityManagerFactoryBean就可以获得EntityManagerFactory:
EntityManagerFactory entityManagerFactory = emFactoryContainer.getNativeEntityManagerFactory();
//或者使用getObject()方法
EntityManagerFactory entityManagerFactory = emFactoryContainer.getObject();
但从@Autowired就应该知道,LocalContainerEntityManagerFactoryBean是一个需要配置的SpringBean:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
	<property name="PersistenceXmlLocation"
		value="/WEB-INF/persistence.xml" />
</bean>
简便起见,我们直接利用PersistenceXmlLocation指定hibernate的persistence.xml配置文件路径,完成所有hibernate相关的配置。

演示:entityManagerFactory不会null


log4j适配

SpringMVC(和Hibernate一样)可以通过添加log4j.xml文件放到项目的Java Path目录下,适配log4j。

只是要在我们原来的log4j.xml中添加一个root logger配置才能记录Hibernate以外的信息:

<root level="INFO">
	<AppenderRef ref="FileSpring" />
</root>

实际上log4j中,logger按树状结构组织,root就是根节点,所有日志信息都应该录入到root,除非子节点logger上声明了additivity="false"

<Logger name="org.hibernate.SQL" level="debug" additivity="false">
演示查看:Spring的日志


Scope

@想一想@:每次请求,都会生成一个LocalContainerEntityManagerFactoryBean对象,再生成一个EntityManagerFactory,会不会造成巨大的性能浪费?

这个问题涉及到SpringBean生命周期,可选值包括:

  • singleton:默认配置,即Spring(容器)只会创建唯一的一个全局共享实例
  • prototype:每一次将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法,都会产生一个新的bean实例
  • request:表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
  • session:作用域表示该针对每一次HTTP请求都会产生一个新的bean
PS:request和session自然只在Web项目中生效。

所以LocalContainerEntityManagerFactoryBean默认就是singleton的,不用担心!

演示:

System.out.println("emFactoryContainer:"+ emFactoryContainer);				
System.out.println("-----------");

如何改变默认的scope呢?可以通过注解的方式指定

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Article extends Entity{

注意这里没有直接使用字符串,而是常量。另外两个web项目的scope由WebApplicationContext指定。

或者通过xml:

<bean id="entityManagerFactory" scope="prototype"

但是,比如我们在repository方法中验证,会发现不对劲!即使article的scope已经被设置为prototype,它在每次被调用时仍然是同一个对象?

@想一想@:为什么呢?因为:

  1. article是repository中的一个字段
  2. repository本身是singleton的

我们自始至终都是在调用同一个repository对象的article字段,这个字段当然没法变化!

演示:将Article注入到scope标记为prototype的Controller中:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RegisterControler {	
那如果我们就要在一个singleton的SpringBean中注入一个prototype的SpringBean呢?

一种办法是通过ApplicationContext的getBean()方法显式的获取SpringBean,触发scope=prototype机制

@Autowired
private ApplicationContext context;
article = context.getBean(Article.class);
System.out.println("article:"+article);


Proxy

还有另外一种方法,就是在指定scope的时候同时指定proxy(代理):

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, 
	proxyMode = ScopedProxyMode.TARGET_CLASS)
演示:proxyMode默认为NO/Default,也就是不使用代理。


所谓“使用代理”,和之前Hibernate的lazy load实现类似,就是:

  • 生成一个SpringBean的时候,只生成该bean的一个proxy对象
  • 直到真正使用这个bean时,才会(使用Application.getBean()等)真正获取它

所以,……

除了不代理(default和no),ScopedProxyMode还有两种:

  • INTERFACES:动态代理,只针对接口,利用反射生成其实现类,
  • TARGET_CLASS:可针对普通类,使用CGLIB操作字节码,生成其子类,一般来说性能更高
proxy机制,也是Spring能够AOP的基础……

作业

  1. 按课堂演示搭建好项目框架,理解Spring中Bean(DI)和Proxy(AOP)的概念。
SpringMVC Hibernate 架构搭建
觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 Java Web开发 中继续学习:

我们的 特色

  • 面向就业
  • 线上线下结合
  • 同学互助
  • 师生共赢

报班 QQ群:273534701

  • 获取视频资料
  • 要求作业点评
  • 参加阶段性测试
  • 模拟面试