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 {@Autowiredprivate CompactDisc cd;@Testpublic 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;@Testpublic 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;}@Testpublic void play() {cd.play();System.out.println("【占位符】CDPlayerFunTest");}
}
?
?
?
另一種寫法
?
@Component
public class CDPlayer implements MediaPlayer {private CompactDisc cd;@Autowiredpublic 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 {@Autowiredprivate MediaPlayer player;@Autowired@Qualifier("sp")private CompactDisc cd;@Autowired@Qualifier("cold")private CompactDisc cd2;@Testpublic void cdShouldNotBeNull() {assertNotNull(cd);}@Testpublic 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 {@Autowiredprivate MediaPlayer player;@Autowired@Qualifier("sp")private CompactDisc cd;@Autowired@Cold@Creamyprivate CompactDisc cd2;@Autowired@Cold@Fruityprivate CompactDisc cd3;@Testpublic void cdShouldNotBeNull() {assertNotNull(cd);}@Testpublic 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;@Autowiredprivate 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 {@Autowiredprivate MediaPlayer player;@Autowired@Qualifier("sp")private CompactDisc cd;@Autowired@Cold@Creamyprivate CompactDisc cd2;@Autowired@Cold@Fruityprivate CompactDisc cd3;@Testpublic void cdShouldNotBeNull() {assertNotNull(cd);}@Autowiredprivate AddI a;@Testpublic void play() {player.play();cd.play();cd2.play();cd3.play();a.getA();}
}
?
?
?
再寫一個多線程
?
public class ClientThread extends Thread {@Autowiredprivate AddI a;@Autowiredpublic ClientThread(AddI a) {this.a = a;}public void run() {a.Add();a.getA();}
}
調用多線程
?
?
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfigTest.class)
public class SpringScopeTest {@Autowiredprivate AddI a;@Testpublic 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 {@Beanpublic 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 {@Autowiredprivate PerformanceI p1;@Testpublic 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 {@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class<?>[]{RootConfig.class};}@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class<?>[]{WebConfig.class};}@Overrideprotected String[] getServletMappings() {return new String[]{"/"};}}
?
AbstractAnnotationConfigDispatcherServletInitializer這個類負責配置DispatcherServlet、初始化Spring MVC容器和Spring容器。
正如可以通過多種方式配置DispatcherServlet一樣,也可以通過多種方式啟動Spring MVC特性。原來我們一般在xml文件中使用<mvc:annotation-driven>元素啟動注解驅動的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{@Beanpublic ViewResolver viewResolver() { //配置JSP視圖解析器InternalResourceViewResolver resolver = new InternalResourceViewResolver();resolver.setPrefix("/WEB-INF/views/");resolver.setSuffix(".jsp");//可以在JSP頁面中通過${}訪問beansresolver.setExposeContextBeansAsAttributes(true);return resolver;}@Overridepublic 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 {@Autowiredprivate 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"%>
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>JSP Page</title></head><body><h1>Spring4 & Sping MVC4 </h1><p>demo by http://blog.csdn.net/unix21</p></body>
</html>
?
?
?
?
?
下面這個是【第5章】的翻譯?https://segmentfault.com/a/1190000004343063?_ea=575820
?
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 {@Beanpublic 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;}@BeanMapperScannerConfigurer 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;}@BeanPlatformTransactionManager 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<School> findByName(@Param("name") String name);
}
測試用例
?
?
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MybatisConfig.class)
//@TransactionConfiguration(defaultRollback=true)
public class SchoolTest {@Autowiredprivate SchoolMapper shoolDao;@Testpublic void findById(){School shool = shoolDao.findById(1);Assert.assertNotNull(shool);System.out.println(shool.getName()); }@Testpublic void findByName(){List<School> 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包即可
?
<dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>1.7.1.RELEASE</version></dependency>
Redis配置
?
?
@Configuration
public class RedisConfig {@Beanpublic RedisConnectionFactory redisCF() {JedisConnectionFactory cf = new JedisConnectionFactory();cf.setHostName("127.0.0.1");//服務器IPcf.setPort(6379);//端口cf.setPassword("密碼");return cf;}@Beanpublic RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {RedisTemplate<String, String> redis = new RedisTemplate<String, String>();redis.setConnectionFactory(cf);return redis;}@Beanpublic StringRedisTemplate stringRedisTemplate(RedisConnectionFactory cf) {StringRedisTemplate redis = new StringRedisTemplate();redis.setConnectionFactory(cf);return redis;}
}
測試用例
?
?
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RedisConfig.class)
public class RedisTest {@Autowiredprivate StringRedisTemplate r1;@Testpublic 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 {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();}}
?
?
這個時候訪問任何頁面都會跳轉到系統自帶的登陸頁面
基于內存的用戶存儲:
?
//基于內存的用戶@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("user1").password("pass1").roles("USER").and().withUser("user2").password("pass2").roles("USER","ADMIN");}
?
?
?
故意輸錯用戶名密碼就進不去
?
?
?
?
?
驗證?指定頁面+指定權限
?
@Overrideprotected 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 {@Overrideprotected 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"%>
<html><head><title>登陸</title></head><body onload='document.f.username.focus();'><h3>登陸頁</h3> <c:if test="${not empty error}"><div style="color: red">${error}</div></c:if><c:if test="${not empty msg}"><div>${msg}</div></c:if><form name='f' action='/gkweb/login' method='POST'><table><tr><td>用戶名:</td><td><input type='text' name='username' value=''></td></tr><tr><td>密碼:</td><td><input type='password' name='password'/></td></tr><tr><td colspan='2'><input name="submit" type="submit" value="登陸"/></td></tr> </table><input type="hidden" name="${_csrf.parameterName}"value="${_csrf.token}" /></form></body>
</html>
此處參考: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()放在后面,不然不具體的就會覆蓋掉具體的。
?
?
?
@Overrideprotected 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()
注銷功能
?
@Overrideprotected 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包
?
<dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency>
?
?
?
?
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}"method="post">
<input type="submit"value="Log out" />
<input type="hidden"name="${_csrf.parameterName}"value="${_csrf.token}"/>
</form>
?
2.硬編碼退出路徑
?
?
?
<form action="${pageContext.request.contextPath}/logout" method="post"><input type="submit" value="Logout" /><input type="hidden"name="${_csrf.parameterName}"value="${_csrf.token}"/>
</form>
?
3.鏈接退出
?
?
<script>function formSubmit() {document.getElementById("logoutForm").submit();}</script><form action="${pageContext.request.contextPath}/logout" method="post" id="logoutForm"><c:if test="${pageContext.request.userPrincipal.name != null}"><h2>Welcome : ${pageContext.request.userPrincipal.name} | <a href="javascript:formSubmit()"> 退出1-鏈接</a></h2></c:if><input type="submit" value="退出1-按鈕" /><input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/></form><c:url var="logoutUrl" value="/logout"/><form action="${logoutUrl}" method="post"><c:if test="${pageContext.request.userPrincipal.name != null}"><h2>Welcome : ${pageContext.request.userPrincipal.name} | <a href="javascript:formSubmit()"> 退出2-鏈接</a></h2></c:if><input type="submit" value="退出2-按鈕" /><input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/></form>
?
自定義的用戶服務
?
實際開發肯定是需要去數據庫或者其他地方查詢用戶賬號密碼等
?
@Overrideprotected 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;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserBean user = userDao.findByName(username); if (user == null) {throw new UsernameNotFoundException("沒有找到對應用戶");}List<SimpleGrantedAuthority> authorities = new ArrayList<>();authorities.add(new SimpleGrantedAuthority(user.getUtype().toString()));return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), authorities);}
}
?
關于Spring的單例問題
也就是@Component、@Repository、@Service? 需要說明的是加上這幾個注解都是等效的,都會變成單例。
一般ServiceImpl會加上。
使用的時候
@Autowired
? ? private 接口? 變量
這樣在使用的時候回自動綁定加上@Service的實現類。
特別需要注意的是,這個實現類不可以有成員變量,否則不安全,因為沒有成員變量,只是通過函數參數傳值,所以是安全的。
?
而javabean一般都必須有成員變量,所以是不能@Service,建議直接new一下使用即可。
@Component和@Bean都是用來注冊Bean并裝配到Spring容器中,但是Bean比Component的自定義性更強。可以實現一些Component實現不了的自定義加載類。
?
為什么dao層和service層用單例,而action用多例
使用單例和多例的判斷條件是會不會對類中公共屬性進行修改,如果有修改則用多例。
action中一般會有前端對應的屬性,如果是單例訪問,所有訪問公用一個action中的成員變量和方法,如果多個請求同時對一個屬性進行修改,則會出現數據出錯;而service中公共屬性頂多是dao層中的引用,dao層中公共屬性是對數據庫包裝類或自定義的類的引用,這個引用通常在項目加載啟動時就已經實例化了,訪問只是使用,并未涉及屬性修改,單例模式可節省系統資源。
總結:Action要接收request的參數,因為參數不同所以用多例;
Dao中唯一的狀態就是連接數據庫, 但是這個恰好對大家都是相同的, 所以是單例
Service, 業務邏輯里面的成員變量都是Dao, 既然Dao是無狀態的, 那么Service也可以認為是無狀態的
總結
以上是生活随笔為你收集整理的Spring4实战学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 石狮子多少钱一对
- 下一篇: Java多线程的11种创建方式以及纠正网