Spring4实战学习笔记

《Spring4实战 第4版》2016年4月新出版的,之前的第三版看起来还是不错的,所以看到新版就直接买下来。

英文版源码地址:Spring in Action, Fourth Edition Covers Spring 4



1.IOC装配Bean

参考【Spring实战4 2.2】,作者提倡无XML配置化

1.1接口只有一个现实类

可以自动装配

public interface CompactDisc {

    void play();
}


import org.springframework.stereotype.Component;

@Component
public class SgtPeppers implements CompactDisc {

    private String title = "Sgt. Pepper's Lonely Hearts Club Band";
    private String artist = "http://blog.csdn.net/unix21";

    public void play() {
        System.out.println("【非常醒目SgtPeppers 】>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist);
    }

}

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class CDPlayerConfig { 
}


单元测试

import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
    @Autowired
    private CompactDisc cd;
 
    @Test
    public void play() {
        cd.play();
    }
}




1.2 接口有多个实现类

【参考 Spring实战4 3.3】
故意再写一个实现类

import org.springframework.stereotype.Component;

@Component
public class SgtPeppersNew implements CompactDisc {

    private String title = "Sgt. Pepper's Lonely Hearts Club Band";
    private String artist = "http://blog.csdn.net/unix21";

    public void play() {
        System.out.println("【非常醒目 SgtPeppersNew】>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist);
    }

}

如果这个时候运行肯定会报错NoUniqueBeanDefinitionException: No qualifying bean of type


解决方法有两种

第一种 在实现类上 标识首选的bean,使用@Primary

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class SgtPeppers implements CompactDisc {

    private String title = "Sgt. Pepper's Lonely Hearts Club Band";
    private String artist = "http://blog.csdn.net/unix21";

    public void play() {
        System.out.println("【非常醒目SgtPeppers 】>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist);
    }

}


但是这种方法不方便精确定义。


第二种  使用@Qualifier注解

import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {
    @Autowired
    @Qualifier("sgtPeppersNew") 
    private CompactDisc cd;
 
    @Test
    public void play() {
        cd.play();
    }
}

需要注意的是bean id的首字母是类名小写。

spring @Qualifier注解


1.3 为组件扫描的bean命名

【参考 Spring实战4  2.2.2】

import org.springframework.stereotype.Component;

@Component("spn")
public class SgtPeppersNew implements CompactDisc {

@Autowired
    @Qualifier("spn") 
    private CompactDisc cd;

也可以使用 @Named效果是一样的,这是java依赖注入规范

import javax.inject.Named;

@Named("spn")
public class SgtPeppersNew implements CompactDisc {

1.4 设定组件扫描的指定包

【参考 Spring实战4  2.2.3】

如果@ComponentScan默认不设置只扫描配置类所在的包作为基础包。

@Configuration
@ComponentScan("blog.csdn.net.unix21")
public class CDPlayerConfigTest {
设置@ComponentScan的value属性就可以指明包名称。


如果想更清晰的表明设置的是基础包
@ComponentScan(basePackages="指定包")


指定多个

@ComponentScan(basePackages={"指定包1","指定包2"})


也可以将其指定为包中所包含的类或者接口

@ComponentScan(basePackages={"XXX.class","XX.class"})


1.5 自动装配

【参考 Spring实战4  2.2.4】

声明自动装配需要@Autowired注解


1.5.1 在构造方法上使用自动装配

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfigTest.class)
public class CDPlayerFunTest {

    private CompactDisc cd;

    @Autowired
    @Qualifier("spn")
    public void CDPlayer(CompactDisc cd) {
        this.cd = cd;
    }

    @Test
    public void play() {
        cd.play();
        System.out.println("【占位符】CDPlayerFunTest");
    }
}


另一种写法

@Component
public class CDPlayer implements MediaPlayer {
  private CompactDisc cd;

  @Autowired
  public CDPlayer(@Qualifier("spn")CompactDisc cd) {
    this.cd = cd;
  }

  public void play() {
    cd.play();
  }

}


1.5.2 在属性Setter方法上使用自动装配

@Component
public class CDPlayer implements MediaPlayer {
  private CompactDisc cd;
  
  @Autowired
  @Qualifier("spn")
  public void setCompactDisc(CompactDisc cd) {
    this.cd = cd;
  }

  public void play() {
    cd.play();
  }
}

避免异常声明  @Autowired(required = false),如果没有匹配的bean,Spring会让这个bean处于未装配转态,但是需要谨慎对待这个设置,代码需要做null检查。

@Autowired是Spring特有的注解,可以替换为@Inject,@Inject来源自Jave依赖注入规范。


1.6 创建自定义的限定符

【参考 Spring实战4  3.3.2】

@Component
@Qualifier("cold")
public class IceCream implements CompactDisc {

    private String title = "Sgt. Pepper's Lonely Hearts Club Band";
    private String artist = "The Beatles";

    public void play() {
        System.out.println("【非常醒目 IceCream】>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist);
    }
}


测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfigTest.class)
public class CDPlayerLogTest {

  @Autowired
  private MediaPlayer player;
  
  @Autowired
  @Qualifier("sp")
  private CompactDisc cd;
  
  @Autowired
  @Qualifier("cold")
  private CompactDisc cd2;
  
  @Test
  public void cdShouldNotBeNull() {
    assertNotNull(cd);
  }

  @Test
  public void play() {
    player.play();
    cd.play();
    cd2.play();
  }
}


好处:这样做的好处限定符不耦合类名,所以可以随意重构类名。

问题:重复的限定符出现在多个类上这是不允许的,因为Java不允许同一个条目上重复出现相同类型的多个注解


1.7 使用自定义限定符注解

针对上述问题可以创建自定义的限定符注解。

@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到  
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})//定义注解的作用目标**作用范围字段、枚举的常量/方法  
@Qualifier
public @interface Cold {}

@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到  
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})//定义注解的作用目标**作用范围字段、枚举的常量/方法  
@Qualifier
public @interface Creamy {}

