SpringBoot企业级开发(SpringSecurity安全控制+pringBatch批处理+异步消息+系统集成SpringIntegration)
Spring Security
多个过滤器来实现所有安全的功能,只需要注册一个特殊的DelegatingFilterProxy过滤器到WebAppliationInitializer即可
实际使用中需要让自己的Initializer类继承AbstractSecurity WebApplicationInitializer抽象类即可。
AbstractSecurityWebApplicationInitializer实现了WebApplicationInitializer接口,并通过onStartup方法调用
①、依赖
spring-boot-starter-data-jpa
spring-boot-starter-security
spring-boot-starter-thymeleaf
ojdbc6
thymeleaf-extras-springsecurity4
②、配置
spring.datasource.drivuerClassName=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc\:oracle\:thin\:@localhost\:1521\:xe
spring.datasource.username=boot
spring.datasource.password=bootlogging.level.org.springframework.security=INFO
spring.thymeleaf.cache=falsespring.jpa.hibernate.ddl-auto=update #自动生成用户表、角色表以及关联表
spring.jpa.show-sql=true
将bootstrap.min.css放置在src/main/resources/static/css下,此路径默认不拦截
③、用户和角色
@Entity
public class SysUser implements UserDetails{//实现该接口,用户实体即为Spring Security使用的用户private static final long serialVersionUID = 1L;@Id@GeneratedValueprivate Long id;private String username;private String password;@ManyToManay(cascade = {CascadeType.REFRESH},fetch=FetchType.EAGER)private List<SysRole> roles;@Overridepublic Colletion<? extends GrantedAuthority> getAuthorities(){//将用户的角色作为权限List<GrantedAuthority> auths = new ArrayList<>();List<SysRole> roles = this.getRoles();for(SysRole role:roles){auths.add(new SimpleGrantedAuthority(role.getName()));}return false;}@Overridepublic boolean isAccountNonExpired(){return true;}@Overridepublic boolean isAccountNonLocked(){return true;}@Overridepublic boolean isCredentialsNonExpired(){return true;}@Overridepublic boolean isEnabled(){return true;}//省略get\set方法
}
@Entity
public class SysRole{@Id@GeneratedValueprivate Long id;private String name;//省略getter、setter方法
}
④、Dao数据访问
public interface SysUserRepository extends JpaRepository<SysUser,Long>{SysUser findByUsername(String username);
}
⑤、自定义Service服务
public class CustomUserService implements UserDetailsService{//自定义需要实现该接口@AutowiredSysUserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(){//获得用户,直接返回给SprngSecuritySysUser user = userRepository.findUsername(username);if(user == null){throw new UsernameNotFoundException("用户名不存在");}return user;}
}
⑥、Configuration配置SpringMVC
注册访问/login转向login.html
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{//访问http://localhost:8080 将会自动跳转到登录页面@Overridepublic void addViewController(ViewControllerRegistry registry){registry.addViewController("/login").setViewName("login");}
}
扩展Spring Security配置,所有请求需要认证登录后才能方法(登录和注销)
SpringBoot针对Spring Security的自动配置通过SecurityAutoConfiguration和SecurityProperties来配置
SpringBoot做了很多配置,需要扩展配置只需要继承WebSecurityConfigurerAdapter类,无需@EnableWebSecurity注解
//相关的安全配置
@Configuration
//@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{@BeanUserDetailsService customUserService(){//注册该beanreturn new CustomUserService();}/**用户认证①、添加内存中的用户,并且可以给用户指定角色权限auth.inMemoryAuthentication().withUser("wyf").password("wyf").roles("ROLE_ADMIN").and().withUser("wisely").password("wisely").roles("ROLE_USER");②、JDBC中的用户直接指定dataSource即可这里的SpringSecurity默认了数据库接口,通过jdbcAuthentication源码可以得出JdbcDaoImpl中定义了默认的用户及角色权限@AutowiredDataSource dataSource;@Overrideprotected void configure(AuthenticationManagerBuilder auth)throws Exception{auth.jdbcAuthentication().dataource(dataSource);//也可以i定义查询用户和权限的SQLauth.jdbcAuthentication().dataource(dataSource).usersByUsernameQuery("select username,password,true from myusers where username=?").authoritiesByUsername("select username,role from roles where username=?");}*///③、通用用户,实现UerDetailsService接口,以上JDBC用户和内存用户就是其实现@Overrideprotected void configure(AuthenticationManagerBuilder auth)throws Exception{auth.userDetailsService(customUserServie());//添加自定义认证,注册}/**请求授权,匹配了一下请求路径,需要针对当前用户的信息对请求路径进行安全处理Ⅰ、antMatchers 使用Ant风格的路径皮喷Ⅱ、regexMatchers 使用正则表达式匹配路径Ⅲ、anyRequest 匹配所有路径安全处理方法:access(String) SpringEL表达式结果为true时可访问anonymous() 匿名访问denyAll() 用户不能访问fullyAuthenticated() 用户完全认证可访问(非remeber me下自动登录)hasAnyAuthority(String..) 如果用户有参数,则其中任一权限可访问hasAnyRole(String..) 如果用户有参数,则其中任一角色可访问hasAuthority(String) 如果用户有参数,则其权限可访问hasIpAddress(String) 如果用户来自参数中的IP则可访问hasRole(String) 若用户有参数中的角色可访问permitAll() 用户可任意访问rememberMe() 运行通过remember-me登录的用户访问authenticated() 用户登录后可访问http.authorizeRequests().antMatchers("/admin/**").hasRole("ROLE_ADMIN").antMatchers("/user/**").hasAnyRole("ROLE_ADMIN","ROLE_USER").anyRequst().authenticated(); //其余所有请求都需要认证登录后才可以访问*/@Overrideprotected void configure(HttpSecurity http) throws Exception{http.authorizeRequests()//开始请求权限配置.anyRequest().authenticated()//所有请求需要认证登陆后才能访问.and().formLogin() //定制登录操作.loginPage("/login")//登录页面的访问地址.defaultSuccessUrl("/index")//指定登录成功后转向的页面.failureUrl("/login?error")//登录失败转向的页面.permitAll()//定制登录行为,登录页面可以任意访问.and().rememberMe()//开启cookie存储用户信息.tokenValiditySeconds(1209600)//指定cookie有效期2个星期.key("myKey")//指定cookie中的私钥.and().logout().logout("/costom-logout")//指定注销的URL路径.logoutSuccessUrl("/logout-success")//指定注销成功后转向的页面.permitAll();//定制注销行为,注销请求可任意访问}
}
⑦、页面
<!--Thymeleaf提供了Spring Security的标签支持-->
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
获得当前用户名
sec:authentication=“name”
当前用户觉得为ROLE_ADMIN时才现实标签内容
sec:authorize=“hasRole(‘ROLE_ADMIN’)”
注销的默认路径/logout,需要通过POST请求提交
th:action=“@{/logout}” method=“post”
Spring Batch
处理大量数据的框架,用来读取大量数据,然后进行一定处理后输出成指定的形式
①、依赖
spring-boot-starter-jdbc
spring-boot-starter-batch(因为使用Oracle,所以排除hsqldb)
spring-boot-starter-web
ojdbc6
hibernate-validator 数据校验
数据准备:
src/main/resources/people.csv中添加 jordan,23,非汉族,芝加哥 等多条类似的数据
数据表sql
id name age nation address
②、实体类
public class Person{@Size(max=4,min=2) //使用JSR-303校验数据private String name;private int age;private String nation;private String address;//省略get和set方法
}
③、数据处理及校验
public class CsvIntemProcessor extemds ValidatingItemProcessor<Person>{@Overridepublic Person process(Person item) throws ValidationException{super.process(item);//调用自定义校验器if(item.getNation().equals("汉族")){item.setNation("01");}else{item.setNation("02");}return item;}
}
public class CsvBeanValidator<T> implements Validator<T>,InitializingBean{private javax.validation.Validator validator;@Overridepublic void afterProperitesSet()throws Exception{//使用JSR-303校验数据ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();validator = validatorFactory.usingContext().getValidator();}@Overridepublic void validate(T value)throws ValidationException{//使用Validator的validate的方法校验数据Set<ConstraintViolation<T>> constraintViolations = validator.validate(value);if(constraitViolations.size()>0){StringBuilder message = new StringBuilder();for(ConstraintVilation<T> constraintViolation : constraintViolations){message.append(constraintViolation.getMessage() + "\n");}throw new ValidationException(message.toString());}}
}
④、Job监听
public class CsvJobListener implements JobExecutionListener{long startTime;long endTime;@Overridepublic void beforeJob(JobExecution jobExecution){startTime = System.currentTimeMillis();System.out.println("任务处理开始");}@Overridepublic void afterJod(JobExecution jobExecution){startTime = System.currentTimeMillis();System.out.println("任务处理结束");System.out.println("耗时:"+(endTime-startTime)+"ms");}
}
⑤、配置
- JobRepository 用来注册Job容器
- JobLauncher 用来启动Job的接口
- Job 实际执行的任务,包含一个或多个Step
- Step 包含ItemReader ItemProcessor和ItemWriter
- ItemReader 用来读取数据的接口
- ItemProcessor 用来处理数据的接口
- ItemWriter 用来输出数据的接口
@Configuration
@EnableBatchProcessing //开启批处理支持
public class CsvBatchConfig{@Beanpublic ItemReader<Person> reader()throws Exception{FlatFileItemReader<Person> reader = new FlatFileItemReader<>();//读取文件reader.setResource(new ClassPathResource("people.csv"));//设置csv文件路径reader.setLineMapper(new DefautLineMapper<Person>(){{setLineTokenizer(new DelimitedLineTokenizer(){{setNames(new String[]{"name","age","nation","address"});}});setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>(){{setTargetType(Person.class);}});}});return reader;}@Beanpublic ItemProcessor<Person,Person> processor(){CsvItemProcessor processor = new CsvItemProcessor();//使用自定义的ItemProcessor的实现CsvItemProcessorprocessor.setValidator(csvBeanValidator());//为processor指定校验器为CsvBeanValidatorreturn processor;}@Beanpublic ItemWriter<Person> write(DataSource dataSource){//SpringBoot让已有的容器bean,注入JdbcBatchItemWriter<Person> writer = new JdbcBatchItemWriter<>();//使用JDBC批处理的jdbcBatchItemWriter来写数据到数据库writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());String sql = "insert into person"+"(id,name,age,nation,address)"+"values(hibernate_sequence.nextval,:name,:age,:nation,:address)";wirter.setSql(sql);//设置要执行批处理的SQL语句writer.setDataSource(dataSource);return writer;}@Beanpublic JobRepository jobRepository(DataSource dataSource,PlatformTransactionManager transactionManager)throws Exception{JobRepositoryFactoryBean jobRepositoryFactoryBean = new JopRepositoryFactoryBean();jobRepositoryFactoryBean.setDataSource(dataSource);jobRepositoryFactoryBean.setTransactionManager(transactionManager);jobRepositoryFactoryBean.setDatabaseType("oracle");return jobRepositoryFactoryBean.getObject();}@Beanpublic SimpleJobLauncher jobLauncher(DataSource dataSource,PlatformTransactionManager transactionManager){SimpleJobLauncher jobLauncher = new SimpleJobLauncher();jobLauncher.setJobRepository(jobRepository(dataSource,transactionManager));return jobLauncher;}@Beanpublic Job importJob(JobBuilderFactory jobs,Step s1){return jobs.get("importJob").incrementer(new RunIdIncrementer()).flow(s1)//为Job指定Step.end().listener(csvJobListener())//绑定监听器.build();}@Beanpublic Step step1(StepBuilderFactory stepBuilderFactory,ItemReader<Person> reader,ItemWriter<Person> writer,ItemProcessor<Person,Person> processor){return stepBuilderFactory.get("step1").<Person,Person>chunk(65000)//批处理,每次提交65000条数据.reader(reader)//为step绑定那个reader.processor(processor)//给step绑定processor.writer(writer)//给step绑定writer.build();}@Beanpublic CsvJobListener csvJobListener(){return new CsvJobListener();}@Beanpublic Validator<Person> csvBeanValidator(){return new CsvBeanValidator<Person>();}
}
⑥、运行
SpringBoot会自动初始化SpringBatch数据库,并将CSV中的数据导入到数据库中
监听器效果
数据已导入且做转换处理
⑦、手动触发任务
注释调CsvBatchConfig类的@Configuration注解,让此类不在起效
新建TriggerBatchConfig配置类,内容同CsvBatchConfig完全保持一致
修改定义ItemReader
@Bean
@StepScope
public FlatFileItemReader<Person> reader(@Value("#{jobParameters['input.file.name']}") String pathToFile)throws Exception{FlatFileItemReader<Person> reader = new FlatFileItemReader<>();//reader.setResource(new ClassPathResource(pathToFile));//reader.setLineMapper(new DefautLineMapper<Person>(){{setLineTokenizer(new DelimitedLineTokenizer(){{setNames(new String[]{"name","age","nation","address"});}});setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>(){{setTargetType(Person.class);}});}});return reader;
}
控制定义
@RestController
public class DemoController{@AutowiredJobLauncher jobLauncher;@AutowiredJob importJob;public JobParameters jobParameters;@RequestMapping("/imp")public String imp(String fileName)throws Exception{String path = fileName + ".csv";jobParameters = new JobParametersBuilder().addLong("time",System.currentTimeMillis()).addString("input.file.name",path).toJobParameters();jobLauncher.run(importJob,jobParameters);return "ok";}}
此时如果关闭SpringBoot为我们自动执行了Job的配置,在application.properties里使用如下代码
spring.batch.job.enabled=false
访问http://localhost:8080/imp?fileName=people可获得相同的数据导入效果
异步消息
Spring通过@EnableJms @EnableRabbit开启支持
SpringBoot自动开启了@EnableJms @EnableRabbit
JMS
①、安装ActiveMQ
docker run -d -p 61616:61616 -p 8161:8161 cloudesire/activemq
其中61616是消息代理的端口
8161是ActiveMQ管理界面端口 http://localhost:8161打开ActiveMQ的管理界面 账号密码admin/admin
也可以将ActiveMQ内嵌在程序里依赖activemq-broker
②、依赖
spring-boot-starter
spring-jms
activemq-client
application.properties配置ActiveMQ消息代理
spring.activemq.broker-url=tcp://localhost:61616
③、消息定义
public class Msg implements MessageCreator{@Overridepublic Message createMessage(Session session) throws JMSException{return session.createTextMessage("测试消息");}
}
④、消息发送及目的地
@SpringBootApplication
public class ChApplication implements CommandLineRunner{//SpringBoot提供的接口@AutowiredJmsTemplate jmsTemplate;//注入SpringBoot配置好的beanpublic static void main(String[] args){SpringApplication.run(ChApplication.class,args);}@Overridepublic void run()throws Exception{jmsTemplate.send("my-destination",new Msg());//向my-destination目的地发送Msg的消息}
}
⑤、消息监听
@Component
public class Receiver{@JmsListener(destination="my-destination")//Spring4.1新特性,监听的目的地,接收消息public void receiveMesssage(String message){Systemm.out.println("接收到:<" + message +">");}
}
⑥、运行
AMQP
①、安装RabbitMQ
先下载安装erlang http://www.erlang.org/download.html
下载RabbitMQ https://www.rabbitmq.com/download.html
docker run -d -p 5672:5672 -p 15672:15672 rabbitmq:3-management
5672属于消息代理的端口
15672属于管理界面的端口 http://localhost:15672 guest/guest
②、依赖
spring-boot-starter-amqp
SpringBoot默认Rabbit注解为localhost、端口号5672
无需为SpringBoot的application.properties配置RabbitMQ的连接信息
③、发送信息及目的地
@SpringBootApplication
public class ChApplication implements CommandLineRunner{@AutowiredRabbitTemplate rabbitTemplate;//注入SpringBoot配置好的public static void main(String[] args){SpringApplication.run(ChApplication.class,args);}@Beanpublic Queue wiselyQueue(){return new Queue("my-queue");//定义目的地队列}@Overridepublic void run(String... args)throws Exception{//向队列my-queue发送消息rabbitTemplate.convertAndSend("my-queue","来自RabbitMQ的问候");}}
④、消息监听
@Component
public class Receiver{@RabbitListener(queues = "my-queue")public void receiveMessage(String message){System.out.println("Received<" + message + ">");}
}
⑤、运行
系统集成Spring Integration
解决不同系统之间交互的问题,通过异步消息驱动来达到系统交互时系统之间的松耦合。
①、读取https://spring.io/blog.atom的新闻聚合文件,atom是一种xml文件,且格式是固定的
将读取到的消息通过分类(Category),将消息转到不同的消息通道
将分类为releases和engineering的消息写入磁盘,将分类为news的消息通过邮件发送
②、依赖
spring-boot-starter-integration
spring-boot-starter-mail
Spring Integration对atom及mail的支持:
spring-integration-feed
spring-integration-mail
②、读取流程(入口类完成)
@Value("https://spring.io/blog.atom")//获得资源
Resource resource@Bean(name=PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller(){//配置默认的轮询方式return Pollers.fixedRate(500).get();
}@Bean
public FeedEntryMessageSource feedMessageSource() throws IOException{//FeedEntryMessageSource messageSource = new FeedEntryMessageSource(resource.getURL(),"news");return messageSource;
}@Bean
public IntegrationFlow myFlow() throws IOException{return IntegrationFlows.from(feedMessageSource())//流程从from 方法开始.<SyndEntry,String> route(payload ->payload.getCategories().get(0).getname(),//选择路由,消息体payload类型SyndEntrymapping ->mapping.channelMapping("releases","releasesChannel").channelMapping("engineering","engineeringChannel")//不同分类的值转向不同的消息通道.channelMapping("news","newsChannel")).get();//获得IntegrationFlow实体,配置为Spring的Bean
}
③、release流程
@Bean
public IntegrationFlow releasesFlow(){return IntegrationFlows.from(MessageChannels.queue("releasesChannel",10)) //从消息通道releasesChannel开始获取数据.<SyndEntry,String> transform(payload->"《" + payload.getTitle()+"》"+payload.getLink()+getProperty("line.separator"))//数据转换.handle(Files.outboundAdapter(new File("e:/springblog"))//file出站适配器.fileExistsMode(FileExistsMode.APPEND).charset("UTF-8").fileNameGenerator(message -> "releases.txt").get()).get();}
④、engineering流程
@Bean
pubic IntegratonFlow engineeringFlow(){return IntegrationFlows.from(MessageChannels.queue("engineeringChannel",10)).<SyndEntry,String> transform(e->"《"+e.getTitle()+"》"+e.getLink()+getProperty("line.separator")).handle(Files.outboundAdapter(new File("e:/springblog")).fileExistsMode(FileExistsMode.APPEND).charset("UTF-8").fileNameGenerator(message->"engineering.txt").get()).get();
}
⑤、news流程
@Bean
public IntegrationFlow newsFlow(){return IntegrationFlows.from(MessageChannels.queue("newsChannel",10)).<SyndEntry,String> transform(payload->"《"+payload.getTitle()+"》"+payload.getLink()+getProperty("line.separator")).enrichHeaders(Mail.headers() //增加消息头信息.subject("来自Spring的新闻").to("wise-man@126.com").from("wisely-man@126.com"))//邮件发送的相关信息.handle(Mail.outboundAdapter("smtp.126.com")//邮件发送的出站适配器.port(25).protocol("smtp").credentials("wisely-man@126.com","****").javaMailProperties(p->p.put("mail.debug"),false)),e->e.id("smtpOut")).get();
}
⑥、运行
release.txt文件内容
邮箱接收结果