Spring的AOP
AOP的概念
AOP(Asspect Oriented Programming)面向切面编程,通过预编译的方式和运行期间的动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而业务逻辑的各部分的耦合度降低,提高程序的可重用性,同时提高开发效率。
通俗描述:不通过修改源代码的方式,在主干功能里面添加新功能。
Spring的AOP代理
spring会自动调整代理模式
xml配置
springProxy.xml
<?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 id="userDaoImpl" class="dynamicproxy.UserDaoImpl"></bean>
<bean id="userAdvice" class="dynamicproxy.UserAdvice"></bean>
<bean id="userProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetName" value="userDaoImpl"></property>
<property name="interceptorNames">
<array>
<value>userAdvice</value>
</array>
</property>
</bean>
</beans>
增强类的实现
public class UserAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("before");
methodInvocation.proceed();
return null;
}
}
JDK代理
public class UserDaoImpl implements UserDao{
// @Override
public int add(int a, int b) {
System.out.println("a+b");
return a+b;
}
public void test(){
System.out.println("test");
}
}
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath*:springProxy.xml")
public class SpringProxyTest {
@Autowired
@Qualifier(value="userProxy")
UserDao userDaoImpl;
@Test
public void t(){
userDaoImpl.test();
System.out.println(userDaoImpl.getClass());
}
}
CGLIB代理
public class UserDaoImpl {
public int add(int a, int b) {
System.out.println("a+b");
return a+b;
}
public void test(){
System.out.println("test");
}
}
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath*:springProxy.xml")
public class SpringProxyTest {
@Autowired
@Qualifier(value="userProxy")
UserDaoImpl userDaoImpl;
@Test
public void t(){
userDaoImpl.test();
System.out.println(userDaoImpl.getClass());
}
}
AOP术语
1、连接点:可以增强的方法。
2、切入点:实际被真正增强的方法。
3、通知(增强):方法中实际被增强的部分。
通知类型:
(1)前置通知
(2)后置通知 After
(3)环绕通知
(4)异常通知
(5)返回通知 return
4、切面:切点和通知加在一起即为切面
5:织入:把切面加入到切点上
AOP的Aspect操作
准备
1、Spring框架一般都是基于AspectJ实现AOP的操作。
(1)什么是AspectJ
AspectJ不是Spring组成部分,独立于AOP框架,一般把AspectJ和Spring框架一起进行AOP操作。AspectJ是静态代理的增强。所谓的静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。
2、基于AspectJ实现AOP操作
(1)基于xml配置文件实现
(2)基于注解实现
3、在项目工程引用AOP相关依赖,aspectJ.jar
4、切入点表达式
作用:知道对哪个类里面的哪个方法进行增强。
语法结构:
execution([权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]))
例如:
(1)对com.hn.dao.UserDao里面的add进行增强
//*表示任意[权限修饰符] [返回类型] …表示方法中的所有参数
execution(* com.hn.dao.UserDao.add(…))
(2)对com.hn.dao.UserDao里面所有的方法进行增强。
execution(* com.hn.dao.UserDao.*(…))
(3)对com.hn.dao包里面所有类类里面所有方法进行增强器。
execution(* com.hn.dao.*(…))
Aspect注解
1、创建类,在类里面定义方法
@Repository
public class User {
public int add(int b){
System.out.println("user add");
return b;
}
}
2、创建增强类,编写增强逻辑(各类通知)
@Component
@Aspect//第三步
public class UserProxy {
//第四步
@Before( value = "execution(* aopannotation.User.add(..))" )
public void before(){
System.out.println("proxy before");
}
/*
@After
@Around
@AfterThrowing
@AfterReturning
*/
@Around( value = "execution(* aopannotation.User.add(..))" )
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object[] args = proceedingJoinPoint.getArgs();
args[0]=5;
System.out.println("proxy环绕前");
Object proceed = proceedingJoinPoint.proceed( args );
System.out.println("proxy环绕后");
System.out.println(proceed);
return proceed;
}
}
3、进行通知的配置
(1)在spring配置文件中开启注解扫描
(2)在使用注解创建对象。
(3)在增强类中增加注解@Aspect
(4)在spring配置文件开启生成代理对象
<?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:aop="http://www.springframework.org/schema/aop"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="aopannotation"></context:component-scan>
<!--可才初始类加@EnableAspectJAutoProxy注解实现此配置,开启Aspectj自动代理-->
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
</beans>
4、配置不同类型的通知
使用时获取User的bean即可。
拓展:
(1)注意:
五大通知执行顺序
①Spring4.0
正常情况:环绕前置=====@Before目标方法执行=环绕返回=环绕最终===@After=====@AfterReturning
异常情况:环绕前置=====@Before目标方法执行=环绕异常=环绕最终===@After=====@AfterThrowing
②Spring5.28
正常情况:环绕前置=====@Before=目标方法执行=@AfterReturning=====@After=环绕返回=环绕最终
异常情况:环绕前置=====@Before=目标方法执行=@AfterThrowing=====@After=环绕异常=环绕最终
(2)其实对于Around之外的函数,可以通过JoinPoint来获得切入信息。
(3)使用@Pointcut进行execution的抽取
@Pointcut(value = "execution(* aopannotation.User.add(..))")
public void pointCutDemo(){}
@Before( value = "pointCutDemo()" )
public void before(){
System.out.println("proxy before");
}
(4)使用@Order配置顺序,如果一个切入点被同类型切入。
xml配置
<bean id="user" class="aopannotation.User"></bean>
<bean id="userProxy" class="aopannotation.UserProxy"></bean>
<aop:config>
<!--配置切入点-->
<aop:pointcut id="p" expression="execution(* aopannotation.User.add(..))"/>
<!--配置切面-->
<aop:aspect ref="userProxy">
<aop:before method="before" pointcut-ref="p"></aop:before>
</aop:aspect>
</aop:config>
底层原理(代理模式)
功能增强的方法
两种情况:
有接口的情况使用JDK动态代理。
静态代理:
public interface UserDao {
public void add();
}
public class UserDaoImpl implements UserDao{
@Override
public void add() {
System.out.println("userdao .....add");
}
}
public class UserProxy implements UserDao{
private UserDao userDao=new UserDaoImpl();
@Override
public void add() {
System.out.println("新增功能");
userDao.add();
}
}
JDK动态代理:
使用Proxy类
(1)调用newProxyInstance方法
public static Object newProxyInstance(ClassLoader loader,类<?>[] interfaces, InvocationHandler h)throws IllegalArgumentException
(2)要增强的类
public interface UserDao {
public int add(int a,int b);
public void test();
}
public class UserDaoImpl implements UserDao {
@Override
public int add(int a, int b) {
System.out.println("a+b");
return a+b;
}
public void test(){
System.out.println("test");
}
}
(3)编写动态代理的实现
class ProxyFactory{
public Object getInstance(Object obj){
Object o = Proxy.newProxyInstance( TestProxy.class.getClassLoader(),
obj.getClass().getInterfaces(), new myInvocation( obj ) );
return o;
}
}
class myInvocation implements InvocationHandler{
private Object obj;
public myInvocation(Object obj){
this.obj=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
Object res = method.invoke( obj, args );
System.out.println("after");
return res;
}
}
(4)调用
public static void main(String[] args) {
UserDao o = (UserDao)( new ProxyFactory().getInstance( new UserDaoImpl() ));
System.out.println( o.add( 1, 1 ) );
o.test();
}
没有接口情况使用cglib动态代理
使用cglib要导入cglib相关的依赖
创建子类增强方法,通过继承重写调用父类方法。
public class User{
public void introduction(){
System.out.println("我是人");
}
}
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("nb");
methodProxy.invokeSuper(o, objects );
// method.invoke( o,objects );调用栈溢出
return null;
}
}
class Test{
public static void main(String[] args) {
System.setProperty( DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader( Test.class.getClassLoader() );
enhancer.setSuperclass( User.class);
enhancer.setCallback( new MyMethodInterceptor());
User o = (User) enhancer.create( );
o.introduction();
}
工厂实现
public class MyMethodInterceptor implements MethodInterceptor {
private Object targetObject;
// 这里的目标类型为Object,则可以接受任意一种参数作为被代理类,实现了动态代理
public <T> T getInstance(T target) {
this.targetObject = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass( target.getClass() );
enhancer.setCallback( this );
//注意该处代理的创建过程
T proxyObj = (T) enhancer.create();
return proxyObj;// 返回代理对象
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object obj = null;
System.out.println( "doSomething---------start" );
obj = method.invoke( targetObject, args );
// methodProxy.invokeSuper(proxy,args );
System.out.println( "doSomething---------end" );
return obj;
}
总结一下JDK和cglib的区别
JDK是通过接口实现从而生成代理类,这个过程经过InvokeHandler,在编译期间就已经生成了
cglib是通过继承类,而生成代理类,这个过程是在运行的时候修改字节码文件生成的.
JDBCTemplace
概念
Spring矿建对JDBC进行封装,使用JDBCTemplate能进行基本的增删改查。
准备工作
引入依赖
配置xml
<?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">
<context:component-scan base-package="dao,service"></context:component-scan>
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<bean id="DruidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${mysql.username}"></property>
<property name="password" value="${mysql.password}"></property>
<property name="url" value="${mysql.url}"></property>
<property name="driverClassName" value="${mysql.driverClassName}"></property>
</bean>
<bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="DruidDataSource"></property>
</bean>
</beans>
增删改 jdbcTemplate.update();
查询:
单个值 jdbcTemplate.queryForObject()
集合 jdbcTemplate.query()
批量操作 jdbcTemplate.batchUpdate();
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
public int add(User user){
String sql="insert into user values(null,?,?,?,?)";
Object []args={user.getName(),user.getPassword(),user.getAddress(),user.getPhone()};
return jdbcTemplate.update( sql,args );
}
@Override
public int queryUserCount() {
String sql="select count(*) from user ";
return jdbcTemplate.queryForObject( sql,Integer.class );
}
@Override
public User queryUserById(int id) {
String sql="select* from user where id=?";
return jdbcTemplate.queryForObject( sql,new BeanPropertyRowMapper<User>(User.class),id );
}
@Override
public List<User> queryList() {
String sql="select* from user ";
return jdbcTemplate.query(sql,new BeanPropertyRowMapper<User>(User.class));
}
}
事务操作
两种方式
编程式事务管理和声明式事务管理。
编程式就是指对于每个情况都写代码进行事务管理。
一般使用AOP进行声明式事务管理。
mybatis以及jdbctemplate都能用DataSourceTransactionManager实现事务管理。
注解操作
引入tx名称空间
<?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:tx="http://www.springframework.org/schema/tx"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="dao,service"></context:component-scan>
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<bean id="DruidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${mysql.username}"></property>
<property name="password" value="${mysql.password}"></property>
<property name="url" value="${mysql.url}"></property>
<property name="driverClassName" value="${mysql.driverClassName}"></property>
</bean>
<bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="DruidDataSource"></property>
</bean>
<bean id="dataSourceTransactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="DruidDataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
</beans>
事务注解配置
@Transactional
1、propagation传播行为
主要为了管理方法之中的嵌套
spring框架中有7种:
例子:
add(){
update()
}
REQUIED 若有事务在运行,当前方法就在这个事务中执行,否则就启动一个新的事务,并在自己的方法中运行。意思就是update(),如果add()有事务就跟随add()使用同一个事务,否则就创建自己配置的事务。
REQUIED_NEW 当前方法必须启动新的事务,并在自己的事务中进行,无论是否已有事务正在运行。无论add()是否有事务,update()都会创建自己的事务进行运行。
2、ioslation隔离级别
脏读:一个未提交的事务读取到另一个未提交事务的数据。
不可重复度:一个为提交的事务读取到另一个已提交事务修改的数据。
幻读:一个为提交的事务读取到另一个已提交事务添加的数据。
3、timeout超时时间
事务需要在一定的时间内进行提交,否则就会回滚。
默认值为-1(不超时),设置时间以秒进行计算
4、readOnly是否只读
默认是false,如果为true,就不能进行增删改。
5、rollbackFor:回滚设置
spring默认只会对不检查异常的出现进行回滚
设置出现哪些检查异常进行事务回滚
6、noRollbackFor:不回滚
设置出现哪些异常不进行事务回滚
xml配置
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="dao,service"></context:component-scan>
<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
<bean id="DruidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${mysql.username}"></property>
<property name="password" value="${mysql.password}"></property>
<property name="url" value="${mysql.url}"></property>
<property name="driverClassName" value="${mysql.driverClassName}"></property>
</bean>
<bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="DruidDataSource"></property>
</bean>
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="DruidDataSource"></property>
</bean>
<!--<tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>-->
<!--在该方法上配置事务-->
<tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="transformMoney"/>
</tx:attributes>
</tx:advice>
<!--切点配置-->
<aop:config>
<aop:pointcut id="pt" expression="execution(* service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor>
</aop:config>
</beans>
完全注解开发
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(basePackages = {"dao","service"})
@EnableTransactionManagement
public class SpringConfig {
@Bean
public DruidDataSource getDruidDataSource(){
InputStream resourceAsStream = SpringConfig.class.getClassLoader().getResourceAsStream( "jdbc.properties" );
Properties properties = new Properties();
try {
properties.load( resourceAsStream );
} catch (IOException e) {
e.printStackTrace();
}
DruidDataSource druidDataSource=new DruidDataSource();
druidDataSource.setDriverClassName( properties.getProperty( "mysql.driverClassName" ) );
druidDataSource.setUsername( properties.getProperty( "mysql.username" ) );
druidDataSource.setPassword( properties.getProperty( "mysql.password" ) );
druidDataSource.setUrl( properties.getProperty( "mysql.url" ) );
return druidDataSource;
}
@Bean
public JdbcTemplate getJdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource( getDruidDataSource() );
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource( getDruidDataSource());
return dataSourceTransactionManager;
}
}
Spring5新功能
Spring5代码基于jdk8,向上兼容jdk9.
自带通用的日志封装
(1)Spring5已经移除了Log4jConfigListener,官方建议使用Log4j2。
(1)Spring5框架整合Log4j2。
导入相关依赖
配置xml,名字必须为log4j2
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出-->
<configuration status="INFO">
<!--先定义所有的appender-->
<appenders>
<!--输出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志输出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出-->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
自定义输出,主要导org.slf4j.Logge包的类
package config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Log {
private static final Logger log= LoggerFactory.getLogger( Log.class );
public static void main(String[] args) {
log.debug( "nb" );
}
}
核心容器支持@Nullable
@Nullable可以加在方法、参数、属性,表示方法的返回值可以为空,参数可以为空,属性可以为空。
核心容器支持函数式风格GenericApplicationContext
@Test
public void t(){
GenericApplicationContext context=new GenericApplicationContext();
context.refresh();
context.registerBean( "user",User.class,()->new User() );
Object user = context.getBean( "user" );
System.out.println(user);
}
JUnit测试包整合
(1)JUnit4
第一步 引入Spring相关测试的依赖,spring-test.jar
第二步 创建测试类
@RunWith(SpringRunner.class)//选择框架整合junit的版本
@ContextConfiguration("classpath:jdbc.xml")//选择要导入的配置
public class JTest{
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void t(){
System.out.println( jdbcTemplate );
}
}
(2)JUnit5
@SpringJUnitConfig(locations = "classpath:jdbc.xml")
public class JTest{
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void t(){
System.out.println( jdbcTemplate );
}
}