@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到  
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})//定义注解的作用目标**作用范围字段、枚举的常量/方法  
@Qualifier
public @interface Fruity {}

@Component
@Cold
@Creamy
public class IceCream implements CompactDisc {

    private String title = "Spring 实现 第4版 读书笔记";
    private String artist = "http://blog.csdn.net/unix21";

    public void play() {
        System.out.println("【非常醒目 IceCream】>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist);
    }
}

@Component
@Cold
@Fruity
public class Popsicle implements CompactDisc {

    private String title = "Spring 实现 第4版 读书笔记";
    private String artist = "http://blog.csdn.net/unix21";

    public void play() {
        System.out.println("【非常醒目 Popsicle】>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Playing " + title + " by " + artist);
    }
}

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfigTest.class)
public class CDPlayerLogTest {

    @Autowired
    private MediaPlayer player;

    @Autowired
    @Qualifier("sp")
    private CompactDisc cd;

    @Autowired
    @Cold
    @Creamy
    private CompactDisc cd2;

    @Autowired
    @Cold
    @Fruity
    private CompactDisc cd3;

    @Test
    public void cdShouldNotBeNull() {
        assertNotNull(cd);
    }

    @Test
    public void play() {
        player.play();
        cd.play();
        cd2.play();
        cd3.play();
    }
}


1.8 bean的作用域

Spring定义了多重作用域,singleton单例,prototype原型等

参考:spring中scope作用域

singleton单例:整个应用中,只创建bean的一个实例,默认Spring上下文中所有的bean都是单例。

prototype原型:每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。

@Component
public class Add implements AddI {
    public  int a=0;
   
    public  void Add() {
        a++;
    }
    
    public  void getA() {
        System.out.println("【非常醒目 Add】>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>a= " +a+"");
    }
}

public interface AddI {
void Add();
    void getA();
}

@Component
public class CDPlayer implements MediaPlayer {

    @Autowired
    @Qualifier("sp")
    private CompactDisc cd;
    
    @Autowired
    private AddI a;

    public void play() {
        System.out.println("【非常醒目 CDPlayer】>>>");
        cd.play();
        a.Add();
        a.getA();
        a.Add();
        a.getA();
        System.out.println("【非常醒目 CDPlayer】<<<");
    }
}


测试用例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfigTest.class)
public class CDPlayerLogTest {

