Spring集成开发篇
一.Spring整合SpringMVC
1.创建web项目
2.Spring整合SpringMVC
首先需要在项目引入Spring,然后在引入SpringMVC,最后让SpringMVC和Spring协调工作
1.导入相关的依赖
2.添加对应的配置文件
3.在web.xml文件中整合
3.Spring和SpringMVC的关系
web.xml文件的ContextLoaderListener
会优先于DispatcherServlet
先执行
ContextLoaderListener核心代码
try {
if (this.context == null) {
// 创建容器对象
this.context = this.createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
// 解析配置文件 加载IoC容器
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将Spring的IoC容器对象保存在了servletContext作用域中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
} catch (RuntimeException var8) {
logger.error("Context initialization failed", var8);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
throw var8;
} catch (Error var9) {
logger.error("Context initialization failed", var9);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
throw var9;
}
}
DispatcherServlet
是一个Servlet,那么分析的时候我们需要先从init方法开始,分析请求响应相关的就应该分析service方法
// SpringMVC的IoC容器的初始化并设置SpringIoC容器为父容器
this.webApplicationContext = this.initWebApplicationContext();
this.initFrameworkServlet();
protected WebApplicationContext initWebApplicationContext() {
// 取出Spring的IoC容器对象
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 将Spring的IoC容器设置为SpringMVC的IoC容器的父容器
cwac.setParent(rootContext);
}
// SpringMVC的IoC容器的初始化
this.configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
wac = this.findWebApplicationContext();
}
if (wac == null) {
wac = this.createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
this.onRefresh(wac);
}
if (this.publishContext) {
String attrName = this.getServletContextAttributeName();
this.getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
SpringMVC是继承自Spring的IoC容器的。
那么这样的SpringMVC容器中的对象就可以直接获取到SpringIoC中的对象。反过来SpringIoC容器无法获取SpringMVC中的对象
Controller可以获取Service和Dao中的对象
但是Service和Dao是获取不到Controller中的对象的!
二、Spring和MyBatis的整合
1.添加Spring框架
2.添加MyBatis框架
3.Spring整合MyBatis
三、Spring和Shiro的整合
Shiro是一个强大且易用的Java安全框架,身份认证,授权,密码加密及会话管理。
Simple Java Security
1).第一个简单案例
1.导入相关依赖
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
2.添加shiro.ini文件
[users]
zhang=123
3.测试
package com.gupaoedu;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
/**
* 让每一个人的职业生涯不留遗憾
*
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/9 15:45
*/
public class ShiroDemo01 {
/**
* Shiro的第一个案例
* @param args
*/
public static void main(String[] args) {
// 1.获取一个SecurityManager工厂对象 加载对应的配置文件
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2.通过Factory对象获取SecurityManager对象
SecurityManager manager = factory.getInstance();
// 3.将SecurityManager添加到运行时环境中
SecurityUtils.setSecurityManager(manager);
// 4.通过SecurityManager获取Subject
Subject subject = SecurityUtils.getSubject();
// 假设获取用户提交的账号密码
String userName = "lisi";
String password = "111";
// 将用户提交的数据封装成一个AuthenticationToken对象
AuthenticationToken token = new UsernamePasswordToken(userName,password);
// 认证
subject.login(token);
}
}
效果:
账号不存在
Exception in thread "main" org.apache.shiro.authc.UnknownAccountException:
密码错误
Exception in thread "main" org.apache.shiro.authc.IncorrectCredentialsException:
4.认证流程分析
public IniSecurityManagerFactory(String iniResourcePath) {
// Ini.fromResourcePath(iniResourcePath) 加载并解析配置文件 将信息保存到了Ini对象中
this(Ini.fromResourcePath(iniResourcePath));
}
public void loadFromPath(String resourcePath) throws ConfigurationException {
InputStream is;
try {
// 获取配置文件【shiro.ini】对应的字节输入流
is = ResourceUtils.getInputStreamForPath(resourcePath);
} catch (IOException var4) {
throw new ConfigurationException(var4);
}
// 解析配置文件
this.load(is);
}
public T createInstance() {
Ini ini = this.resolveIni();
Object instance;
String msg;
if (CollectionUtils.isEmpty(ini)) {
log.debug("No populated Ini available. Creating a default instance.");
instance = this.createDefaultInstance();
if (instance == null) {
msg = this.getClass().getName() + " implementation did not return a default instance in " + "the event of a null/empty Ini configuration. This is required to support the " + "Factory interface. Please check your implementation.";
throw new IllegalStateException(msg);
}
} else {
log.debug("Creating instance from Ini [" + ini + "]");
instance = this.createInstance(ini);
if (instance == null) {
msg = this.getClass().getName() + " implementation did not return a constructed instance from " + "the createInstance(Ini) method implementation.";
throw new IllegalStateException(msg);
}
}
return instance;
}
protected Map<String, ?> createDefaults(Ini ini, Section mainSection) {
Map<String, Object> defaults = new LinkedHashMap();
// 创建SecurityManager对象
SecurityManager securityManager = this.createDefaultInstance();
// 将创建的securityManager对象保存到了Map集合中
defaults.put("securityManager", securityManager);
// 是否应该悄悄的创建Realm对象
if (this.shouldImplicitlyCreateRealm(ini)) {
// 创建了Realm对象
Realm realm = this.createRealm(ini);
if (realm != null) {
defaults.put("iniRealm", realm);
}
}
return defaults;
}
private void processDefinitions(Ini ini) {
if (CollectionUtils.isEmpty(ini)) {
log.warn("{} defined, but the ini instance is null or empty.", this.getClass().getSimpleName());
} else {
Section rolesSection = ini.getSection("roles");
if (!CollectionUtils.isEmpty(rolesSection)) {
log.debug("Discovered the [{}] section. Processing...", "roles");
this.processRoleDefinitions(rolesSection);
}
// 获取shiro.ini文件中[users]对应的信息
Section usersSection = ini.getSection("users");
if (!CollectionUtils.isEmpty(usersSection)) {
log.debug("Discovered the [{}] section. Processing...", "users");
this.processUserDefinitions(usersSection);
} else {
log.info("{} defined, but there is no [{}] section defined. This realm will not be populated with any users and it is assumed that they will be populated programatically. Users must be defined for this Realm instance to be useful.", this.getClass().getSimpleName(), "users");
}
}
}
protected void processUserDefinitions(Map<String, String> userDefs) {
if (userDefs != null && !userDefs.isEmpty()) {
Iterator i$ = userDefs.keySet().iterator();
while(true) {
while(i$.hasNext()) {
String username = (String)i$.next();
String value = (String)userDefs.get(username);
String[] passwordAndRolesArray = StringUtils.split(value);
String password = passwordAndRolesArray[0];
SimpleAccount account = this.getUser(username);
if (account == null) {
// 保存的有解析的账号密码信息
account = new SimpleAccount(username, password, this.getName());
this.add(account);
}
account.setCredentials(password);
if (passwordAndRolesArray.length > 1) {
for(int i = 1; i < passwordAndRolesArray.length; ++i) {
String rolename = passwordAndRolesArray[i];
account.addRole(rolename);
SimpleRole role = this.getRole(rolename);
if (role != null) {
account.addObjectPermissions(role.getPermissions());
}
}
} else {
account.setRoles((Set)null);
}
}
return;
}
}
}
protected void add(SimpleAccount account) {
String username = this.getUsername(account);
this.users.put(username, account);
}
public class SimpleAccountRealm extends AuthorizingRealm {
// 在shiro.ini文件中配置的账号密码信息 其实是保存在SimpleAccountRealm的users集合中的
protected final Map<String, SimpleAccount> users;
protected final Map<String, SimpleRole> roles;
Login源码分析
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" + token + "]. Please ensure that the appropriate Realm implementation is " + "configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
} else {
// 具体的认证操作
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " + "submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
} else {
return info;
}
}
}
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 账号的认证 判断账号是否存在
AuthenticationInfo info = this.doGetAuthenticationInfo(token);
// 账号如果不存在 返回null
if (info == null) {
if (log.isDebugEnabled()) {
String msg = "No authentication information found for submitted authentication token [" + token + "]. " + "Returning null.";
log.debug(msg);
}
return null;
} else {
// 密码验证
CredentialsMatcher cm = this.getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
String msg = "The credentials provided for account [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
} else {
return info;
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication. If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}
}
账号验证进入的是 SimpleAccountRealm
中
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取用户提交的认证的账号密码
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
// 根据提交的账号 去Users中获取Account对象
SimpleAccount account = this.getUser(upToken.getUsername());
// 账号存在
if (account != null) {
// 判断是否锁定
if (account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}
// 判断账号是否过期
if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}
}
return account;
}
密码验证
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
// 获取用户提交的密码
Object tokenCredentials = this.getCredentials(token);
// 获取shiro.ini文件中 账号对应的密码
Object accountCredentials = this.getCredentials(info);
// 判断密码是否相等
return this.equals(tokenCredentials, accountCredentials);
}
认证的流程
1.创建token令牌,token令牌中有用户提交的账号密码信息
2.执行subject.login(token),最终由SecurityManager通过Authenticator进行认证
3.Authentictor的中ModularRealmAuthenticator调用realm从ini配置文件中获取用户真实的账号密码,iniRealm
4.IniRealm先根据token中的账号去ini中查找该账号,如果不存在就返回null,抛出UnknownAccountException异常,如果账号存在,那么就会做密码的匹配。如果匹配不通过那么就会抛出IncorrectCredentialsException异常
2).自定义的Realm
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NA91yH8i-1626832880778)(img\1594285931766.png)]
3).加密处理
Md5单独应用
/**
* 让每一个人的职业生涯不留遗憾
*
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/9 17:34
*/
public class Md5Test {
/**
* MD5加密演示
* @param args
*/
public static void main(String[] args) {
// salt:盐
Md5Hash md5Hash = new Md5Hash("123456","aaa",1024);
System.out.println(md5Hash);
}
}
和Shiro结合Md加密
/**
* 完成自定义的账号验证
* @param authenticationToken
* @return
* 返回null表示 账号不存在
* @throws AuthenticationException
*
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
System.out.println("提交的账号是MD5:" + token.getUsername());
// 根据账号去数据库中查询对应的记录
if(!"zhang".equals(token.getUsername())){
// 表示 账号不存在
return null;
}
// 表示账号存在
String password = "b8d63fc060e2b5651e8cb4e71ba61e6f";// 表示数据库中查询的密码是123
String salt = "aaa"; // zhang 这个账号对应的 盐值
return new SimpleAuthenticationInfo(token.getUsername(),password,new SimpleByteSource(salt),"myRealm");
}
4). 授权操作
授权,又称为访问控制
,是对资源的访问管理的过程,在用户通过认证
,授予他可以访问某些资源的权限。
授权的方式
shiro支持三种授权方式
1.通过if/else语句实现
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
2.注解触发
@RequiresRoles("admin")
public void hello() {
//有权限
}
3.通过标签触发
<shiro:hasRole name="admin">
<!— 有权限—>
</shiro:hasRole>
通过if/else方式实现
// zhang role2
public boolean hasRole(PrincipalCollection principal, String roleIdentifier) {
// 获取当前用户具有的角色 [admin,role1,root,role3]
AuthorizationInfo info = this.getAuthorizationInfo(principal);
// role2是否在[admin,role1,root,role3] 中
return this.hasRole(roleIdentifier, info);
}
5). SSM整合操作
SSM=(SpringMVC+Spring+MyBatis)
整合操作的步骤
1.导入相关的jar包依赖
2.添加相关的配置文件
3.整合相关的框架(Spring + SpringMVC)(Spring+MyBatis)
4.测试
6). SSM和Shiro的整合
1.添加依赖
<!-- shiro相关的依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
2.配置web.xml文件
在web.xml中配置shiro的过滤器
<!-- 添加一个Shiro的过滤器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true 由servlet容器来控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean -->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiro</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>Archetype Created Web Application</display-name>
<!-- 这里配置Spring配置文件的位置,param-name是固定的,
param-value是文件位置 这个配置可以省略,如果省略,
系统默认去/WEB-INF/目录下查找applicationContext.xml作为Spring的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 添加一个Shiro的过滤器 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true 由servlet容器来控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean -->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiro</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
3.添加shiro的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置凭证匹配器 -->
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher" id="credentialsMatcher">
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="1024"/>
</bean>
<!-- 自定义的Bean -->
<bean class="com.gupaoedu.realm.MyRealm" id="myRealm">
<!-- 关联凭证匹配器-->
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!-- 配置SecurityManager -->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
<property name="realm" ref="myRealm"/>
</bean>
<!-- 注册ShiroFilterFactoryBean id必须和web.xml中设置的 targetBeanName的值一致-->
<bean class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" id="shiro">
<!-- 关联SecurityManager -->
<property name="securityManager" ref="securityManager" />
<!-- 登录地址 如果用户请求的的地址是 login.do 那么会对该地址认证-->
<property name="loginUrl" value="/login.do"/>
<!-- 登录成功的跳转地址 -->
<property name="successUrl" value="/success.jsp"/>
<!-- 访问未授权的页面跳转的地址 -->
<property name="unauthorizedUrl" value="/refuse.jsp"/>
<!-- 设置 过滤器链 -->
<property name="filterChainDefinitions">
<value>
<!--加载顺序从上往下。
authc需要认证
anon可以匿名访问的资源
-->
/login.do=authc
/**=anon
</value>
</property>
</bean>
</beans>
4.自定义的Realm
package com.gupaoedu.realm;
import com.gupaoedu.bean.User;
import com.gupaoedu.service.IUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.SimpleByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
* 让每一个人的职业生涯不留遗憾
*
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/10 14:49
*/
public class MyRealm extends AuthorizingRealm {
@Autowired
private IUserService userService;
/**
* 认证的方法
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String userName = token.getUsername();
List<User> list = userService.query(userName);
if(list == null || list.size() != 1){
return null; // 表示账号不存在
}
User user = list.get(0);
return new SimpleAuthenticationInfo(user,user.getPassword(),new SimpleByteSource(user.getSalt()),"myrealm");
}
/**
* 授权方法
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
5.controller
package com.gupaoedu.controller;
import com.gupaoedu.bean.User;
import com.gupaoedu.service.IUserService;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* 让每一个人的职业生涯不留遗憾
*
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/10 14:16
*/
@Controller
public class UserController {
@Autowired
private IUserService userService;
@RequestMapping("/user/query")
public String query(String userName){
return userService.query(userName).toString();
}
/**
* 在认证失败后执行的方法,在此方法中我们可以获取认证的相关信息,并跳转到对应的页面
* @param model
* @param request
* @return
*/
@RequestMapping("/login.do")
public String login(Model model,HttpServletRequest request){
Object attribute = request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
if(attribute != null){
System.out.println(attribute.toString() + " ---- ");
}
return "/exception.jsp";
}
}
7).Shiro整合Spring认证源码分析
Shiro整合SSM,在DelegatingFilterProxy
加载的IoC容器其实是Spring的IoC容器
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
// this.getTargetBeanName() shiro ShiroFilterFactoryBean--> AbstractShiroFilter
Filter delegate = (Filter)wac.getBean(this.getTargetBeanName(), Filter.class);
if (this.isTargetFilterLifecycle()) {
delegate.init(this.getFilterConfig());
}
return delegate;
}
FormAuthenticationFilter原理
OncePerRequestFilter 保证该过滤器只会操作一次
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
Exception exception = null;
try {
boolean continueChain = this.preHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]");
}
if (continueChain) {
// 当前过滤器以及处理完成,让链路中的下一个过滤器来处理请求
this.executeChain(request, response, chain);
}
this.postHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Successfully invoked postHandle method");
}
} catch (Exception var9) {
exception = var9;
} finally {
this.cleanup(request, response, exception);
}
}
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return this.isAccessAllowed(request, response, mappedValue) || this.onAccessDenied(request, response, mappedValue);
}
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// 判断当前用户是否以及认证成功
Subject subject = this.getSubject(request, response);
return subject.isAuthenticated();
}
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (this.isLoginRequest(request, response)) { // 判断当前请求是否是认证的地址
if (this.isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return this.executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
return true;
}
} else {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
}
// 保存当前请求的地址,并跳转到登录页面
this.saveRequestAndRedirectToLogin(request, response);
return false;
}
}
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
AuthenticationToken token = this.createToken(request, response);
if (token == null) {
String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.";
throw new IllegalStateException(msg);
} else {
try {
Subject subject = this.getSubject(request, response);
// 认证方法的调用 会进入自定义的Realm中
subject.login(token);
// 认证成功
return this.onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException var5) {
// 认证失败
return this.onLoginFailure(token, var5, request, response);
}
}
}
8).多Realm的使用
9). 基于SSM的授权操作
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.21.RELEASE</version>
</dependency>
SpringMVC添加对应的配置
<!-- 放开Shiro注解 -->
<aop:config proxy-target-class="true"></aop:config>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--- Shiro 集成SpringMVC 拦截异常信息 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props >
<prop key="org.apache.shiro.authz.UnauthorizedException">redirect:/failed.jsp</prop>
</props>
</property>
</bean>
@RequiresRoles(value = {"USER_QUERY"})
基于注解控制的权限校验,可以控制请求成功与否。如果进入到特定页面后,那么还有更细粒度的权限控制,这时我们需要通过第三种权限校验,基于标签的权限校验
标签的使用
1.引入标签库
2.标签的使用
shiro:authenticated
shiro:guest
shiro:hasAnyRoles
shiro:hasPermission
shiro:locksPermisson
shiro:locksRole
shiro:notAuthenticated
shiro:principal
shiro:user
10). 缓存
1.依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.5.0</version>
</dependency>
2.配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--diskStore:缓存数据持久化的目录 地址 -->
<diskStore path="C:\tools\ehcache" />
<!--
eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。
maxElementsInMemory:缓存中允许创建的最大对象数
overflowToDisk:内存不足时,是否启用磁盘缓存。
timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前, 两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是 0 就意味着元素可以停顿无穷长的时间。
timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。
diskPersistent:设定在虚拟机重启时是否进行磁盘存储,默认为false
diskExpiryThreadIntervalSeconds: 属性可以设置该线程执行的间隔时间(默认是120秒,不能太小
1 FIFO,先进先出
2 LFU,最少被使用,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
3 LRU,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
3.shiro配置
<!-- 配置缓存管理器 -->
<bean class="org.apache.shiro.cache.ehcache.EhCacheManager" id="cacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- 配置SecurityManager -->
<bean class="org.apache.shiro.web.mgt.DefaultWebSecurityManager" id="securityManager">
<property name="realm" ref="myRealm"/>
<property name="cacheManager" ref="cacheManager"/>
</bean>
11).Session管理
Shiro的Session是不依赖于Web容器,如果是在Web项目中,那么Session和我们常用的的HttpSession是相通的
四、Spring整合SpringSecurity
SpringSecurity是Spring采用AOP思想,基于Servlet过滤器实现的一个权限管理框架,它提供了完善的认证机制和方法级的授权功能,是一款非常优秀的权限管理框架。
1.初始SpringSecurity
准备Spring
+SpringMVC
的web环境
添加SpringSecurity相关的依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
添加SpringSecurity对应的过滤器
<!-- 添加SpringSecurity的过滤器 -->
<filter>
<filter-name>springSecurityFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
添加SpringSecurity的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd">
<!--
auto-config="true" 表示自动加载SpringSecurity的配置文件
use-expressions="true" 使用Spring的EL表达式
-->
<security:http auto-config="true" use-expressions="true">
<!--
定义拦截资源
pattern="/**" 拦截所有的资源
access="hasAnyRole(ROLE_USER)" 表示只有ROLE_USER这个角色可以访问资源
-->
<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"></security:intercept-url>
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<!--
配置用户信息, noop:SpringSecurity中默认 密码验证是要加密的, noop表示不加密
-->
<security:user-service>
<security:user name="zhang" authorities="ROLE_USER" password="{noop}123"></security:user>
<security:user name="lisi" authorities="ROLE_USER" password="{noop}456"></security:user>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
2.自定义登录界面
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd">
<!-- 释放静态资源-->
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/js/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<!--
auto-config="true" 表示自动加载SpringSecurity的配置文件
use-expressions="true" 使用Spring的EL表达式
-->
<security:http auto-config="true" use-expressions="true">
<!-- 让认证页面可以匿名访问-->
<security:intercept-url pattern="/login.jsp" access="permitAll()"/>
<!--
定义拦截资源
pattern="/**" 拦截所有的资源
access="hasAnyRole(ROLE_USER)" 表示只有ROLE_USER这个角色可以访问资源
-->
<security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER')"></security:intercept-url>
<!-- 配置认证信息
login-page="/login.jsp" 自定义登录页面
login-processing-url="/login" security中处理登录的请求
default-target-url="/home.jsp" 默认的跳转页面
authentication-failure-url="/failure.jsp" 登录失败的页面
-->
<security:form-login
login-page="/login.jsp"
login-processing-url="/login"
default-target-url="/home.jsp"
authentication-failure-url="/failure.jsp"
/>
<!-- 关闭crsf过滤器 -->
<!--<security:csrf disabled="true"/>-->
</security:http>
<security:authentication-manager>
<security:authentication-provider>
<!--
配置用户信息, noop:SpringSecurity中默认 密码验证是要加密的, noop表示不加密
-->
<security:user-service>
<security:user name="zhang" authorities="ROLE_USER" password="{noop}123"></security:user>
<security:user name="lisi" authorities="ROLE_USER" password="{noop}456"></security:user>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
Crsf防护
<%--
Created by IntelliJ IDEA.
User: admin
Date: 2020/7/14
Time: 15:30
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<html>
<head>
<title>登录界面</title>
</head>
<body>
<h1>登录管理</h1>
<form action="/login" method="post">
账号:<input type="text" name="username"><br>
密码:<input type="password" name="password" ><br>
<security:csrfInput/>
<input type="submit" value="登录"><br>
</form>
<img src="img/t1.png">
</body>
</html>
3.认证的流程
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
// 认证的请求方法必须是POST请求
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 将请求提交的账号密码封装为了UsernamePasswordAuthenticationToken对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
// 认证的方法
return this.getAuthenticationManager().authenticate(authRequest);
}
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
// 获取所有的认证方式 账号密码 微信 QQ 邮箱 ...
Iterator var8 = this.getProviders().iterator();
while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
// 具体的认证操作
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (AccountStatusException var13) {
this.prepareException(var13, authentication);
throw var13;
} catch (InternalAuthenticationServiceException var14) {
this.prepareException(var14, authentication);
throw var14;
} catch (AuthenticationException var15) {
lastException = var15;
}
}
}
if (result == null && this.parent != null) {
try {
result = parentResult = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var11) {
;
} catch (AuthenticationException var12) {
parentException = var12;
lastException = var12;
}
}
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
this.prepareException((AuthenticationException)lastException, authentication);
}
throw lastException;
}
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
});
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
// 认证的方法
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User '" + username + "' not found");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
throw var6;
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
// 认证的方法
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
4.自定义数据库认证
package com.gupaoedu.security.service.impl;
import com.gupaoedu.security.bean.UserBean;
import com.gupaoedu.security.mapper.UserMapper;
import com.gupaoedu.security.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 让每一个人的职业生涯不留遗憾
*
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/14 17:09
*/
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper mapper;
@Override
public UserBean queryByUserName(String userName) {
return null;
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
// 去数据库中查询
List<UserBean> list = mapper.queryByUserName(s);
System.out.println("自定义的认证操作....");
if(list != null && list.size() == 1){
UserBean userBean = list.get(0);
if(userBean != null){
// 账号存在 给当前登录的账号 授权相关的角色
List<SimpleGrantedAuthority> authr = new ArrayList<>();
authr.add(new SimpleGrantedAuthority("ROLE_USER"));
UserDetails user = new User(s,"{noop}"+userBean.getPassword()
,authr);
return user;
}
}
// 返回空表示账号不存在
return null;
}
}
<security:authentication-manager>
<security:authentication-provider user-service-ref="userServiceImpl">
<!--
配置用户信息, noop:SpringSecurity中默认 密码验证是要加密的, noop表示不加密
<security:user-service>
<security:user name="zhang" authorities="ROLE_USER" password="{noop}123"></security:user>
<security:user name="lisi" authorities="ROLE_USER" password="{noop}456"></security:user>
</security:user-service>-->
</security:authentication-provider>
</security:authentication-manager>
5.密码加密
public static void main(String[] args) {
// SHA-256 + 随机salt
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
System.out.println(passwordEncoder.encode("123"));
System.out.println(passwordEncoder.encode("123"));
System.out.println(passwordEncoder.encode("123"));
}
loadUserByUsername中就不需要加{noop}
了
<!-- -->
<bean class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" id="passwordEncoder" />
<security:authentication-manager>
<security:authentication-provider user-service-ref="userServiceImpl">
<!--
配置用户信息, noop:SpringSecurity中默认 密码验证是要加密的, noop表示不加密
<security:user-service>
<security:user name="zhang" authorities="ROLE_USER" password="{noop}123"></security:user>
<security:user name="lisi" authorities="ROLE_USER" password="{noop}456"></security:user>
</security:user-service>-->
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
6.认证状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JXRRQjej-1626832880780)(img\1594725800312.png)]
7.授权
<security:global-method-security jsr250-annotations="enabled"
pre-post-annotations="enabled"
secured-annotations="enabled" />
1.jsr250
必须放开对AOP注解的支持
<!-- 打开AOP注解的支持 -->
<aop:aspectj-autoproxy proxy-target-class="true" />
添加aspectj的依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
@Controller
public class OrderController {
@RolesAllowed({"ROLE_USER"})
@RequestMapping("/roder/query")
public String query(){
return "/order.jsp";
}
@RolesAllowed({"ROLE_CREATE"})
@RequestMapping("/roder/create")
public String create(){
return "/order.jsp";
}
}
2.Spring表达式的使用
@Controller
public class RoleController {
@PreAuthorize(value = "hasAnyRole('ROLE_USER')")
@RequestMapping("/role/query")
public String query(){
return "/role.jsp";
}
@PreAuthorize(value = "hasAnyRole('ROLE_CREATE')")
@RequestMapping("/role/create")
public String create(){
return "/role.jsp";
}
}
3.SpringSecurity提供的注解
@RestController
public class UserController {
@Secured(value = {"ROLE_USER"})
@RequestMapping("/user/query")
public String query(){
return "hello query ";
}
@Secured(value = {"ROLE_CREATE"})
@RequestMapping("/user/create")
public String create(){
return "hello create";
}
}
4.通过标签处理
<%--
Created by IntelliJ IDEA.
User: admin
Date: 2020/7/14
Time: 20:44
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>角色管理</h1>
当前登录的账号:<security:authentication property="principal.username" />
<security:authorize access="hasAnyRole('ROLE_USER')">
<a href="#">系统管理</a><br>
</security:authorize>
<security:authorize access="hasAnyRole('ROLE_USER1')">
<a href="#">用户管理</a><br>
</security:authorize>
<security:authorize access="hasAnyRole('ROLE_CREATE')">
<a href="#">订单管理</a><br>
</security:authorize>
</body>
</html>
五、Spring整合Quartz
http://www.quartz-scheduler.org/overview/
Quartz 是一个特性丰富的,开源的任务调度库,它几乎可以嵌入所有的 Java 程序,从很小的独立应用程序到大型商业系统。Quartz 可以用来创建成百上千的简单的或者复杂的任务,这些任务可以用来执行任何程序可以做的事情。 Quartz 拥有很多企业级的特性,包括支持 JTA 事务和集群。
Quartz是一个老牌的任务调度系统,98年构思,01年发布,现在更新就比较慢了,原因是因为它已经非常成熟了。
任务调度框架: 任务+调度[执行]
1.Quartz基本使用
1.1 引入依赖
<dependencies>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
1.2 创建Job
public class MyJob implements Job {
/**
* 任务触发要执行的方法
* @param jobExecutionContext
* @throws JobExecutionException
*/
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("我是谁?我在哪?");
}
}
1.3 创建trigger
// 创建一个对应的触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();
1.4 任务调度
package com.gupaoedu.quartz.test;
import com.gupaoedu.quartz.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
/**
* 让每一个人的职业生涯不留遗憾
*
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/15 15:07
*/
public class Test01 {
/**
* Quartz 第一个案例
* @param args
*/
public static void main(String[] args) throws Exception {
// 创建一个对应的触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1","group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();
// JobDetial
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1")
.build();
// 将JobDetail 和 Trigger 关联起来
SchedulerFactory factory = new StdSchedulerFactory();
// 通过工厂对象获取对应的Schedule对象
Scheduler scheduler = factory.getScheduler();
// 绑定 JobDetail 和 Trigger
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
}
2.Quartz体系结构
2.1 JobDetail
我们创建的实现Job接口的实现类,使用JobBuilder包装成了一个JobDetail,它可以携带KV数据
2.2 Trigger
定义任务的触发规律,Trigger,使用TriggerBuilder来创建
子接口 | 描述 | 特点 |
---|---|---|
SimpleTrigger | 简单的触发器 | 固定时刻或时间间隔,毫秒 |
CalendarIntervalTrigger | 基于日历的触发器 | 比简单触发器有更多的时间单位,支持非固定时间的触发,例如一年可能365/366,一个月可能28/29/30/31 |
DailyTimeIntervalTrigger | 基于日期的触发器 | 每天的某个时间段 |
CronTrigger | 基于Cron表达式的触发器 | 基于表达式,功能更加的强大 |
Cron表达式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m79kOIWy-1626832880782)(img\1594798552973.png)]
星号(*):可用在所有字段中,表示对应时间域的每一个时刻,例如,在分钟字段时,表示“每分钟”;
问号(?):该字符只在日期和星期字段中使用,它通常指定为“无意义的值”,相当于点位符;
减号(-):表达一个范围,如在小时字段中使用“10-12”,则表示从 10 到 12 点,即 10,11,12;
逗号(,):表达一个列表值,如在星期字段中使用“MON,WED,FRI”,则表示星期一,星期三和星期五;
斜杠(/):x/y 表达一个等步长序列,x 为起始值,y 为增量步长值。如在分钟字段中使用 0/15,则表示为 0,15,30 和
45 秒,而 5/15 在分钟字段中表示 5,20,35,50,你也可以使用*/y,它等同于 0/y;
L:该字符只在日期和星期字段中使用,代表“Last”的意思,但它在两个字段中意思不同。L 在日期字段中,表示
这个月份的最后一天,如一月的 31 号,非闰年二月的 28 号;如果 L 用在星期中,则表示星期六,等同于 7。但是,如 果 L 出现在星期字段里,而且在前面有一个数值 X,则表示“这个月的最后 X 天”,例如,6L 表示该月的最后星期五;
W:该字符只能出现在日期字段里,是对前导日期的修饰,表示离该日期最近的工作日。例如 15W 表示离该月 15 号最近的工作日,如果该月 15 号是星期六,则匹配 14 号星期五;如果 15 日是星期日,则匹配 16 号星期一;如果 15 号是星期二,那结果就是 15 号星期二。但必须注意关联的匹配日期不能够跨月,如你指定 1W,如果 1 号是星期六, 结果匹配的是 3 号星期一,而非上个月最后的那天。W 字符串只能指定单一日期,而不能指定日期范围;
LW 组合:在日期字段可以组合使用 LW,它的意思是当月的最后一个工作日; 井号(#):该字符只能在星期字段中使用,表示当月某个工作日。如 6#3 表示当月的第三个星期五(6 表示星期五, #3 表示当前的第三个),而 4#5 表示当月的第五个星期三,假设当月没有第五个星期三,忽略不触发;
C:该字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是计划所关联的日期,如果日期没有 被关联,则相当于日历中所有日期。例如 5C 在日期字段中就相当于日历 5 日以后的第一天。1C 在星期字段中相当于 星期日后的第一天。
Cron 表达式对特殊字符的大小写不敏感,对代表星期的缩写英文大小写也不敏感。
3.Quartz中的监听器
Quartz中提供了三种监听器
1.监听Scheduler
2.监听Trigger的
3.监听Job的
4.Spring整合Quartz
4.1 导入相关的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gupaoedu</groupId>
<artifactId>gp_quartz_spring</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- quartz 的jar -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
<!-- spring相关jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
<!-- 日志相关jar包 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.6</version>
</dependency>
</dependencies>
</project>
4.2 创建对应的Job
package com.gupaoedu.quartz.job;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 让每一个人的职业生涯不留遗憾
*
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/15 16:15
*/
public class MyJob1 implements Job {
private Logger logger = LoggerFactory.getLogger(MyJob1.class);
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
logger.info("任务1执行了..." + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
4.3 Quartz的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!--
Spring整合Quartz的实现步骤:
1.定义工作任务Job
2.定义触发器Trigger,并将触发器和工作任务绑定
3.定义调度器Scheduler,并将Trigger注册到Scheduler中
-->
<bean id="myJob1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean" >
<!-- 指定Job的名称-->
<property name="name" value="my_job_1" />
<!-- 指定Job的组别 -->
<property name="group" value="my_goup_1" />
<!-- 指定Job的实现类 -->
<property name="jobClass" value="com.gupaoedu.quartz.job.MyJob1" />
<!-- 必须设置为true,如果设置为false,当没有活动的触发器与之关联时会在调度器中删除该任务 -->
<property name="durability" value="true" />
</bean>
<bean id="myJob2" class="org.springframework.scheduling.quartz.JobDetailFactoryBean" >
<!-- 指定Job的名称-->
<property name="name" value="my_job_2" />
<!-- 指定Job的组别 -->
<property name="group" value="my_goup_2" />
<!-- 指定Job的实现类 -->
<property name="jobClass" value="com.gupaoedu.quartz.job.MyJob2" />
<!-- 必须设置为true,如果设置为false,当没有活动的触发器与之关联时会在调度器中删除该任务 -->
<property name="durability" value="true" />
</bean>
<!-- 触发器 -->
<bean name="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean" >
<!-- Trigger的名称 -->
<property name="name" value="my_trigger_1" />
<!-- Trigger的组别 -->
<property name="group" value="my_group_1" />
<!-- 指定Trigger绑定的Job -->
<property name="jobDetail" ref="myJob1" />
<!-- 指定Trigger的延迟时间 -->
<property name="startDelay" value="1000"/>
<!-- 间隔时间 -->
<property name="repeatInterval" value="5000" />
<!-- 指定Trigger重复次数 -->
<property name="repeatCount" value="3" />
</bean>
<!-- 触发器 -->
<bean name="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean" >
<!-- Trigger的名称 -->
<property name="name" value="my_trigger_2" />
<!-- Trigger的组别 -->
<property name="group" value="my_group_2" />
<!-- 指定Trigger绑定的Job -->
<property name="jobDetail" ref="myJob2" />
<!-- 指定Cronin表达式 -->
<property name="cronExpression" value="0/1 * * * * ?" />
</bean>
<!-- 定义调度器 -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean" id="scheduler">
<property name="triggers">
<list>
<ref bean="simpleTrigger"></ref>
<ref bean="cronTrigger"></ref>
</list>
</property>
</bean>
</beans>
4.4 测试
public class Test01 {
public static void main(String[] args) throws SchedulerException {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-quratz.xml");
Scheduler scheduler = ac.getBean("scheduler", Scheduler.class);
scheduler.start();
}
}
六、Spring整合Lombok
1.Lombok是什么?
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
Lombok是一种Java实用工具,可以帮助开发人员消除Java代码中冗长的代码,尤其是对简单Java对象(POJO),通过注解来实现。
2.Lombok的原理是什么?
JSR269:插件话的注解处理API,JDK6提供的特性,在Javac的编译期利用注解实现
3.Lombok的安装
3.1 Javac方式使用
需要lombokJar的支持,将Lombok的jar拷贝到类路径中,
javac -cp lombok.jar ....
3.2 Maven
引入对应的Lombok依赖
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
3.3 Intellij IDEA
安装插件
The Jetbrains IntelliJ IDEA editor is compatible with lombok.
Add the Lombok IntelliJ plugin to add lombok support for IntelliJ:
- Go to
File > Settings > Plugins
- Click on
Browse repositories...
- Search for
Lombok Plugin
- Click on
Install plugin
- Restart IntelliJ IDEA
4.Lombok中的常用注解
1.@Getter和@Setter注解
@Getter和@Setter给我自动添加对应的Getter和Setter方法,该注解可以添加在属性头部,也可以添加在类的头部
对应的方法的访问修饰符通过AccessLevel控制
public enum AccessLevel {
PUBLIC,
MODULE,
PROTECTED,
PACKAGE,
PRIVATE,
NONE;
private AccessLevel() {
}
}
静态属性不会产生对应的Getter方法和Setter方法
final类型的属性只会产生Getter方法
2.@ToString
帮助我自动生成一个toString方法
如果不想要某些字段出现在ToString方法中,那么可以通过exclude属性指定要排除的属性
@ToString(exclude = {"address","age"})
public class User04 {
private Integer id;
private String userName;
private String password;
private String address;
private Integer age;
}
如果我们要指定哪些成员变量出现在toString方法中,那么我们可以通过of属性实现
@ToString(of = {"userName","age"})
public class User04 {
private Integer id;
private String userName;
private String password;
private String address;
private Integer age;
}
3.EqualsAndHashCode
帮助自动生成equals方法和hashCode方法
@EqualsAndHashCode
public class User05 {
private Integer id;
private String userName;
private String password;
private String address;
private Integer age;
}
要排除某些属性
@EqualsAndHashCode(exclude = {"address","age"})
public class User05 {
private Integer id;
private String userName;
private String password;
private String address;
private Integer age;
}
指定某些特定的属性来equals和hashCode
@EqualsAndHashCode(of = {"id"})
public class User05 {
private Integer id;
private String userName;
private String password;
private String address;
private Integer age;
}
4.NonNull
该注解添加在方法的参数前面表示该参数不能为空,否则抛NullpointException
5.构造器注解
NoArgsConstractor:无参的构造器
RequiredArgsConstructor:添加必要的有参构造器
被@NonNull修饰的成员变量和final修饰成员变量【未初始化】
AllArgsConstructor:添加具有所有成员变量的构造器
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.gupaoedu.pojo;
import lombok.NonNull;
public class User06 {
private Integer id;
final String HELLO;
@NonNull
private String userName;
private String password;
private String address;
private Integer age;
public void say(@NonNull String msg) {
if (msg == null) {
throw new NullPointerException("msg is marked non-null but is null");
} else {
System.out.println(msg);
}
}
public static void main(String[] args) {
}
public User06(String HELLO, @NonNull String userName) {
if (userName == null) {
throw new NullPointerException("userName is marked non-null but is null");
} else {
this.HELLO = HELLO;
this.userName = userName;
}
}
public User06(Integer id, String HELLO, @NonNull String userName, String password, String address, Integer age) {
if (userName == null) {
throw new NullPointerException("userName is marked non-null but is null");
} else {
this.id = id;
this.HELLO = HELLO;
this.userName = userName;
this.password = password;
this.address = address;
this.age = age;
}
}
}
6.Data注解
@Data其实是一个集成了 @Getter/@Setter+@ToString +@EqualsAndHashCode + @RequiredArgsConstructor注解
7.@Builder
给我提供一种对象链式调用的方式,会帮我们在POJO类中创建一个对应的内部类来实现该操作
@Data
@Builder
public class User07 {
private Integer id;
final String HELLO ;
@NonNull private String userName;
private String password;
private String address;
private Integer age;
public static void main(String[] args) {
User07 user = User07.builder()
.id(1)
.userName("波波老师")
.address("长沙")
.age(18)
.build();
System.out.println(user);
}
}
8.@Log
帮助我们简化日志操作
@Data
@Log
public class User08 {
private Integer id;
final String HELLO ;
@NonNull private String userName;
private String password;
private String address;
private Integer age;
public static void main(String[] args) {
log.info("bobo test ...");
}
}
9.val
类似于JavaScript中的var
,也就是当我们不清楚变量的类型的时候可以通过val
来修饰,当赋值的时候确定类型
public static void main(String[] args) {
Map<String,Object> map = new HashMap();
val map2 = new HashMap();
log.info("bobo test ...");
}
10.@Cleanup
帮助我们关闭IO流
public static void main(String[] args) throws IOException {
@Cleanup InputStream in = new FileInputStream("aaa.txt");
@Cleanup OutputStream out = new FileOutputStream("bbb.txt");
int num = 0;
byte[] b = new byte[1024];
while((num = in.read(b)) != -1){
out.write(b,0,num);
}
}
单体架构–>前后端分离
1个人了解项目的所有的内容—> 1个人仅仅了解项目的一部分内容
七、Spring整合Swagger
1.Swagger介绍
- 号称是世界上最流行的API框架
- Restful Api 文档在线生成器 ==> API文档与API定义同步更新
- 直接运行,在线测试API
- 支持多种语言【Java,PHP等】
- 官网: https://swagger.io
2.Swagger整合Spring使用
Swagger和SpringMVC的整合
2.1 创建web项目
添加对应的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gupaoedu</groupId>
<artifactId>gp_swagger_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>gp_swagger_demo Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.6.3</version>
</dependency>
</dependencies>
<build>
<finalName>gp_swagger_demo</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
2.2 整合SpringMVC
添加对应配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- SpringMVC容器只扫描Controller -->
<context:component-scan base-package="com.gupaoedu"
use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<mvc:annotation-driven />
<!-- swagger静态文件路径 -->
<!--重要!配置swagger资源不被拦截-->
<mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/" />
<mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/" />
<!--将静态资源交由默认的servlet处理-->
<mvc:default-servlet-handler/>
</beans>
web.xml文件中配置对应的前端控制器
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>test</display-name>
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
2.3 整合Swagger
1.依赖
springfox-swagger2
springfox-swagger-ui
必须的,然后还需要第三方 Jackson的支持
2.配置类
/**
* 让每一个人的职业生涯不留遗憾
* Swagger的配置类
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/17 16:27
*/
@Configuration
@EnableSwagger2 // 放开Swagger的支持
public class SwaggerConfig {
}
3.SpringMVC放开静态资源
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- SpringMVC容器只扫描Controller -->
<context:component-scan base-package="com.gupaoedu"
use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<mvc:annotation-driven />
<!-- swagger静态文件路径 -->
<!--重要!配置swagger资源不被拦截-->
<mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/" />
<mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/" />
<!--将静态资源交由默认的servlet处理-->
<mvc:default-servlet-handler/>
<!-- 将Swagger对应的配置类添加到IoC容器中 -->
<bean class="com.gupaoedu.config.SwaggerConfig" />
</beans>
2.4 测试效果
添加Tomcat插件
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8080</port> <!-- 访问端口-->
<path>/</path> <!-- 访问路径-->
</configuration>
</plugin>
</plugins>
http://localhost:8080/swagger-ui.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B7lEV3MY-1626832880783)(img\swagger-ui.png)]
3.覆盖默认的配置
在Swagger的配置类中我们重新去定义Docket对象,在其中指定相关的信息即可
package com.gupaoedu.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* 让每一个人的职业生涯不留遗憾
* Swagger的配置类
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/17 16:27
*/
@Configuration
@EnableSwagger2 // 放开Swagger的支持
public class SwaggerConfig {
/**
* 覆盖Swagger默认的配置
* @return
*/
@Bean
public Docket getDocket(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());
}
public ApiInfo apiInfo(){
Contact DEFAULT_CONTACT = new Contact("BoBo老师" ,"http://www.gupaoedu.com","dengpbs@163.com");
return new ApiInfo("GuPao BoBo 老师"
, "GuPao Swagger 测试"
, "1.0"
, "urn:tos"
, DEFAULT_CONTACT
, "Apache 2.0"
, "http://www.apache.org/licenses/LICENSE-2.0");
}
}
4.分组
package com.gupaoedu.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* 让每一个人的职业生涯不留遗憾
* Swagger的配置类
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/17 16:27
*/
@Configuration
@EnableSwagger2 // 放开Swagger的支持
public class SwaggerConfig {
/**
* 覆盖Swagger默认的配置
* @return
*/
@Bean
public Docket getDocket1(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("A") // 设置组别名称的时候一定不要用中文
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.gupaoedu.controller"))
.build();
}
/**
* 覆盖Swagger默认的配置
* @return
*/
@Bean
public Docket getDocket2(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("B")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.gupaoedu.controller2"))
.build();
}
public ApiInfo apiInfo(){
Contact DEFAULT_CONTACT = new Contact("BoBo老师" ,"http://www.gupaoedu.com","dengpbs@163.com");
return new ApiInfo("GuPao BoBo 老师"
, "GuPao Swagger 测试"
, "1.0"
, "urn:tos"
, DEFAULT_CONTACT
, "Apache 2.0"
, "http://www.apache.org/licenses/LICENSE-2.0");
}
}
5.常用的注解
@Api:作用在类头部,描述类
@Api(tags = {"用户管理"})
@RestController
public class UserController {
@GetMapping("/user/hello")
public String hello(){
return "Hello Swagger ...";
}
@PostMapping("/user/query")
public User query(){
return new User();
}
}
@ApiModel:在PO,VO中的类上,同样的是用来描述Bean信息的
@ApiModel(value = "用户Bean")
public class User {
private Integer id;
private String userName;
private String 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;
}
}
@ApiModelProperty:添加在Bean的属性中,描述给成员变量添加描述
@ApiModelProperty("编号")
private Integer id;
@ApiModelProperty("账号")
private String userName;
@ApiModelProperty("密码")
private String password;
@ApiParam:添加对方法中参数的描述
@ApiOperation:添加对方法功能的描述
@ApiIgnore:忽略方法或类型
@ApiIgnore
@GetMapping("/user/hello")
public String hello(){
return "Hello Swagger ...";
}
@ApiOperation("用户管理-查询用户信息")
@PostMapping("/user/query")
public User query(@ApiParam("账号") String userName, @ApiParam("密码") String password){
return new User();
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-850n1Jra-1626832880785)(img\swagger截图.png)]
八、Spring整合Druid
数据库连接池:管理数据库的连接,应用只专注于数据的操作。
1.常用的数据库连接池
数据库连接池 | 描述 |
---|---|
C3P0 | 开源的,开源项目有hibernate,Spring等 |
DBCP | 预创建连接,达到复用,减少资源消耗的目的 |
DRUID | 阿里巴巴,结合了C3P0和DBCP的优点,同时加入了监控。目前最好的连接池【德鲁伊】 |
2.基本使用
2.1 引入依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.22</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
2.2 测试代码
package com.gupaoedu.druid;
import com.alibaba.druid.pool.DruidDataSource;
import javax.sql.DataSource;
import java.sql.SQLException;
/**
* 让每一个人的职业生涯不留遗憾
*
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/18 15:15
*/
public class Demo {
private static final String URL = "jdbc:mysql://localhost:3306/gp?serverTimezone=UTC";
private static final String DRIVERNAME = "com.mysql.cj.jdbc.Driver";
private static final String USERNAME = "root";
private static final String PASSWORD = "123456";
/**
* Druid连接测试
* @param args
*/
public static void main(String[] args) throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(URL);
dataSource.setDriverClassName(DRIVERNAME);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
// 数据库连接池的相关属性
// 初始的连接数
dataSource.setInitialSize(5);
// 最小连接数
dataSource.setMinIdle(5);
// 最大连接数
dataSource.setMaxActive(30);
// 等待连接的最大时间
dataSource.setMaxWait(3000);
// 间隔时间 检测空闲
dataSource.setTimeBetweenEvictionRunsMillis(60000);
// 连接池的最下生存时间
dataSource.setMinEvictableIdleTimeMillis(40000);
System.out.println(dataSource.getConnection());
}
}
druid配置参数
配置 | 缺省值 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:“DataSource-” + System.identityHashCode(this) | |
jdbcUrl | 连接数据库的url,不同数据库不一样。例如: mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter | |
driverClassName | 根据url自动识别 | 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 有两个含义: 1) Destroy线程会检测连接的间隔时间 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | |
numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall | |
proxyFilters | 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
3.和Spring整合
添加对应的依赖
修改Spring配置文件中数据源的内容即可
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${names}" />
<property name="password" value="${password}" />
</bean>
4.Druid监控开启
Druid中内置的提供的有一个StatFilter,用于统建监控信息,StatFilter的别名stat, druid-xxx.jar /META-INF/druid-filter.properties中
druid.filters.default=com.alibaba.druid.filter.stat.StatFilter
druid.filters.stat=com.alibaba.druid.filter.stat.StatFilter
druid.filters.mergeStat=com.alibaba.druid.filter.stat.MergeStatFilter
druid.filters.counter=com.alibaba.druid.filter.stat.StatFilter
druid.filters.encoding=com.alibaba.druid.filter.encoding.EncodingConvertFilter
druid.filters.log4j=com.alibaba.druid.filter.logging.Log4jFilter
druid.filters.log4j2=com.alibaba.druid.filter.logging.Log4j2Filter
druid.filters.slf4j=com.alibaba.druid.filter.logging.Slf4jLogFilter
druid.filters.commonlogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.commonLogging=com.alibaba.druid.filter.logging.CommonsLogFilter
druid.filters.wall=com.alibaba.druid.wall.WallFilter
druid.filters.config=com.alibaba.druid.filter.config.ConfigFilter
4.1 开启统建
<!-- 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${names}" />
<property name="password" value="${password}" />
<!-- 开启监控统计 -->
<property name="filters" value="stat" />
</bean>
4.2 在web.xml文件中配置相关过滤器
<!--druid 监控 -->
<filter>
<filter-name>DruidWebStatFilter</filter-name>
<filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>exclusions</param-name>
<param-value>/static/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value>
</init-param>
<init-param>
<param-name>sessionStatMaxCount</param-name>
<param-value>1000</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>DruidWebStatFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- druid监控 -->
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>
4.3 添加对应的Controller
package com.gupaoedu.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
/**
* 让每一个人的职业生涯不留遗憾
*
* @author 波波老师【咕泡学院】
* @Description: ${todo}
* @date 2020/7/18 15:55
*/
@Controller
@RequestMapping("/druid")
public class DruidController {
@RequestMapping("/index")
public String index(Model model, HttpServletRequest request){
return "/druid/index";
}
}
4.4 测试
http://localhost:8080/druid/index.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RDMeeZPD-1626832880785)(img\druid监控.png)]