Spring Boot常见企业开发场景应用、自动配置原理结构分析(二)https://developer.aliyun.com/article/1423060
- 这里导入了ActiveMQ的起步依赖
- 编写DAO
public interface UserRepository extends JpaRepository{ } 1. 编写Service public interface UserService { /** * 根据ID获取用户 * @param i * @return */ User get(int id); /** * 查询所有用户 * @return */ List findAll(); /** * 新增用户 * @param user */ void add(User user); } @Service @Transactional public class UserServiceImpl implements UserService{ @Autowired private UserRepository userRepository; @Override public User get(int id) { return userRepository.findOne(id); } @Override public List findAll() { return userRepository.findAll(); } @Override public void add(User user) { userRepository.save(user); } } 1. 编写Controller @Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Autowired private JmsTemplate jmsTemplate; @RequestMapping("/findUserList") public String findUserList(Model model) { List list = userService.findAll(); model.addAttribute("list", list); return "/index.jsp"; } @RequestMapping("/add") public String add(User user) { userService.add(user); final List userList = userService.findAll(); jmsTemplate.send("queue_page", new MessageCreator() { @Override public Message createMessage(Session session) throws JMSException { return session.createTextMessage(JSON.toJSONString(userList)); } }); return "redirect:/user/findUserList"; } } 1. 在添加一个用户,往AMQ中发送一条queue消息 2. 编写Listener @Component public class PageGeneratorListener { @Value("${freemarker.output_path}") private String OUTPUT_PATH; @Autowired private Configuration configuration; @JmsListener(destination="queue_page") public void genHtml(String userListStr) throws Exception { Template template = configuration.getTemplate("user_list.ftl"); List userList = JSON.parseArray(userListStr, User.class); Map map = new HashMap(); map.put("list", userList); template.process(map, new FileWriter(OUTPUT_PATH + "user_list.html")); } } 1. 监听AMQ中queue_page队列的消息,如果接收到消息,使用FreeMarker重新生成一个HTML页面在服务器端 2. 编写入口 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); } @Bean(name="datasource") @Primary @ConfigurationProperties(prefix="c3p0") public ComboPooledDataSource c3p0DataSource() throws PropertyVetoException { ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); return comboPooledDataSource; } } 1. 编写配置文件 server.port=10086 server.context-path=/ freemarker.output_path=G:/workspace/free_test/t51/src/main/webapp/ spring.activemq.broker-url=tcp://localhost:61616 spring.activemq.user=admin spring.activemq.password=admin c3p0.driverClass=com.mysql.jdbc.Driver c3p0.jdbcUrl=jdbc:mysql:///springboot c3p0.user=root c3p0.password=000000
- 构建基于Spring Security访问控制应用程序
- 导入依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.14.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> </dependencies> a. 导入了Spring Security的起步依赖spring-boot-starter-security b. 编写DAO public interface UserRepository extends JpaRepository<User, Integer>{ // 根据用户名查询用户 User findByUsername(String username); }
- 编写Service
public interface UserService { /** * 根据ID获取用户 * @param i * @return */ User get(int id); /** * 查询所有用户 * @return */ List<User> findAll(); /** * 新增用户 * @param user */ void add(User user); } @Service @Transactional public class UserServiceImpl implements UserService{ @Autowired private UserRepository userRepository; @Override public User get(int id) { return userRepository.findOne(id); } @Override public List<User> findAll() { return userRepository.findAll(); } @Override public void add(User user) { userRepository.save(user); } }
- 编写Spring Security登录验证用户服务
@Service @Transactional public class AuthUserService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if(StringUtils.isNotBlank(username)) { // 从数据库中获取用户 User user = userRepository.findByUsername(username); if(user != null) { // 创建用户、加载角色 List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); return new org.springframework.security.core.userdetails.User( username, user.getPassword(), authorities); } else { throw new UsernameNotFoundException("用户名不存在"); } } return null; } }
- 编写Controller
@Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/findUserList") public String findUserList(Model model) { List<User> list = userService.findAll(); model.addAttribute("list", list); return "/index.jsp"; } @RequestMapping("/add") public String add(User user) { userService.add(user); return "redirect:/user/findUserList"; } }
- 编写应用入口
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); } @Bean(name="datasource") @Primary @ConfigurationProperties(prefix="c3p0") public ComboPooledDataSource c3p0DataSource() throws PropertyVetoException { ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); return comboPooledDataSource; } }
- 编写配置文件
server.port=10086 server.context-path=/ freemarker.output_path=G:/workspace/free_test/t51/src/main/webapp/ c3p0.driverClass=com.mysql.jdbc.Driver c3p0.jdbcUrl=jdbc:mysql:///springboot c3p0.user=root c3p0.password=000000
- 编写页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>JSP测试</title> </head> <body> <table border="1"> <form action="<%=request.getContextPath()%>/user/add" method="post"> <tr> <td>用户名:<input type="text" name="username"/></td> <td>密码:<input type="text" name="password"/></td> <td><input type="submit" value="新增"/></td> </tr> </form> </table> <table border=1> <tr> <th>ID</th> <th>用户名</th> <th>密码</th> </tr> <c:forEach items="${list}" var="user"> <tr> <td>${user.id}</td> <td>${user.username}</td> <td>${user.password}</td> </tr> </c:forEach> </table> </body> </html>
- 访问http://localhost:10086/user/findUserList,弹出登录对话框,输入数据库中任意的用户名和密码登录即可。
构建基于Dubbox分布式架构应用程序
启动ZooKeeper
编写服务提供者
- 导入依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.14.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.cjoop</groupId> <artifactId>spring-boot-starter-dubbox</artifactId> <version>0.0.1</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.7</version> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> </dependencies>
- 这里导入了Dubbox的起步依赖,这样配置起来很方便。
- 导入实体类,注意因为要在网络上传输,所以要实现Serializable接口
@Entity @Table(name="t_user") public class User implements Serializable{ @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private String username; private String password; public User() { } public User(Integer id, String username, String password) { this.id = id; this.username = username; this.password = password; } public User(String username, String password) { this(null, username, password); } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User [id=" + id + ", username=" + username + ", password=" + password + "]"; } }
- 编写DAO
public interface UserRepository extends JpaRepository<User, Integer>{ User findByUsername(String username); }
- 编写Service(注意:请使用Dubbox中的@Service,否则服务将不会被发布)
public interface UserService { /** * 根据ID获取用户 * @param i * @return */ User get(int id); /** * 查询所有用户 * @return */ List<User> findAll(); /** * 新增用户 * @param user */ void add(User user); } @Service @Transactional public class UserServiceImpl implements UserService{ @Autowired private UserRepository userRepository; @Override public User get(int id) { return userRepository.findOne(id); } @Override public List<User> findAll() { return userRepository.findAll(); } @Override public void add(User user) { userRepository.save(user); } }
- 编写入口
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); } @Bean(name="datasource") @Primary @ConfigurationProperties(prefix="c3p0") public ComboPooledDataSource c3p0DataSource() throws PropertyVetoException { ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); return comboPooledDataSource; } }
- 编写配置文件
server.port=10086 server.context-path=/ #配置Dubbo包扫描,自动将带有Service注解的类发布为Dubbox服务 dubbo.annotation.package=com.itheima.springboot.service dubbo.application.name=com.itheima.user.service c3p0.driverClass=com.mysql.jdbc.Driver c3p0.jdbcUrl=jdbc:mysql:///springboot c3p0.user=root c3p0.password=000000
- 启动应用,如果服务发布成功,可以在Dubbo Admin上看到已经发布的服务
编写服务消费者
- 导入依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.14.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <dependency> <groupId>com.cjoop</groupId> <artifactId>spring-boot-starter-dubbox</artifactId> <version>0.0.1</version> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.7</version> </dependency> <dependency> <groupId>com.github.sgroschupf</groupId> <artifactId>zkclient</artifactId> <version>0.1</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> </dependency> </dependencies>
- 引入实体类
public class User { private Integer id; private String username; private String password; public User() { } public User(Integer id, String username, String password) { this.id = id; this.username = username; this.password = password; } public User(String username, String password) { this(null, username, password); } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public String toString() { return "User [id=" + id + ", username=" + username + ", password=" + password + "]"; } } 1. 引入Service接口 public interface UserService { /** * 根据ID获取用户 * @param i * @return */ User get(int id); /** * 查询所有用户 * @return */ List<User> findAll(); /** * 新增用户 * @param user */ void add(User user); } 1. 编写Controller @Controller @RequestMapping("/user") public class UserController { @Reference private UserService userService; @RequestMapping("/findUserList") public String findUserList(Model model) { List<User> list = userService.findAll(); model.addAttribute("list", list); return "/index.jsp"; } @RequestMapping("/add") public String add(User user) { userService.add(user); return "redirect:/user/findUserList"; } }
- 编写入口
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class); } }
- 编写配置文件
server.port=10087 server.context-path=/ dubbo.annotation.package=com.itheima.springboot.controller dubbo.application.name=com.itheima.user.web
- 编写页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>JSP测试</title> </head> <body> <table border="1"> <form action="<%=request.getContextPath()%>/user/add" method="post"> <tr> <td>用户名:<input type="text" name="username"/></td> <td>密码:<input type="text" name="password"/></td> <td><input type="submit" value="新增"/></td> </tr> </form> </table> <table border=1> <tr> <th>ID</th> <th>用户名</th> <th>密码</th> </tr> <c:forEach items="${list}" var="user"> <tr> <td>${user.id}</td> <td>${user.username}</td> <td>${user.password}</td> </tr> </c:forEach> </table> </body> </html>
- 访问http://localhost:10087/user/findUserList
Spring Boot自动配置原理结构分析
通过实践,可以隐约感觉到。Spring Boot相当于基于Maven和Spring做了一个开发平台,使用这个平台可以减少配置、快速开发。那么Spring Boot到底是如何做到的呢?
回想,我们开发的第一个案例。我们只是往pom.xml中进行简单配置,就可以开始进行Spring开发了。
然后,更新项目可以看到,在Maven的依赖中,导入了很多的JAR包。
这两段配置怎么这么神奇,它到底做了什么?先来看看这个spring-boot-starter-parent的pom文件。
先是定义了很多的常量
里面还定义了一些依赖的版本锁定、插件的版本锁定。但没有导入具体的JAR包。
这个spring-boot-starter-parent从spring-boot-dependencies中继承,这个pom文件中定义了大量的版本号、以及版本锁定。
这些版本应该都是做过兼容性测试的,一般不要去修改否则出现不兼容问题是比较麻烦的。
再看看spring-boot-starter这个依赖
这个starter起步依赖中包含了导入了spring-boot依赖,spring-boot依赖导入了spring framework的核心依赖。
spring-boot-autoconfigure依赖,spring-boot-starter-logging会自动日志相关的依赖。
这个spring-boot-autoconfigure里面包含了很多玄机。
我猜想,Spring Boot是通过自动配置,来帮助我们自动创建了很多bean在IOC容器中。
所以接下来要回答两个问题:
1、Spring创建了哪些Bean?
2、因为我们之前都是通过编写很多的配置文件来创建和配置bean的,那Spring是如何读取配置来创建这些bean的?
接着猜:
以前的配置信息肯定还有,Spring不应该是把之前假设的平台全部推倒,而是把常用的配置整合起来了,就省去了我们自己来手动配置的过程。那么,我猜:每一个Starter都会有其对应的配置信息。我们来找一找spring-boot-starter的配置信息。
这个autoconfigure里面有大量的包,而且命名方式是以技术组件来命名的
要知道Spring Boot创建了哪些bean,直接去看自动配置包中,以Configuration结尾的类就可以了。要想看看具体application.properties中应该配置哪些属性,直接去看以properties文件结尾的类就可以了。
来看一段自动配置的源代码,下面这段代码是从JmsAutoConfiguration中截取出来的。
@Configuration @ConditionalOnClass({ Message.class, JmsTemplate.class }) @ConditionalOnBean(ConnectionFactory.class) @EnableConfigurationProperties(JmsProperties.class) @Import(JmsAnnotationDrivenConfiguration.class) public class JmsAutoConfiguration { @Configuration protected static class JmsTemplateConfiguration { private final JmsProperties properties; private final ObjectProvider<DestinationResolver> destinationResolver; private final ObjectProvider<MessageConverter> messageConverter; public JmsTemplateConfiguration(JmsProperties properties, ObjectProvider<DestinationResolver> destinationResolver, ObjectProvider<MessageConverter> messageConverter) { this.properties = properties; this.destinationResolver = destinationResolver; this.messageConverter = messageConverter; } @Bean @ConditionalOnMissingBean @ConditionalOnSingleCandidate(ConnectionFactory.class) public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) { JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory); jmsTemplate.setPubSubDomain(this.properties.isPubSubDomain()); DestinationResolver destinationResolver = this.destinationResolver .getIfUnique(); if (destinationResolver != null) { jmsTemplate.setDestinationResolver(destinationResolver); } MessageConverter messageConverter = this.messageConverter.getIfUnique(); if (messageConverter != null) { jmsTemplate.setMessageConverter(messageConverter); } JmsProperties.Template template = this.properties.getTemplate(); if (template.getDefaultDestination() != null) { jmsTemplate.setDefaultDestinationName(template.getDefaultDestination()); } if (template.getDeliveryDelay() != null) { jmsTemplate.setDeliveryDelay(template.getDeliveryDelay()); } jmsTemplate.setExplicitQosEnabled(template.determineQosEnabled()); if (template.getDeliveryMode() != null) { jmsTemplate.setDeliveryMode(template.getDeliveryMode().getValue()); } if (template.getPriority() != null) { jmsTemplate.setPriority(template.getPriority()); } if (template.getTimeToLive() != null) { jmsTemplate.setTimeToLive(template.getTimeToLive()); } if (template.getReceiveTimeout() != null) { jmsTemplate.setReceiveTimeout(template.getReceiveTimeout()); } return jmsTemplate; } }
这里面有几个很重要的注解
- @ConditionalOnClass
这个注解表示,如果检测到当前的JVM中加载了Message.class, JmsTemplate.class,就加载该Java Config配置。 - @ConditionalOnMissingBean
这个注解表示,如果IOC容器中没有检测到这个类型的Bean,就创建一个。如果检测到了,那么就不创建了。所以,如果我们自己配置了JmsTemplate这个Bean,那这个自动配置就失效了 - @ConditionalOnBean
这个注解表示,如果IOC容器中有指定类型的Bean,才加载Java Config配置。例如:这里如果检测到容器中有ConnectionFactory类型的Bean,才会创建JmsTemplate。 - @EnableConfigurationProperties
这个注解表示将以Properties结尾的配置类,加载到当前的自动配置类中。一般的Starter中的Properties类都可以从application.properties中的指定前缀的属性加载。从而让我们可以轻松的自定义里面的配置。 - @Import
导入其他的Java Config配置,相当于之前XML配置中的import。
结尾
大家应该有一个直接的体会,Spring Boot真的让我们的工作更加轻松了。以前要写的很多配置、导很多的依赖,现在只需要短短几行代码就可以解决问题。而且,不再需要我们去考虑版本之间的兼容问题了。相信,很快大家编写的应用都会切换到Spring Boot。它将让我们将更多的精力放在编写、设计结构、算法上。