    @Autowired
    private MediaPlayer player;

    @Autowired
    @Qualifier("sp")
    private CompactDisc cd;

    @Autowired
    @Cold
    @Creamy
    private CompactDisc cd2;

    @Autowired
    @Cold
    @Fruity
    private CompactDisc cd3;

    @Test
    public void cdShouldNotBeNull() {
        assertNotNull(cd);
    }

    @Autowired
    private AddI a;
    
    @Test
    public void play() {
        player.play();
        cd.play();
        cd2.play();
        cd3.play();
        a.getA();
    }
}


再写一个多线程

public class ClientThread extends Thread {

    @Autowired
    private AddI a;

    @Autowired
    public ClientThread(AddI a) {
        this.a = a;
    }

    public void run() {
        a.Add();
        a.getA();
    }
}

调用多线程

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfigTest.class)
public class SpringScopeTest {

    @Autowired
    private AddI a;

    @Test
    public void Scope() {
        for (int i = 0; i < 10; i++) {
            ClientThread t = new ClientThread(a);
            t.start();
        }
    }
}


改为SCOPE_PROTOTYPE

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
//@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class Add implements AddI {
    public  int a=0;
   
    public  void Add() {
        a++;
    }
    
    public  void getA() {
        System.out.println("【非常醒目 Add】>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>a= " +a+"");
    }
}



看到差异了吧。


补充说明:@Repository、@Service、@Controller 和 @Component将类标识为Bean,都是一样的,用在不同的地方而已。


2.AOP切面编程

定义接口

public interface PerformanceI {
    public void perform();
}

实现类

import org.springframework.stereotype.Component;

@Component
public class Performance implements PerformanceI{
    public void perform(){
    System.out.println("【非常醒目  Performance perform 调用中】 By http://blog.csdn.net/unix21");   
    }
}

定义切面

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class MyAspect {
    @Before("execution(* com.demo.PerformanceI.perform(..))")
    public void before(){
    System.out.println("【非常醒目 [方法调用前] 】");
    }
    
    @After("execution(* com.demo.PerformanceI.perform(..))")
    public void after(){
    System.out.println("【非常醒目 [方法调用后] 】");
    }
    
    @AfterThrowing("execution(* com.demo.PerformanceI.perform(..))")
    public void afterThrowing(){
    System.out.println("【非常醒目 [方法异常后] 】");
    }
}

配置文件

import com.demo.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan("com.demo")
public class AppConfig {
    @Bean
    public MyAspect myAspect() {
        return new MyAspect();
    }
   
}


测试用例

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class MyTest {

    @Autowired
    private PerformanceI p1;
    
    @Test
    public void play() {
        p1.perform();
    }
}


运行:


实现了方法调用前后的AOP效果。

这个Spring官方参考做的不错:http://docs.spring.io/spring/docs/4.2.5.RELEASE/javadoc-api/

这里选不同的版本:http://docs.spring.io/spring/docs/



3.Spring MVC

DispatcherServlet是Spring MVC的核心,每当应用接受一个HTTP请求,由DispatcherServlet负责将请求分发给应用的其他组件。
在旧版本中,DispatcherServlet之类的servlet一般在web.xml文件中配置;但是Spring 3.1引入了注解就无需再使用web.xml文件。

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    @Override
    protected Class[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}

AbstractAnnotationConfigDispatcherServletInitializer这个类负责配置DispatcherServlet、初始化Spring MVC容器和Spring容器。


正如可以通过多种方式配置DispatcherServlet一样,也可以通过多种方式启动Spring MVC特性。原来我们一般在xml文件中使用元素启动注解驱动的Spring MVC特性。这里我们使用JavaConfig配置,最简单的Spring MVC配置类代码如下:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("com.xxx.controller")
public class WebConfig extends WebMvcConfigurerAdapter{
    @Bean
    public ViewResolver viewResolver() { //配置JSP视图解析器
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        //可以在JSP页面中通过${}访问beans
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable(); //配置静态文件处理
    }
}
@Configuration表示这是Java配置类;@EnableWebMvc注解用于启动Spring MVC特性。

通过@ComponentScan注解指定bean的自动发现机制作用的范围,被@Controller等注解修饰的web的bean将被发现并加载到spring mvc应用容器,这样就不需要在配置类中显式定义任何控制器bean了。

通过@Bean注解添加一个ViewResolverbean,具体来说是InternalResourceViewResolver。


RootConfig的配置就非常简单了,唯一需要注意的是,它在设置扫描机制的时候,将之前WebConfig设置过的那个包排除了;也就是说,这两个扫描机制作用的范围正交。RootConfig的代码如下:

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages = {"com.xxx.*"},
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)})
public class RootConfig {
}


写一个控制器,定义之前的IOC对象PerformanceI

@Controller
public class HomeController {
    @Autowired
    private PerformanceI p1;
    
    @RequestMapping(value = "/home", method = RequestMethod.GET)
    public String home() {
        p1.perform();
        return "home";
    }
}


在WEB-INF/views下新增模板文件home.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>


    
        
        JSP Page
    
    
        

Spring4 & Sping MVC4

demo by http://blog.csdn.net/unix21





下面这个是【第5章】的翻译 https://segmentfault.com/a/1190000004343063?_ea=575820


默认首页

我们希望用户访问域名直接到默认首页,那么需要删除网站web目录下的index.html。

tomcat会自动默认去寻找在webroot目录下面的index文件,如果要使用自定义方法,则要保证webroot下面没有index相关的文件。


自己写一个index

@RequestMapping(value = "/", method = RequestMethod.GET)
    public String index() {
        return "index";
    }

再定义一个index.jsp即可

具体请参考:Spring MVC 下设置默认访问页面的3种方式


映射多个地址

 @RequestMapping(value = {"", "/","index"}, method = RequestMethod.GET)
    public String index() {
        return "index";
    }
或者
 @RequestMapping({"", "/","index"})
    public String index() {
        return "index";
    }


输出json

形式一

@ResponseBody
    @RequestMapping(value = "/list", method = RequestMethod.GET,produces = "text/json;charset=UTF-8")
    public String h1(HttpServletRequest req, HttpServletResponse res) throws Exception {
        UserBean user= ...;      
        ObjectMapper mapper = new ObjectMapper();
        String js=mapper.writeValueAsString(user);
        return js;
    }


形式二

@ResponseBody
    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public void list(HttpServletRequest req, HttpServletResponse res) throws Exception {
	    res.setContentType("text/json;charset=UTF-8");
        UserBean user= ...;
        ObjectMapper mapper = new ObjectMapper();
        String js=mapper.writeValueAsString(user);
        PrintWriter pWriter = res.getWriter();
        pWriter.write(js);
    }


4.Spring4整合MyBatis3

说明:《Spring实战(第4版)》并没有提到MyBatis的整合,这个是我自己写的,写一起只为查看方便。

新建MybatisConfig文件

import javax.sql.DataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
public class MybatisConfig {

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUsername("admin");
        dataSource.setPassword("admin");
        dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");//如果其他数据库换对应的驱动即可
        dataSource.setUrl("jdbc:sqlserver://blog.csdn.net.unix21:3499;DatabaseName=testdb");
        return dataSource;
    }

    @Bean
    MapperScannerConfigurer mpperScannnerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setSqlSessionFactoryBeanName("sqlSessionFactory");
        msc.setBasePackage("com.unix.mapper");//自动扫描mapper包
        return msc;
    }

    @Bean(name = "sqlSessionFactory")
    SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setDataSource(dataSource);
        ssfb.setTypeAliasesPackage("com.unix.bean");//自动扫描bean包
        return ssfb;
    }

    @Bean
    PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

新增一个mapper接口

public interface  SchoolMapper {
    @Select("select * from School where id =#{id}")
    School findById(@Param("id") int id);
    
    @Select("select * from School where Name like '${name}%'")
    List findByName(@Param("name") String name);
}

测试用例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MybatisConfig.class)
//@TransactionConfiguration(defaultRollback=true)
public class SchoolTest {
    @Autowired
    private SchoolMapper shoolDao;
    
    @Test
    public void findById(){
        School shool = shoolDao.findById(1);
        Assert.assertNotNull(shool);
        System.out.println(shool.getName());       
    }
    
    @Test
    public void findByName(){
        List result = shoolDao.findByName("苏州中学");
        Assert.assertNotNull(result);
        for (School s : result) {
        System.out.println(s.getName());
        }        
    }
}


5.Spring4使用Redis

redis在日常开发中已经成为了标配了,在spring4中使用redis非常简单,无需自己去写一个jedis的工厂方法连接池之类的代码,因为Spring已经写好了,你只需要引用spring-data-redis包即可


            org.springframework.data
            spring-data-redis
            1.7.1.RELEASE
        

Redis配置

@Configuration
public class RedisConfig {

    @Bean
    public RedisConnectionFactory redisCF() {
        JedisConnectionFactory cf = new JedisConnectionFactory();
        cf.setHostName("127.0.0.1");//服务器IP
        cf.setPort(6379);//端口
        cf.setPassword("密码");
        return cf;
    }

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory cf) {
        RedisTemplate redis = new RedisTemplate();
        redis.setConnectionFactory(cf);
        return redis;
    }
    
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory cf) {
        StringRedisTemplate redis = new StringRedisTemplate();
        redis.setConnectionFactory(cf);
        return redis;
    }
}

测试用例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RedisConfig.class)
public class RedisTest {
    @Autowired
    private StringRedisTemplate r1;
    @Test
    public void get() {
        
        String foo=DateUtil.getNowTimeString();
        r1.opsForValue().set("foo", foo);
        
        foo=r1.opsForValue().get("foo");
        System.out.println("【Redis 测试>>>get set】"+foo+"By http://blog.csdn.net/unix21");
    }
}


6.Spring Security


import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
}

@Configuration
@EnableWebMvcSecurity
public class SecuredConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    http
        .authorizeRequests()
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .and()
        .httpBasic();
    
    }

}


这个时候访问任何页面都会跳转到系统自带的登陆页面

基于内存的用户存储:

    //基于内存的用户
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("user1").password("pass1").roles("USER").and()
                .withUser("user2").password("pass2").roles("USER","ADMIN");
    }


故意输错用户名密码就进不去


验证 指定页面+指定权限

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
              .authorizeRequests()
                  .antMatchers("/news").hasRole("USER")
                  .and()
              .formLogin()
                .and()
                .httpBasic();
    }



我们设定/news需要User角色的用户可以看,其他页面随便看


我们用user2登陆,由于没权限返回403


只有用user1登陆才可以看到页面内容。


自定义登陆页

现实开发中不肯能用Spring提供的简易登陆页,除非是一个很小的内部系统。

@Configuration
@EnableWebMvcSecurity
public class SecuredConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/news/*").hasRole("USER")  
                .and()
                .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/")
                .failureUrl("/login?error")
                .permitAll()
                .and()
                .httpBasic();
    }

.antMatchers("/news/*").hasRole("USER")   :匹配  /news/以及/news/ 开头的所有页面  需要USER权限

.loginPage("/login")   :自定义登陆页
.defaultSuccessUrl("/")   :默认成功页,如果没有权限则跳转到该页面

 .failureUrl("/login?error")  :默认失败页面


  
    // 1. /login 登录页面的常规显示
    // 2. /login?error 登录验证失败的展示
    // 3. /login?logout 注销登录的处理
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public ModelAndView login(
            @RequestParam(value = "error", required = false) String error,
            @RequestParam(value = "logout", required = false) String logout) {

        ModelAndView model = new ModelAndView();
        if (error != null) {
            model.addObject("error", "用户名密码不对!");
        }

        if (logout != null) {
            model.addObject("msg", "You've been logged out successfully.");
        }
        model.setViewName("login");

        return model;

    }

login.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>

    
        登陆
    
    
        

登陆页

${error}
${msg}
用户名:
密码:
此处参考: http://www.mkyong.com/spring-security/spring-security-form-login-example/


配置多个页面权限控制

.antMatchers("/news/*","/user/*").hasRole("USER")

写法二

.antMatchers("/news/*").hasRole("USER")
.antMatchers("/user/*").hasRole("USER")
可以将任意多的antMatchers(),anyRequest()连接起来,但是这些规则会按给定的顺序发挥作用,所以需要将 最为具体的请求路径放在最前面,而最不具体的路径anyRequest()放在后面,不然不具体的就会覆盖掉具体的。


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .defaultSuccessUrl("/helloadmin")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

对URL进行权限配置,使得"/", "/home"不需要登录就可以访问,其他需要登录。登录的地址是'/login',当登录成功后将跳转到/helloadmin页面,并且登录、登出页面都是不需要登录就可以访问的。

参考:使用Spring Security进行权限验证


.httpBasic()

支持弹窗就是windows自带的认证框进行认证。由于不好扩展这个基本没什么用,可以不用。


.rememberMe()

记住我的状态

.rememberMe().key("web")
                    .tokenValiditySeconds(1209600);

.logout()

注销功能

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .antMatchers("/news/*").hasRole("USER")
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .defaultSuccessUrl("/")
                    .failureUrl("/login?error")
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .and()
                .logout()
                    .logoutSuccessUrl("/login?logout")
                    .and()
                .rememberMe().key("gkweb")
                    .tokenValiditySeconds(1209600);
    }

注销就是页面给用户一个链接或者按钮

1.使用jstl声明退出路径

引用jar包


            jstl
            jstl
            1.2
        

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>



2.硬编码退出路径


3.链接退出



Welcome : ${pageContext.request.userPrincipal.name} | 退出1-链接

Welcome : ${pageContext.request.userPrincipal.name} | 退出2-链接


自定义的用户服务

实际开发肯定是需要去数据库或者其他地方查询用户账号密码等

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(new CustomUserDetailsService(userDao));
}


注意:CustomUserDetailsService需要的bean一定要从userDetailsService调用CustomUserDetailsService的构造器传递过去,

而不能直接在CustomUserDetailsService使用@Autowired注解出来。
 

CustomUserDetailsService集成的接口UserDetailsService无需自己重新定义,参考 泛型推断类型不符合上限

public class CustomUserDetailsService implements UserDetailsService {

    private final UserMapper userDao;
    public CustomUserDetailsService(UserMapper u) {
        this.userDao = u;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {       
        UserBean user = userDao.findByName(username);        
        if (user == null) {
            throw new UsernameNotFoundException("没有找到对应用户");
        }
        List authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(user.getUtype().toString()));
        return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), authorities);
    }
}

跨站攻击

Spring Security3.2开始默认是开启的的,如果有特殊要求可以关闭跨站防御

.csrf().disable();


多权限组合

需要注意要以页面为单位,不然下面的配置会和上面的冲突

.authorizeRequests()
                    .antMatchers("/news/*","/school/*").hasAnyRole("1","4")
                    .antMatchers("/goods/*").hasRole("4")
                    .and()


自定义403页面

http.authorizeRequests()
	    .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
	    .and().formLogin()
		.loginPage("/login").failureUrl("/login?error")
		.usernameParameter("username")
		.passwordParameter("password")
	    .and().logout().logoutSuccessUrl("/login?logout")
	    .and()
		.exceptionHandling().accessDeniedPage("/403")
	}

然后自己写一个SpringMVC的/403页面即可

参考: Spring Security : Customize 403 access denied page


自定义404页面

其实这个是SpringMVC干的事情,非常简单

定义一个

    @RequestMapping(value = "*", method = RequestMethod.GET)
    public String my404() {
        return "404";
    }


POST参数中文乱码的解决

注意,如果没有使用Springsecurity,那么可以直接在SpingWebInitializer定义一个getServletFilters()

如下:

import javax.servlet.Filter;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class SpingWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    @Override
    protected Class[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
       
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        return new Filter[] { characterEncodingFilter};
    }
}


如果使用了Springsecurity,那么需要在Springsecurity的配置文件里配置,因为 CharacterEncodingFilter 需要在 SecurityFilter之前启动

public class SecuredConfig extends WebSecurityConfigurerAdapter {
@Override
    protected void configure(HttpSecurity http) throws Exception {
        //The CharacterEncodingFilter must be started before SecurityFilter. I moved it to security configuration:
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        encodingFilter.setForceEncoding(true);
        http.addFilterBefore(encodingFilter,CsrfFilter.class);
... ...



你可能感兴趣的:(Spring,Java)